1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 package com.google.android.exoplayer2.ui;
18 
19 import static java.lang.annotation.RetentionPolicy.SOURCE;
20 
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.util.AttributeSet;
24 import android.util.TypedValue;
25 import android.view.View;
26 import android.view.accessibility.CaptioningManager;
27 import android.widget.FrameLayout;
28 import androidx.annotation.Dimension;
29 import androidx.annotation.IntDef;
30 import androidx.annotation.Nullable;
31 import androidx.annotation.RequiresApi;
32 import com.google.android.exoplayer2.text.CaptionStyleCompat;
33 import com.google.android.exoplayer2.text.Cue;
34 import com.google.android.exoplayer2.text.TextOutput;
35 import com.google.android.exoplayer2.util.Util;
36 import java.lang.annotation.Documented;
37 import java.lang.annotation.Retention;
38 import java.util.Collections;
39 import java.util.List;
40 
41 /** A view for displaying subtitle {@link Cue}s. */
42 public final class SubtitleView extends FrameLayout implements TextOutput {
43 
44   /**
45    * The default fractional text size.
46    *
47    * @see SubtitleView#setFractionalTextSize(float, boolean)
48    */
49   public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f;
50 
51   /**
52    * The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a
53    * fraction of the viewport height.
54    *
55    * @see #setBottomPaddingFraction(float)
56    */
57   public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f;
58 
59   /**
60    * Indicates a {@link SubtitleTextView} should be used to display subtitles. This is the default.
61    */
62   public static final int VIEW_TYPE_TEXT = 1;
63 
64   /**
65    * Indicates a {@link SubtitleWebView} should be used to display subtitles.
66    *
67    * <p>This will instantiate a {@link android.webkit.WebView} and use CSS and HTML styling to
68    * render the subtitles. This supports some additional styling features beyond those supported by
69    * {@link SubtitleTextView} such as vertical text.
70    */
71   public static final int VIEW_TYPE_WEB = 2;
72 
73   /**
74    * The type of {@link View} to use to display subtitles.
75    *
76    * <p>One of:
77    *
78    * <ul>
79    *   <li>{@link #VIEW_TYPE_TEXT}
80    *   <li>{@link #VIEW_TYPE_WEB}
81    * </ul>
82    */
83   @Documented
84   @Retention(SOURCE)
85   @IntDef({VIEW_TYPE_TEXT, VIEW_TYPE_WEB})
86   public @interface ViewType {}
87 
88   private @ViewType int viewType;
89   private Output output;
90   private View innerSubtitleView;
91 
SubtitleView(Context context)92   public SubtitleView(Context context) {
93     this(context, null);
94   }
95 
SubtitleView(Context context, @Nullable AttributeSet attrs)96   public SubtitleView(Context context, @Nullable AttributeSet attrs) {
97     super(context, attrs);
98     SubtitleTextView subtitleTextView = new SubtitleTextView(context, attrs);
99     output = subtitleTextView;
100     innerSubtitleView = subtitleTextView;
101     addView(innerSubtitleView);
102     viewType = VIEW_TYPE_TEXT;
103   }
104 
105   @Override
onCues(List<Cue> cues)106   public void onCues(List<Cue> cues) {
107     setCues(cues);
108   }
109 
110   /**
111    * Sets the cues to be displayed by the view.
112    *
113    * @param cues The cues to display, or null to clear the cues.
114    */
setCues(@ullable List<Cue> cues)115   public void setCues(@Nullable List<Cue> cues) {
116     output.onCues(cues != null ? cues : Collections.emptyList());
117   }
118 
119   /**
120    * Set the type of {@link View} used to display subtitles.
121    *
122    * <p>NOTE: {@link #VIEW_TYPE_WEB} is currently very experimental, and doesn't support most
123    * styling and layout properties of {@link Cue}.
124    *
125    * @param viewType The {@link ViewType} to use.
126    */
setViewType(@iewType int viewType)127   public void setViewType(@ViewType int viewType) {
128     if (this.viewType == viewType) {
129       return;
130     }
131     switch (viewType) {
132       case VIEW_TYPE_TEXT:
133         setView(new SubtitleTextView(getContext()));
134         break;
135       case VIEW_TYPE_WEB:
136         setView(new SubtitleWebView(getContext()));
137         break;
138       default:
139         throw new IllegalArgumentException();
140     }
141     this.viewType = viewType;
142   }
143 
setView(T view)144   private <T extends View & Output> void setView(T view) {
145     removeView(innerSubtitleView);
146     innerSubtitleView = view;
147     output = view;
148     addView(view);
149   }
150 
151   /**
152    * Set the text size to a given unit and value.
153    *
154    * <p>See {@link TypedValue} for the possible dimension units.
155    *
156    * @param unit The desired dimension unit.
157    * @param size The desired size in the given units.
158    */
setFixedTextSize(@imension int unit, float size)159   public void setFixedTextSize(@Dimension int unit, float size) {
160     Context context = getContext();
161     Resources resources;
162     if (context == null) {
163       resources = Resources.getSystem();
164     } else {
165       resources = context.getResources();
166     }
167     setTextSize(
168         Cue.TEXT_SIZE_TYPE_ABSOLUTE,
169         TypedValue.applyDimension(unit, size, resources.getDisplayMetrics()));
170   }
171 
172   /**
173    * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a
174    * default size before API level 19.
175    */
setUserDefaultTextSize()176   public void setUserDefaultTextSize() {
177     float fontScale = Util.SDK_INT >= 19 && !isInEditMode() ? getUserCaptionFontScaleV19() : 1f;
178     setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale);
179   }
180 
181   /**
182    * Sets the text size to be a fraction of the view's remaining height after its top and bottom
183    * padding have been subtracted.
184    * <p>
185    * Equivalent to {@code #setFractionalTextSize(fractionOfHeight, false)}.
186    *
187    * @param fractionOfHeight A fraction between 0 and 1.
188    */
setFractionalTextSize(float fractionOfHeight)189   public void setFractionalTextSize(float fractionOfHeight) {
190     setFractionalTextSize(fractionOfHeight, false);
191   }
192 
193   /**
194    * Sets the text size to be a fraction of the height of this view.
195    *
196    * @param fractionOfHeight A fraction between 0 and 1.
197    * @param ignorePadding Set to true if {@code fractionOfHeight} should be interpreted as a
198    *     fraction of this view's height ignoring any top and bottom padding. Set to false if
199    *     {@code fractionOfHeight} should be interpreted as a fraction of this view's remaining
200    *     height after the top and bottom padding has been subtracted.
201    */
setFractionalTextSize(float fractionOfHeight, boolean ignorePadding)202   public void setFractionalTextSize(float fractionOfHeight, boolean ignorePadding) {
203     setTextSize(
204         ignorePadding
205             ? Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING
206             : Cue.TEXT_SIZE_TYPE_FRACTIONAL,
207         fractionOfHeight);
208   }
209 
setTextSize(@ue.TextSizeType int textSizeType, float textSize)210   private void setTextSize(@Cue.TextSizeType int textSizeType, float textSize) {
211     output.setTextSize(textSizeType, textSize);
212   }
213 
214   /**
215    * Sets whether styling embedded within the cues should be applied. Enabled by default.
216    * Overrides any setting made with {@link SubtitleView#setApplyEmbeddedFontSizes}.
217    *
218    * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied.
219    */
setApplyEmbeddedStyles(boolean applyEmbeddedStyles)220   public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) {
221     output.setApplyEmbeddedStyles(applyEmbeddedStyles);
222   }
223 
224   /**
225    * Sets whether font sizes embedded within the cues should be applied. Enabled by default.
226    * Only takes effect if {@link SubtitleView#setApplyEmbeddedStyles} is set to true.
227    *
228    * @param applyEmbeddedFontSizes Whether font sizes embedded within the cues should be applied.
229    */
setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes)230   public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) {
231     output.setApplyEmbeddedFontSizes(applyEmbeddedFontSizes);
232   }
233 
234   /**
235    * Sets the caption style to be equivalent to the one returned by
236    * {@link CaptioningManager#getUserStyle()}, or to a default style before API level 19.
237    */
setUserDefaultStyle()238   public void setUserDefaultStyle() {
239     setStyle(
240         Util.SDK_INT >= 19 && isCaptionManagerEnabled() && !isInEditMode()
241             ? getUserCaptionStyleV19()
242             : CaptionStyleCompat.DEFAULT);
243   }
244 
245   /**
246    * Sets the caption style.
247    *
248    * @param style A style for the view.
249    */
setStyle(CaptionStyleCompat style)250   public void setStyle(CaptionStyleCompat style) {
251     output.setStyle(style);
252   }
253 
254   /**
255    * Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET},
256    * as a fraction of the view's remaining height after its top and bottom padding have been
257    * subtracted.
258    * <p>
259    * Note that this padding is applied in addition to any standard view padding.
260    *
261    * @param bottomPaddingFraction The bottom padding fraction.
262    */
setBottomPaddingFraction(float bottomPaddingFraction)263   public void setBottomPaddingFraction(float bottomPaddingFraction) {
264     output.setBottomPaddingFraction(bottomPaddingFraction);
265   }
266 
267   @RequiresApi(19)
isCaptionManagerEnabled()268   private boolean isCaptionManagerEnabled() {
269     CaptioningManager captioningManager =
270         (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE);
271     return captioningManager.isEnabled();
272   }
273 
274   @RequiresApi(19)
getUserCaptionFontScaleV19()275   private float getUserCaptionFontScaleV19() {
276     CaptioningManager captioningManager =
277         (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE);
278     return captioningManager.getFontScale();
279   }
280 
281   @RequiresApi(19)
getUserCaptionStyleV19()282   private CaptionStyleCompat getUserCaptionStyleV19() {
283     CaptioningManager captioningManager =
284         (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE);
285     return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
286   }
287 
288   /* package */ interface Output {
onCues(List<Cue> cues)289     void onCues(List<Cue> cues);
setTextSize(@ue.TextSizeType int textSizeType, float textSize)290     void setTextSize(@Cue.TextSizeType int textSizeType, float textSize);
setApplyEmbeddedStyles(boolean applyEmbeddedStyles)291     void setApplyEmbeddedStyles(boolean applyEmbeddedStyles);
setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes)292     void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes);
setStyle(CaptionStyleCompat style)293     void setStyle(CaptionStyleCompat style);
setBottomPaddingFraction(float bottomPaddingFraction)294     void setBottomPaddingFraction(float bottomPaddingFraction);
295   }
296 }
297