diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java index 3a07a74042..f4c0f26fc8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java @@ -492,8 +492,7 @@ public final class WebvttCueParser { return; } if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) { - spannedText.setSpan(new StyleSpan(style.getStyle()), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan(spannedText, new StyleSpan(style.getStyle()), start, end); } if (style.isLinethrough()) { spannedText.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -502,34 +501,29 @@ public final class WebvttCueParser { spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (style.hasFontColor()) { - spannedText.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan(spannedText, new ForegroundColorSpan(style.getFontColor()), start, end); } if (style.hasBackgroundColor()) { - spannedText.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan( + spannedText, new BackgroundColorSpan(style.getBackgroundColor()), start, end); } if (style.getFontFamily() != null) { - spannedText.setSpan(new TypefaceSpan(style.getFontFamily()), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan(spannedText, new TypefaceSpan(style.getFontFamily()), start, end); } Layout.Alignment textAlign = style.getTextAlign(); if (textAlign != null) { - spannedText.setSpan( - new AlignmentSpan.Standard(textAlign), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan(spannedText, new AlignmentSpan.Standard(textAlign), start, end); } switch (style.getFontSizeUnit()) { case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL: - spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan( + spannedText, new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end); break; case WebvttCssStyle.FONT_SIZE_UNIT_EM: - spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan(spannedText, new RelativeSizeSpan(style.getFontSize()), start, end); break; case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT: - spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + addOrReplaceSpan(spannedText, new RelativeSizeSpan(style.getFontSize() / 100), start, end); break; case WebvttCssStyle.UNSPECIFIED: // Do nothing. @@ -537,6 +531,26 @@ public final class WebvttCueParser { } } + /** + * Adds {@code span} to {@code spannedText} between {@code start} and {@code end}, removing any + * existing spans of the same type and with the same indices. + * + *

This is useful for types of spans that don't make sense to duplicate and where the + * evaluation order might have an unexpected impact on the final text, e.g. {@link + * ForegroundColorSpan}. + */ + private static void addOrReplaceSpan( + SpannableStringBuilder spannedText, Object span, int start, int end) { + Object[] existingSpans = spannedText.getSpans(start, end, span.getClass()); + for (Object existingSpan : existingSpans) { + if (spannedText.getSpanStart(existingSpan) == start + && spannedText.getSpanEnd(existingSpan) == end) { + spannedText.removeSpan(existingSpan); + } + } + spannedText.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + /** * Returns the tag name for the given tag contents. * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java index f405f1c407..5c044c029b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java @@ -405,7 +405,7 @@ public class WebvttDecoderTest { Spanned s4 = getUniqueSpanTextAt(subtitle, /* timeUs= */ 25000000); assertThat(s1.getSpans(/* start= */ 0, s1.length(), ForegroundColorSpan.class)).hasLength(1); assertThat(s1.getSpans(/* start= */ 0, s1.length(), BackgroundColorSpan.class)).hasLength(1); - assertThat(s2.getSpans(/* start= */ 0, s2.length(), ForegroundColorSpan.class)).hasLength(2); + assertThat(s2.getSpans(/* start= */ 0, s2.length(), ForegroundColorSpan.class)).hasLength(1); assertThat(s3.getSpans(/* start= */ 10, s3.length(), UnderlineSpan.class)).hasLength(1); assertThat(s4.getSpans(/* start= */ 0, /* end= */ 16, BackgroundColorSpan.class)).hasLength(2); assertThat(s4.getSpans(/* start= */ 17, s4.length(), StyleSpan.class)).hasLength(1);