From 2fd8cf0206175730eaa2371cd328b95e4a917322 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 28 Jan 2020 15:18:52 +0000 Subject: [PATCH] Add SubtitleWebView PiperOrigin-RevId: 291927263 --- .../android/exoplayer2/ui/SubtitleView.java | 94 ++++++++--- .../exoplayer2/ui/SubtitleWebView.java | 149 ++++++++++++++++++ 2 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index e4ac70a507..281ad7d1e3 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -16,18 +16,24 @@ */ package com.google.android.exoplayer2.ui; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.util.TypedValue; +import android.view.View; import android.view.ViewGroup; import android.view.accessibility.CaptioningManager; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; import java.util.Collections; import java.util.List; @@ -49,20 +55,50 @@ public final class SubtitleView extends ViewGroup implements TextOutput { */ public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f; - private final SubtitleTextView subtitleTextView; + /** + * Indicates a {@link SubtitleTextView} should be used to display subtitles. This is the default. + */ + public static final int VIEW_TYPE_TEXT = 1; + + /** + * Indicates a {@link SubtitleWebView} should be used to display subtitles. + * + *

This will instantiate a {@link android.webkit.WebView} and use CSS and HTML styling to + * render the subtitles. This supports some additional styling features beyond those supported by + * {@link SubtitleTextView} such as vertical text. + */ + public static final int VIEW_TYPE_WEB = 2; + + /** + * The type of {@link View} to use to display subtitles. + * + *

One of: + * + *

+ */ + @IntDef + @Documented + @Retention(SOURCE) + public @interface ViewType {} + + private Output output; + private View innerSubtitleView; public SubtitleView(Context context) { this(context, null); } - // The null checker doesn't like the `addView()` call, - @SuppressWarnings("nullness:method.invocation.invalid") public SubtitleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - subtitleTextView = new SubtitleTextView(context, attrs); - addView(subtitleTextView); + SubtitleTextView subtitleTextView = new SubtitleTextView(context, attrs); + output = subtitleTextView; + innerSubtitleView = subtitleTextView; + addView(innerSubtitleView); } - + @Override public void onCues(List cues) { setCues(cues); @@ -74,20 +110,43 @@ public final class SubtitleView extends ViewGroup implements TextOutput { * @param cues The cues to display, or null to clear the cues. */ public void setCues(@Nullable List cues) { - subtitleTextView.onCues(cues != null ? cues : Collections.emptyList()); + output.onCues(cues != null ? cues : Collections.emptyList()); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { - subtitleTextView.layout(l, t, r, b); + innerSubtitleView.layout(l, t, r, b); } } + /** + * Set the type of {@link View} used to display subtitles. + * + *

NOTE: {@link #VIEW_TYPE_WEB} is currently very experimental, and doesn't support most + * styling and layout properties of {@link Cue}. + */ + public void setViewType(@ViewType int viewType) { + if (viewType == VIEW_TYPE_TEXT && !(innerSubtitleView instanceof SubtitleTextView)) { + setView(new SubtitleTextView(getContext())); + } else if (viewType == VIEW_TYPE_WEB && !(innerSubtitleView instanceof SubtitleWebView)) { + setView(new SubtitleWebView(getContext())); + } else { + throw new IllegalArgumentException(); + } + } + + private void setView(T view) { + removeView(innerSubtitleView); + innerSubtitleView = view; + output = view; + addView(view); + } + /** * Set the text size to a given unit and value. - *

- * See {@link TypedValue} for the possible dimension units. + * + *

See {@link TypedValue} for the possible dimension units. * * @param unit The desired dimension unit. * @param size The desired size in the given units. @@ -144,7 +203,7 @@ public final class SubtitleView extends ViewGroup implements TextOutput { } private void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) { - subtitleTextView.setTextSize(textSizeType, textSize); + output.setTextSize(textSizeType, textSize); } /** @@ -154,7 +213,7 @@ public final class SubtitleView extends ViewGroup implements TextOutput { * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. */ public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { - subtitleTextView.setApplyEmbeddedStyles(applyEmbeddedStyles); + output.setApplyEmbeddedStyles(applyEmbeddedStyles); } /** @@ -164,7 +223,7 @@ public final class SubtitleView extends ViewGroup implements TextOutput { * @param applyEmbeddedFontSizes Whether font sizes embedded within the cues should be applied. */ public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) { - subtitleTextView.setApplyEmbeddedFontSizes(applyEmbeddedFontSizes); + output.setApplyEmbeddedFontSizes(applyEmbeddedFontSizes); } /** @@ -184,7 +243,7 @@ public final class SubtitleView extends ViewGroup implements TextOutput { * @param style A style for the view. */ public void setStyle(CaptionStyleCompat style) { - subtitleTextView.setStyle(style); + output.setStyle(style); } /** @@ -197,7 +256,7 @@ public final class SubtitleView extends ViewGroup implements TextOutput { * @param bottomPaddingFraction The bottom padding fraction. */ public void setBottomPaddingFraction(float bottomPaddingFraction) { - subtitleTextView.setBottomPaddingFraction(bottomPaddingFraction); + output.setBottomPaddingFraction(bottomPaddingFraction); } @TargetApi(19) @@ -223,15 +282,10 @@ public final class SubtitleView extends ViewGroup implements TextOutput { /* package */ interface Output { void onCues(List cues); - void setTextSize(@Cue.TextSizeType int textSizeType, float textSize); - void setApplyEmbeddedStyles(boolean applyEmbeddedStyles); - void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes); - void setStyle(CaptionStyleCompat style); - void setBottomPaddingFraction(float bottomPaddingFraction); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java new file mode 100644 index 0000000000..06039c9016 --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.google.android.exoplayer2.ui; + +import static com.google.android.exoplayer2.ui.SubtitleView.DEFAULT_BOTTOM_PADDING_FRACTION; +import static com.google.android.exoplayer2.ui.SubtitleView.DEFAULT_TEXT_SIZE_FRACTION; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.webkit.WebView; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.text.CaptionStyleCompat; +import com.google.android.exoplayer2.text.Cue; +import java.util.Collections; +import java.util.List; + +/** + * A {@link SubtitleView.Output} that uses a {@link WebView} to render subtitles. + * + *

This is useful for subtitle styling not supported by Android's native text libraries such as + * vertical text. + * + *

NOTE: This is currently extremely experimental and doesn't support most {@link Cue} styling + * properties. + */ +/* package */ final class SubtitleWebView extends ViewGroup implements SubtitleView.Output { + + private final WebView webView; + + private List cues; + @Cue.TextSizeType private int textSizeType; + private float textSize; + private boolean applyEmbeddedStyles; + private boolean applyEmbeddedFontSizes; + private CaptionStyleCompat style; + private float bottomPaddingFraction; + + public SubtitleWebView(Context context) { + this(context, null); + } + + public SubtitleWebView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + cues = Collections.emptyList(); + textSizeType = Cue.TEXT_SIZE_TYPE_FRACTIONAL; + textSize = DEFAULT_TEXT_SIZE_FRACTION; + applyEmbeddedStyles = true; + applyEmbeddedFontSizes = true; + style = CaptionStyleCompat.DEFAULT; + bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; + + webView = new WebView(context, attrs); + webView.setBackgroundColor(Color.TRANSPARENT); + addView(webView); + } + + @Override + public void onCues(List cues) { + this.cues = cues; + updateWebView(); + } + + @Override + public void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) { + if (this.textSizeType == textSizeType && this.textSize == textSize) { + return; + } + this.textSizeType = textSizeType; + this.textSize = textSize; + updateWebView(); + } + + @Override + public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { + if (this.applyEmbeddedStyles == applyEmbeddedStyles + && this.applyEmbeddedFontSizes == applyEmbeddedStyles) { + return; + } + this.applyEmbeddedStyles = applyEmbeddedStyles; + this.applyEmbeddedFontSizes = applyEmbeddedStyles; + updateWebView(); + } + + @Override + public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) { + if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) { + return; + } + this.applyEmbeddedFontSizes = applyEmbeddedFontSizes; + updateWebView(); + } + + @Override + public void setStyle(CaptionStyleCompat style) { + if (this.style == style) { + return; + } + this.style = style; + updateWebView(); + } + + @Override + public void setBottomPaddingFraction(float bottomPaddingFraction) { + if (this.bottomPaddingFraction == bottomPaddingFraction) { + return; + } + this.bottomPaddingFraction = bottomPaddingFraction; + updateWebView(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (changed) { + webView.layout(l, t, r, b); + } + } + + private void updateWebView() { + StringBuilder cueText = new StringBuilder(); + for (int i = 0; i < cues.size(); i++) { + if (i > 0) { + cueText.append("
"); + } + cueText.append(cues.get(i).text); + } + webView.loadData( + "

" + + cueText + + "

", + "text/html", + /* encoding= */ null); + } +}