diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bc3e4b6d7f..74ceadc863 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -41,6 +41,11 @@ (rendering is coming later). * Fix `SubtitlePainter` to render `EDGE_TYPE_OUTLINE` using the correct color ([#6724](https://github.com/google/ExoPlayer/pull/6724)). + * Add support for WebVTT default + [text](https://www.w3.org/TR/webvtt1/#default-text-color) and + [background](https://www.w3.org/TR/webvtt1/#default-text-background) colors + ([PR #4178](https://github.com/google/ExoPlayer/pull/4178), + [issue #6581](https://github.com/google/ExoPlayer/issues/6581)). * DRM: Add support for attaching DRM sessions to clear content in the demo app. * Downloads: Merge downloads in `SegmentDownloader` to improve overall download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)). 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 f62b073f60..c57d9d2164 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 @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.text.webvtt; import static com.google.android.exoplayer2.text.SpanUtil.addOrReplaceSpan; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.graphics.Color; import android.graphics.Typeface; import android.text.Layout; import android.text.SpannableStringBuilder; @@ -48,7 +49,9 @@ import java.lang.annotation.Retention; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -137,6 +140,44 @@ public final class WebvttCueParser { private static final String TAG = "WebvttCueParser"; + /** + * See WebVTT's default text + * colors. + */ + private static final Map DEFAULT_TEXT_COLORS; + + static { + Map defaultColors = new HashMap<>(); + defaultColors.put("white", Color.rgb(255, 255, 255)); + defaultColors.put("lime", Color.rgb(0, 255, 0)); + defaultColors.put("cyan", Color.rgb(0, 255, 255)); + defaultColors.put("red", Color.rgb(255, 0, 0)); + defaultColors.put("yellow", Color.rgb(255, 255, 0)); + defaultColors.put("magenta", Color.rgb(255, 0, 255)); + defaultColors.put("blue", Color.rgb(0, 0, 255)); + defaultColors.put("black", Color.rgb(0, 0, 0)); + DEFAULT_TEXT_COLORS = Collections.unmodifiableMap(defaultColors); + } + + /** + * See WebVTT's default text + * background colors. + */ + private static final Map DEFAULT_BACKGROUND_COLORS; + + static { + Map defaultBackgroundColors = new HashMap<>(); + defaultBackgroundColors.put("bg_white", Color.rgb(255, 255, 255)); + defaultBackgroundColors.put("bg_lime", Color.rgb(0, 255, 0)); + defaultBackgroundColors.put("bg_cyan", Color.rgb(0, 255, 255)); + defaultBackgroundColors.put("bg_red", Color.rgb(255, 0, 0)); + defaultBackgroundColors.put("bg_yellow", Color.rgb(255, 255, 0)); + defaultBackgroundColors.put("bg_magenta", Color.rgb(255, 0, 255)); + defaultBackgroundColors.put("bg_blue", Color.rgb(0, 0, 255)); + defaultBackgroundColors.put("bg_black", Color.rgb(0, 0, 0)); + DEFAULT_BACKGROUND_COLORS = Collections.unmodifiableMap(defaultBackgroundColors); + } + /** * Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text. * @@ -514,6 +555,8 @@ public final class WebvttCueParser { text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); break; case TAG_CLASS: + applyDefaultColors(text, startTag.classes, start, end); + break; case TAG_LANG: case TAG_VOICE: case "": // Case of the "whole cue" virtual tag. @@ -529,6 +572,26 @@ public final class WebvttCueParser { } } + /** + * Adds {@link ForegroundColorSpan}s and {@link BackgroundColorSpan}s to {@code text} for entries + * in {@code classes} that match WebVTT's default text colors or default text background + * colors. + */ + private static void applyDefaultColors( + SpannableStringBuilder text, String[] classes, int start, int end) { + for (String className : classes) { + if (DEFAULT_TEXT_COLORS.containsKey(className)) { + int color = DEFAULT_TEXT_COLORS.get(className); + text.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (DEFAULT_BACKGROUND_COLORS.containsKey(className)) { + int color = DEFAULT_BACKGROUND_COLORS.get(className); + text.setSpan(new BackgroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + private static void applyStyleToText(SpannableStringBuilder spannedText, WebvttCssStyle style, int start, int end) { if (style == null) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java index aa83fbc8ed..e308300d07 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.text.webvtt; import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat; import static com.google.common.truth.Truth.assertThat; +import android.graphics.Color; import android.text.Spanned; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.text.span.RubySpan; @@ -79,6 +80,47 @@ public final class WebvttCueParserTest { .withTextAndPosition("", RubySpan.POSITION_OVER); } + @Test + public void testParseDefaultTextColor() throws Exception { + Spanned text = parseCueText("In this sentence this text is red"); + + assertThat(text.toString()).isEqualTo("In this sentence this text is red"); + assertThat(text) + .hasForegroundColorSpanBetween( + "In this sentence ".length(), "In this sentence this text".length()) + .withColor(Color.RED); + } + + @Test + public void testParseUnsupportedDefaultTextColor() throws Exception { + Spanned text = parseCueText("In this sentence this text is not papaya"); + + assertThat(text.toString()).isEqualTo("In this sentence this text is not papaya"); + assertThat(text).hasNoSpans(); + } + + @Test + public void testParseDefaultBackgroundColor() throws Exception { + Spanned text = parseCueText("In this sentence this text has a cyan background"); + + assertThat(text.toString()).isEqualTo("In this sentence this text has a cyan background"); + assertThat(text) + .hasBackgroundColorSpanBetween( + "In this sentence ".length(), "In this sentence this text".length()) + .withColor(Color.CYAN); + } + + @Test + public void testParseUnsupportedDefaultBackgroundColor() throws Exception { + Spanned text = + parseCueText( + "In this sentence this text doesn't have a papaya background"); + + assertThat(text.toString()) + .isEqualTo("In this sentence this text doesn't have a papaya background"); + assertThat(text).hasNoSpans(); + } + @Test public void testParseWellFormedUnclosedEndAtCueEnd() throws Exception { Spanned text = parseCueText("An unclosed u tag with "