1/* 2 * Copyright 2018 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 17package androidx.media.subtitle; 18 19import android.content.Context; 20import android.util.AttributeSet; 21import android.view.View; 22import android.view.ViewGroup; 23import android.view.accessibility.CaptioningManager; 24import android.view.accessibility.CaptioningManager.CaptionStyle; 25import android.view.accessibility.CaptioningManager.CaptioningChangeListener; 26 27import androidx.annotation.RequiresApi; 28 29/** 30 * Abstract widget class to render a closed caption track. 31 */ 32@RequiresApi(28) 33abstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget { 34 35 interface ClosedCaptionLayout { 36 void setCaptionStyle(CaptionStyle captionStyle); 37 void setFontScale(float scale); 38 } 39 40 /** Captioning manager, used to obtain and track caption properties. */ 41 private final CaptioningManager mManager; 42 43 /** Current caption style. */ 44 protected CaptionStyle mCaptionStyle; 45 46 /** Callback for rendering changes. */ 47 protected OnChangedListener mListener; 48 49 /** Concrete layout of CC. */ 50 protected ClosedCaptionLayout mClosedCaptionLayout; 51 52 /** Whether a caption style change listener is registered. */ 53 private boolean mHasChangeListener; 54 55 ClosedCaptionWidget(Context context) { 56 this(context, null); 57 } 58 59 ClosedCaptionWidget(Context context, AttributeSet attrs) { 60 this(context, attrs, 0); 61 } 62 63 ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) { 64 this(context, attrs, defStyle, 0); 65 } 66 67 ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyleAttr, 68 int defStyleRes) { 69 super(context, attrs, defStyleAttr, defStyleRes); 70 71 // Cannot render text over video when layer type is hardware. 72 setLayerType(View.LAYER_TYPE_SOFTWARE, null); 73 74 mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); 75 mCaptionStyle = mManager.getUserStyle(); 76 77 mClosedCaptionLayout = createCaptionLayout(context); 78 mClosedCaptionLayout.setCaptionStyle(mCaptionStyle); 79 mClosedCaptionLayout.setFontScale(mManager.getFontScale()); 80 addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT, 81 LayoutParams.MATCH_PARENT); 82 83 requestLayout(); 84 } 85 86 public abstract ClosedCaptionLayout createCaptionLayout(Context context); 87 88 @Override 89 public void setOnChangedListener(OnChangedListener listener) { 90 mListener = listener; 91 } 92 93 @Override 94 public void setSize(int width, int height) { 95 final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 96 final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 97 98 measure(widthSpec, heightSpec); 99 layout(0, 0, width, height); 100 } 101 102 @Override 103 public void setVisible(boolean visible) { 104 if (visible) { 105 setVisibility(View.VISIBLE); 106 } else { 107 setVisibility(View.GONE); 108 } 109 110 manageChangeListener(); 111 } 112 113 @Override 114 public void onAttachedToWindow() { 115 super.onAttachedToWindow(); 116 117 manageChangeListener(); 118 } 119 120 @Override 121 public void onDetachedFromWindow() { 122 super.onDetachedFromWindow(); 123 124 manageChangeListener(); 125 } 126 127 @Override 128 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 129 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 130 ((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec); 131 } 132 133 @Override 134 protected void onLayout(boolean changed, int l, int t, int r, int b) { 135 ((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b); 136 } 137 138 /** 139 * Manages whether this renderer is listening for caption style changes. 140 */ 141 private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() { 142 @Override 143 public void onUserStyleChanged(CaptionStyle userStyle) { 144 mCaptionStyle = userStyle; 145 mClosedCaptionLayout.setCaptionStyle(mCaptionStyle); 146 } 147 148 @Override 149 public void onFontScaleChanged(float fontScale) { 150 mClosedCaptionLayout.setFontScale(fontScale); 151 } 152 }; 153 154 private void manageChangeListener() { 155 final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE; 156 if (mHasChangeListener != needsListener) { 157 mHasChangeListener = needsListener; 158 159 if (needsListener) { 160 mManager.addCaptioningChangeListener(mCaptioningListener); 161 } else { 162 mManager.removeCaptioningChangeListener(mCaptioningListener); 163 } 164 } 165 } 166} 167 168