Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d43daf36 authored by Alan Viverette's avatar Alan Viverette Committed by Lajos Molnar
Browse files

Add WebVTT caption renderer

Currently missing support for region anchor points, robust layout
when snapping to lines, and vertical text.

BUG: 10260603
Change-Id: I3463b4aa0039442159144e66922d67f5dfee58ed
parent 171c63db
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -28991,12 +28991,12 @@ package android.view.accessibility {
  }
  public class CaptioningManager {
    method public void addCaptioningStateChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
    method public void addCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
    method public final float getFontScale();
    method public final java.util.Locale getLocale();
    method public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
    method public final boolean isEnabled();
    method public void removeCaptioningStateChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
    method public void removeCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
  }
  public static final class CaptioningManager.CaptionStyle {
@@ -29010,7 +29010,7 @@ package android.view.accessibility {
    field public final int foregroundColor;
  }
  public abstract class CaptioningManager.CaptioningChangeListener {
  public static abstract class CaptioningManager.CaptioningChangeListener {
    ctor public CaptioningManager.CaptioningChangeListener();
    method public void onEnabledChanged(boolean);
    method public void onFontScaleChanged(float);
+4 −4
Original line number Diff line number Diff line
@@ -140,7 +140,7 @@ public class CaptioningManager {
     *
     * @param listener the listener to add
     */
    public void addCaptioningStateChangeListener(CaptioningChangeListener listener) {
    public void addCaptioningChangeListener(CaptioningChangeListener listener) {
        synchronized (mListeners) {
            if (mListeners.isEmpty()) {
                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
@@ -163,11 +163,11 @@ public class CaptioningManager {

    /**
     * Removes a listener previously added using
     * {@link #addCaptioningStateChangeListener}.
     * {@link #addCaptioningChangeListener}.
     *
     * @param listener the listener to remove
     */
    public void removeCaptioningStateChangeListener(CaptioningChangeListener listener) {
    public void removeCaptioningChangeListener(CaptioningChangeListener listener) {
        synchronized (mListeners) {
            mListeners.remove(listener);

@@ -366,7 +366,7 @@ public class CaptioningManager {
     * Listener for changes in captioning properties, including enabled state
     * and user style preferences.
     */
    public abstract class CaptioningChangeListener {
    public static abstract class CaptioningChangeListener {
        /**
         * Called when the captioning enabled state changes.
         *
+68 −94
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.media.Metadata;
import android.media.SubtitleController;
import android.media.SubtitleTrack.RenderingWidget;
import android.media.WebVttRenderer;
import android.net.Uri;
import android.util.AttributeSet;
@@ -46,7 +47,6 @@ import android.widget.MediaController.MediaPlayerControl;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Map;
import java.util.Vector;

@@ -100,14 +100,11 @@ public class VideoView extends SurfaceView
    private boolean     mCanSeekBack;
    private boolean     mCanSeekForward;

    /** List of views overlaid on top of the video. */
    private ArrayList<View> mOverlays;
    /** Subtitle rendering widget overlaid on top of the video. */
    private RenderingWidget mSubtitleWidget;

    /**
     * Listener for overlay layout changes. Invalidates the video view to ensure
     * that captions are redrawn whenever their layout changes.
     */
    private OnLayoutChangeListener mOverlayLayoutListener;
    /** Listener for changes to subtitle data, used to redraw when needed. */
    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;

    public VideoView(Context context) {
        super(context);
@@ -302,11 +299,10 @@ public class VideoView extends SurfaceView
            mMediaPlayer = new MediaPlayer();
            // TODO: create SubtitleController in MediaPlayer, but we need
            // a context for the subtitle renderers
            SubtitleController controller = new SubtitleController(
                    getContext(),
                    mMediaPlayer.getMediaTimeProvider(),
                    mMediaPlayer);
            controller.registerRenderer(new WebVttRenderer(getContext(), null));
            final Context context = getContext();
            final SubtitleController controller = new SubtitleController(
                    context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
            controller.registerRenderer(new WebVttRenderer(context));
            mMediaPlayer.setSubtitleAnchor(controller, this);

            if (mAudioSession != 0) {
@@ -792,117 +788,95 @@ public class VideoView extends SurfaceView
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Layout overlay views, if necessary.
        if (changed && mOverlays != null && !mOverlays.isEmpty()) {
            measureAndLayoutOverlays();
        if (mSubtitleWidget != null) {
            mSubtitleWidget.onAttachedToWindow();
        }
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        final int count = mOverlays.size();
        for (int i = 0; i < count; i++) {
            final View overlay = mOverlays.get(i);
            overlay.draw(canvas);
        if (mSubtitleWidget != null) {
            mSubtitleWidget.onDetachedFromWindow();
        }
    }

    /**
     * Adds a view to be overlaid on top of this video view. During layout, the
     * view will be forced to match the bounds, less padding, of the video view.
     * <p>
     * Overlays are drawn in the order they are added. The last added overlay
     * will be drawn on top.
     *
     * @param overlay the view to overlay
     * @see #removeOverlay(View)
     */
    private void addOverlay(View overlay) {
        if (mOverlays == null) {
            mOverlays = new ArrayList<View>(1);
        }

        if (mOverlayLayoutListener == null) {
            mOverlayLayoutListener = new OnLayoutChangeListener() {
    @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    invalidate();
                }
            };
        }

        if (mOverlays.isEmpty()) {
            setWillNotDraw(false);
        }
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        mOverlays.add(overlay);
        overlay.addOnLayoutChangeListener(mOverlayLayoutListener);
        measureAndLayoutOverlays();
        if (mSubtitleWidget != null) {
            measureAndLayoutSubtitleWidget();
        }

    /**
     * Removes a view previously added using {@link #addOverlay}.
     *
     * @param overlay the view to remove
     * @see #addOverlay(View)
     */
    private void removeOverlay(View overlay) {
        if (mOverlays == null) {
            return;
    }

        overlay.removeOnLayoutChangeListener(mOverlayLayoutListener);
        mOverlays.remove(overlay);
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        if (mOverlays.isEmpty()) {
            setWillNotDraw(true);
        if (mSubtitleWidget != null) {
            final int saveCount = canvas.save();
            canvas.translate(getPaddingLeft(), getPaddingTop());
            mSubtitleWidget.draw(canvas);
            canvas.restoreToCount(saveCount);
        }

        invalidate();
    }

    /**
     * Forces a measurement and layout pass for all overlaid views.
     *
     * @see #addOverlay(View)
     * @see #setSubtitleWidget(RenderingWidget)
     */
    private void measureAndLayoutOverlays() {
        final int left = getPaddingLeft();
        final int top = getPaddingTop();
        final int right = getWidth() - left - getPaddingRight();
        final int bottom = getHeight() - top - getPaddingBottom();
        final int widthSpec = MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY);
        final int heightSpec = MeasureSpec.makeMeasureSpec(bottom - top, MeasureSpec.EXACTLY);
    private void measureAndLayoutSubtitleWidget() {
        final int width = getWidth() - getPaddingLeft() - getPaddingRight();
        final int height = getHeight() - getPaddingTop() - getPaddingBottom();

        final int count = mOverlays.size();
        for (int i = 0; i < count; i++) {
            final View overlay = mOverlays.get(i);
            overlay.measure(widthSpec, heightSpec);
            overlay.layout(left, top, right, bottom);
        }
        mSubtitleWidget.setSize(width, height);
    }

    /** @hide */
    @Override
    public void setSubtitleView(View view) {
        if (mSubtitleView == view) {
    public void setSubtitleWidget(RenderingWidget subtitleWidget) {
        if (mSubtitleWidget == subtitleWidget) {
            return;
        }

        if (mSubtitleView != null) {
            removeOverlay(mSubtitleView);
        final boolean attachedToWindow = isAttachedToWindow();
        if (mSubtitleWidget != null) {
            if (attachedToWindow) {
                mSubtitleWidget.onDetachedFromWindow();
            }

            mSubtitleWidget.setOnChangedListener(null);
        }

        mSubtitleWidget = subtitleWidget;

        if (subtitleWidget != null) {
            if (mSubtitlesChangedListener == null) {
                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
                    @Override
                    public void onChanged(RenderingWidget renderingWidget) {
                        invalidate();
                    }
                };
            }
        mSubtitleView = view;
        if (mSubtitleView != null) {
            addOverlay(mSubtitleView);

            setWillNotDraw(false);
            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);

            if (attachedToWindow) {
                subtitleWidget.onAttachedToWindow();
                requestLayout();
            }
        } else {
            setWillNotDraw(true);
        }

    private View mSubtitleView;
        invalidate();
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -74,6 +74,10 @@ public class SubtitleView extends View {
    private float mSpacingAdd = 0;
    private int mInnerPaddingX = 0;

    public SubtitleView(Context context) {
        this(context, null);
    }

    public SubtitleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
+11 −15
Original line number Diff line number Diff line
@@ -20,8 +20,7 @@ import java.util.Locale;
import java.util.Vector;

import android.content.Context;
import android.media.MediaPlayer.OnSubtitleDataListener;
import android.view.View;
import android.media.SubtitleTrack.RenderingWidget;
import android.view.accessibility.CaptioningManager;

/**
@@ -32,7 +31,6 @@ import android.view.accessibility.CaptioningManager;
 * @hide
 */
public class SubtitleController {
    private Context mContext;
    private MediaTimeProvider mTimeProvider;
    private Vector<Renderer> mRenderers;
    private Vector<SubtitleTrack> mTracks;
@@ -50,7 +48,6 @@ public class SubtitleController {
            Context context,
            MediaTimeProvider timeProvider,
            Listener listener) {
        mContext = context;
        mTimeProvider = timeProvider;
        mListener = listener;

@@ -79,11 +76,11 @@ public class SubtitleController {
        return mSelectedTrack;
    }

    private View getSubtitleView() {
    private RenderingWidget getRenderingWidget() {
        if (mSelectedTrack == null) {
            return null;
        }
        return mSelectedTrack.getView();
        return mSelectedTrack.getRenderingWidget();
    }

    /**
@@ -110,7 +107,7 @@ public class SubtitleController {
        }

        mSelectedTrack = track;
        mAnchor.setSubtitleView(getSubtitleView());
        mAnchor.setSubtitleWidget(getRenderingWidget());

        if (mSelectedTrack != null) {
            mSelectedTrack.setTimeProvider(mTimeProvider);
@@ -268,17 +265,16 @@ public class SubtitleController {
    }

    /**
     * Subtitle anchor, an object that is able to display a subtitle view,
     * Subtitle anchor, an object that is able to display a subtitle renderer,
     * e.g. a VideoView.
     */
    public interface Anchor {
        /**
         * Anchor should set the subtitle view to the supplied view,
         * or none, if the supplied view is null.
         *
         * @param view subtitle view, or null
         * Anchor should use the supplied subtitle rendering widget, or
         * none if it is null.
         * @hide
         */
        public void setSubtitleView(View view);
        public void setSubtitleWidget(RenderingWidget subtitleWidget);
    }

    private Anchor mAnchor;
@@ -290,11 +286,11 @@ public class SubtitleController {
        }

        if (mAnchor != null) {
            mAnchor.setSubtitleView(null);
            mAnchor.setSubtitleWidget(null);
        }
        mAnchor = anchor;
        if (mAnchor != null) {
            mAnchor.setSubtitleView(getSubtitleView());
            mAnchor.setSubtitleWidget(getRenderingWidget());
        }
    }

Loading