Commit 99ebeaed by ibaker Committed by Oliver Woodman

Change the SubtitleView.Output interface

This allows properties to propagate when switching view types
(e.g. bottomPaddingFraction).

It also allows the style-stripping code to be pushed up to SubtitleView
and therefore shared.

PiperOrigin-RevId: 310353534
parent 70273a03
......@@ -807,6 +807,12 @@ public final class Cue {
return this;
}
/** Sets {@link Cue#windowColorSet} to false. */
public Builder clearWindowColor() {
this.windowColorSet = false;
return this;
}
/**
* Returns true if the fill color of the window is set.
*
......
......@@ -77,6 +77,14 @@ public class CueTest {
}
@Test
public void clearWindowColor() {
Cue cue =
new Cue.Builder().setText(SpannedString.valueOf("text")).setWindowColor(Color.CYAN).build();
assertThat(cue.buildUpon().clearWindowColor().build().windowColorSet).isFalse();
}
@Test
public void buildWithNoTextOrBitmapFails() {
assertThrows(RuntimeException.class, () -> new Cue.Builder().build());
}
......
......@@ -34,7 +34,6 @@ import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
......@@ -82,8 +81,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private int cuePositionAnchor;
private float cueSize;
private float cueBitmapHeight;
private boolean applyEmbeddedStyles;
private boolean applyEmbeddedFontSizes;
private int foregroundColor;
private int backgroundColor;
private int windowColor;
......@@ -142,8 +139,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* which the same parameters are passed.
*
* @param cue The cue to draw.
* @param applyEmbeddedStyles Whether styling embedded within the cue should be applied.
* @param applyEmbeddedFontSizes If {@code applyEmbeddedStyles} is true, defines whether font
* sizes embedded within the cue should be applied. Otherwise, it is ignored.
* @param style The style to use when drawing the cue text.
* @param defaultTextSizePx The default text size to use when drawing the text, in pixels.
......@@ -158,8 +153,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
*/
public void draw(
Cue cue,
boolean applyEmbeddedStyles,
boolean applyEmbeddedFontSizes,
CaptionStyleCompat style,
float defaultTextSizePx,
float cueTextSizePx,
......@@ -176,8 +169,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// Nothing to draw.
return;
}
windowColor = (cue.windowColorSet && applyEmbeddedStyles)
? cue.windowColor : style.windowColor;
windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor;
}
if (areCharSequencesEqual(this.cueText, cue.text)
&& Util.areEqual(this.cueTextAlignment, cue.textAlignment)
......@@ -189,8 +181,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
&& Util.areEqual(this.cuePositionAnchor, cue.positionAnchor)
&& this.cueSize == cue.size
&& this.cueBitmapHeight == cue.bitmapHeight
&& this.applyEmbeddedStyles == applyEmbeddedStyles
&& this.applyEmbeddedFontSizes == applyEmbeddedFontSizes
&& this.foregroundColor == style.foregroundColor
&& this.backgroundColor == style.backgroundColor
&& this.windowColor == windowColor
......@@ -219,8 +209,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
this.cuePositionAnchor = cue.positionAnchor;
this.cueSize = cue.size;
this.cueBitmapHeight = cue.bitmapHeight;
this.applyEmbeddedStyles = applyEmbeddedStyles;
this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
this.foregroundColor = style.foregroundColor;
this.backgroundColor = style.backgroundColor;
this.windowColor = windowColor;
......@@ -266,31 +254,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return;
}
// Remove embedded styling or font size if requested.
if (!applyEmbeddedStyles) {
// Remove all spans, regardless of type.
for (Object span : cueText.getSpans(0, cueText.length(), Object.class)) {
cueText.removeSpan(span);
}
} else if (!applyEmbeddedFontSizes) {
AbsoluteSizeSpan[] absSpans = cueText.getSpans(0, cueText.length(), AbsoluteSizeSpan.class);
for (AbsoluteSizeSpan absSpan : absSpans) {
cueText.removeSpan(absSpan);
}
RelativeSizeSpan[] relSpans = cueText.getSpans(0, cueText.length(), RelativeSizeSpan.class);
for (RelativeSizeSpan relSpan : relSpans) {
cueText.removeSpan(relSpan);
}
} else {
// Apply embedded styles & font size.
if (cueTextSizePx > 0) {
// Use an AbsoluteSizeSpan encompassing the whole text to apply the default cueTextSizePx.
cueText.setSpan(
new AbsoluteSizeSpan((int) cueTextSizePx),
/* start= */ 0,
/* end= */ cueText.length(),
Spanned.SPAN_PRIORITY);
}
if (cueTextSizePx > 0) {
// Use an AbsoluteSizeSpan encompassing the whole text to apply the default cueTextSizePx.
cueText.setSpan(
new AbsoluteSizeSpan((int) cueTextSizePx),
/* start= */ 0,
/* end= */ cueText.length(),
Spanned.SPAN_PRIORITY);
}
// Remove embedded font color to not destroy edges, otherwise it overrides edge color.
......
......@@ -40,8 +40,6 @@ import java.util.List;
private List<Cue> cues;
@Cue.TextSizeType private int textSizeType;
private float textSize;
private boolean applyEmbeddedStyles;
private boolean applyEmbeddedFontSizes;
private CaptionStyleCompat style;
private float bottomPaddingFraction;
......@@ -55,18 +53,22 @@ import java.util.List;
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;
}
@Override
public void onCues(List<Cue> cues) {
if (this.cues == cues || this.cues.isEmpty() && cues.isEmpty()) {
return;
}
public void update(
List<Cue> cues,
CaptionStyleCompat style,
float textSize,
@Cue.TextSizeType int textSizeType,
float bottomPaddingFraction) {
this.cues = cues;
this.style = style;
this.textSize = textSize;
this.textSizeType = textSizeType;
this.bottomPaddingFraction = bottomPaddingFraction;
// Ensure we have sufficient painters.
while (painters.size() < cues.size()) {
painters.add(new SubtitlePainter(getContext()));
......@@ -76,54 +78,6 @@ import java.util.List;
}
@Override
public void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
if (this.textSizeType == textSizeType && this.textSize == textSize) {
return;
}
this.textSizeType = textSizeType;
this.textSize = textSize;
invalidate();
}
@Override
public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
if (this.applyEmbeddedStyles == applyEmbeddedStyles
&& this.applyEmbeddedFontSizes == applyEmbeddedStyles) {
return;
}
this.applyEmbeddedStyles = applyEmbeddedStyles;
this.applyEmbeddedFontSizes = applyEmbeddedStyles;
invalidate();
}
@Override
public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) {
return;
}
this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
invalidate();
}
@Override
public void setStyle(CaptionStyleCompat style) {
if (this.style == style) {
return;
}
this.style = style;
invalidate();
}
@Override
public void setBottomPaddingFraction(float bottomPaddingFraction) {
if (this.bottomPaddingFraction == bottomPaddingFraction) {
return;
}
this.bottomPaddingFraction = bottomPaddingFraction;
invalidate();
}
@Override
public void dispatchDraw(Canvas canvas) {
@Nullable List<Cue> cues = this.cues;
if (cues.isEmpty()) {
......@@ -163,8 +117,6 @@ import java.util.List;
SubtitlePainter painter = painters.get(i);
painter.draw(
cue,
applyEmbeddedStyles,
applyEmbeddedFontSizes,
style,
defaultViewTextSizePx,
cueTextSizePx,
......
......@@ -20,6 +20,10 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
import android.content.res.Resources;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
......@@ -35,6 +39,7 @@ 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.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -85,6 +90,14 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
@IntDef({VIEW_TYPE_TEXT, VIEW_TYPE_WEB})
public @interface ViewType {}
private List<Cue> cues;
private CaptionStyleCompat style;
@Cue.TextSizeType private int defaultTextSizeType;
private float defaultTextSize;
private float bottomPaddingFraction;
private boolean applyEmbeddedStyles;
private boolean applyEmbeddedFontSizes;
private @ViewType int viewType;
private Output output;
private View innerSubtitleView;
......@@ -95,6 +108,14 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
public SubtitleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
cues = Collections.emptyList();
style = CaptionStyleCompat.DEFAULT;
defaultTextSizeType = Cue.TEXT_SIZE_TYPE_FRACTIONAL;
defaultTextSize = DEFAULT_TEXT_SIZE_FRACTION;
bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION;
applyEmbeddedStyles = true;
applyEmbeddedFontSizes = true;
SubtitleTextView subtitleTextView = new SubtitleTextView(context, attrs);
output = subtitleTextView;
innerSubtitleView = subtitleTextView;
......@@ -113,7 +134,8 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
* @param cues The cues to display, or null to clear the cues.
*/
public void setCues(@Nullable List<Cue> cues) {
output.onCues(cues != null ? cues : Collections.emptyList());
this.cues = (cues != null ? cues : Collections.emptyList());
updateOutput();
}
/**
......@@ -149,6 +171,7 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
innerSubtitleView = view;
output = view;
addView(view);
updateOutput();
}
/**
......@@ -211,7 +234,9 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
}
private void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
output.setTextSize(textSizeType, textSize);
this.defaultTextSizeType = textSizeType;
this.defaultTextSize = textSize;
updateOutput();
}
/**
......@@ -221,7 +246,8 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
* @param applyEmbeddedStyles Whether styling embedded within the cues should be applied.
*/
public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
output.setApplyEmbeddedStyles(applyEmbeddedStyles);
this.applyEmbeddedStyles = applyEmbeddedStyles;
updateOutput();
}
/**
......@@ -231,7 +257,8 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
* @param applyEmbeddedFontSizes Whether font sizes embedded within the cues should be applied.
*/
public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
output.setApplyEmbeddedFontSizes(applyEmbeddedFontSizes);
this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
updateOutput();
}
/**
......@@ -251,7 +278,8 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
* @param style A style for the view.
*/
public void setStyle(CaptionStyleCompat style) {
output.setStyle(style);
this.style = style;
updateOutput();
}
/**
......@@ -264,7 +292,8 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
* @param bottomPaddingFraction The bottom padding fraction.
*/
public void setBottomPaddingFraction(float bottomPaddingFraction) {
output.setBottomPaddingFraction(bottomPaddingFraction);
this.bottomPaddingFraction = bottomPaddingFraction;
updateOutput();
}
@RequiresApi(19)
......@@ -288,12 +317,94 @@ public final class SubtitleView extends FrameLayout implements TextOutput {
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
private void updateOutput() {
output.update(
getCuesWithStylingPreferencesApplied(),
style,
defaultTextSize,
defaultTextSizeType,
bottomPaddingFraction);
}
/**
* Returns {@link #cues} with {@link #applyEmbeddedStyles} and {@link #applyEmbeddedFontSizes}
* applied.
*
* <p>If {@link #applyEmbeddedStyles} is false then all styling spans are removed from {@link
* Cue#text}, {@link Cue#textSize} and {@link Cue#textSizeType} are set to {@link Cue#DIMEN_UNSET}
* and {@link Cue#windowColorSet} is set to false.
*
* <p>Otherwise if {@link #applyEmbeddedFontSizes} is false then only size-related styling spans
* are removed from {@link Cue#text} and {@link Cue#textSize} and {@link Cue#textSizeType} are set
* to {@link Cue#DIMEN_UNSET}
*/
private List<Cue> getCuesWithStylingPreferencesApplied() {
if (applyEmbeddedStyles && applyEmbeddedFontSizes) {
return cues;
}
List<Cue> strippedCues = new ArrayList<>(cues.size());
for (int i = 0; i < cues.size(); i++) {
strippedCues.add(removeEmbeddedStyling(cues.get(i)));
}
return strippedCues;
}
private Cue removeEmbeddedStyling(Cue cue) {
@Nullable CharSequence cueText = cue.text;
if (!applyEmbeddedStyles) {
Cue.Builder strippedCue =
cue.buildUpon().setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET).clearWindowColor();
if (cueText != null) {
// Remove all spans, regardless of type.
strippedCue.setText(cueText.toString());
}
return strippedCue.build();
} else if (!applyEmbeddedFontSizes) {
if (cueText == null) {
return cue;
}
Cue.Builder strippedCue = cue.buildUpon().setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET);
if (cueText instanceof Spanned) {
SpannableString spannable = SpannableString.valueOf(cueText);
AbsoluteSizeSpan[] absSpans =
spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class);
for (AbsoluteSizeSpan absSpan : absSpans) {
spannable.removeSpan(absSpan);
}
RelativeSizeSpan[] relSpans =
spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class);
for (RelativeSizeSpan relSpan : relSpans) {
spannable.removeSpan(relSpan);
}
strippedCue.setText(spannable);
}
return strippedCue.build();
}
return cue;
}
/* package */ interface Output {
void onCues(List<Cue> 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);
/**
* Updates the list of cues displayed.
*
* @param cues The cues to display.
* @param style A {@link CaptionStyleCompat} to use for styling unset properties of cues.
* @param defaultTextSize The default font size to apply when {@link Cue#textSize} is {@link
* Cue#DIMEN_UNSET}.
* @param defaultTextSizeType The type of {@code defaultTextSize}.
* @param bottomPaddingFraction The bottom padding to apply when {@link Cue#line} is {@link
* Cue#DIMEN_UNSET}, as a fraction of the view's remaining height after its top and bottom
* padding have been subtracted.
* @see #setStyle(CaptionStyleCompat)
* @see #setTextSize(int, float)
* @see #setBottomPaddingFraction(float)
*/
void update(
List<Cue> cues,
CaptionStyleCompat style,
float defaultTextSize,
@Cue.TextSizeType int defaultTextSizeType,
float bottomPaddingFraction);
}
}
......@@ -16,9 +16,6 @@
*/
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.text.Layout;
......@@ -57,14 +54,6 @@ import java.util.List;
private final SubtitleTextView subtitleTextView;
private final WebView webView;
private final List<Cue> cues;
@Cue.TextSizeType private int defaultTextSizeType;
private float defaultTextSize;
private boolean applyEmbeddedStyles;
private boolean applyEmbeddedFontSizes;
private CaptionStyleCompat style;
private float bottomPaddingFraction;
public SubtitleWebView(Context context) {
this(context, null);
......@@ -72,13 +61,6 @@ import java.util.List;
public SubtitleWebView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
cues = new ArrayList<>();
defaultTextSizeType = Cue.TEXT_SIZE_TYPE_FRACTIONAL;
defaultTextSize = DEFAULT_TEXT_SIZE_FRACTION;
applyEmbeddedStyles = true;
applyEmbeddedFontSizes = true;
style = CaptionStyleCompat.DEFAULT;
bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION;
subtitleTextView = new SubtitleTextView(context, attrs);
webView =
......@@ -104,74 +86,26 @@ import java.util.List;
}
@Override
public void onCues(List<Cue> cues) {
public void update(
List<Cue> cues,
CaptionStyleCompat style,
float textSize,
@Cue.TextSizeType int textSizeType,
float bottomPaddingFraction) {
List<Cue> bitmapCues = new ArrayList<>();
this.cues.clear();
List<Cue> textCues = new ArrayList<>();
for (int i = 0; i < cues.size(); i++) {
Cue cue = cues.get(i);
if (cue.bitmap != null) {
bitmapCues.add(cue);
} else {
this.cues.add(cue);
textCues.add(cue);
}
}
subtitleTextView.onCues(bitmapCues);
subtitleTextView.update(bitmapCues, style, textSize, textSizeType, bottomPaddingFraction);
// Invalidate to trigger subtitleTextView to draw.
invalidate();
updateWebView();
}
@Override
public void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
if (this.defaultTextSizeType == textSizeType && this.defaultTextSize == textSize) {
return;
}
this.defaultTextSizeType = textSizeType;
this.defaultTextSize = textSize;
invalidate();
updateWebView();
}
@Override
public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
if (this.applyEmbeddedStyles == applyEmbeddedStyles
&& this.applyEmbeddedFontSizes == applyEmbeddedStyles) {
return;
}
this.applyEmbeddedStyles = applyEmbeddedStyles;
this.applyEmbeddedFontSizes = applyEmbeddedStyles;
invalidate();
updateWebView();
}
@Override
public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) {
return;
}
this.applyEmbeddedFontSizes = applyEmbeddedFontSizes;
invalidate();
updateWebView();
}
@Override
public void setStyle(CaptionStyleCompat style) {
if (this.style == style) {
return;
}
this.style = style;
invalidate();
updateWebView();
}
@Override
public void setBottomPaddingFraction(float bottomPaddingFraction) {
if (this.bottomPaddingFraction == bottomPaddingFraction) {
return;
}
this.bottomPaddingFraction = bottomPaddingFraction;
invalidate();
updateWebView();
updateWebView(textCues, style, textSize, textSizeType, bottomPaddingFraction);
}
/**
......@@ -181,11 +115,15 @@ import java.util.List;
* other methods may be called on this view after destroy.
*/
public void destroy() {
cues.clear();
webView.destroy();
}
private void updateWebView() {
private void updateWebView(
List<Cue> cues,
CaptionStyleCompat style,
float defaultTextSize,
@Cue.TextSizeType int defaultTextSizeType,
float bottomPaddingFraction) {
StringBuilder html = new StringBuilder();
html.append(
Util.formatInvariant(
......@@ -250,8 +188,7 @@ import java.util.List;
String writingMode = convertVerticalTypeToCss(cue.verticalType);
String cueTextSizeCssPx = convertTextSizeToCss(cue.textSizeType, cue.textSize);
String windowCssColor =
HtmlUtils.toCssRgba(
cue.windowColorSet && applyEmbeddedStyles ? cue.windowColor : style.windowColor);
HtmlUtils.toCssRgba(cue.windowColorSet ? cue.windowColor : style.windowColor);
String positionProperty;
String lineProperty;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment