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

Commit e23ce3b1 authored by Lajos Molnar's avatar Lajos Molnar Committed by Android (Google) Code Review
Browse files

Merge "Add CaptioningManager listener Subtitle support." into klp-dev

parents c255a711 d486f965
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -12758,6 +12758,9 @@ package android.media {
    field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
    field public static final java.lang.String KEY_HEIGHT = "height";
    field public static final java.lang.String KEY_IS_ADTS = "is-adts";
    field public static final java.lang.String KEY_IS_AUTOSELECT = "is-autoselect";
    field public static final java.lang.String KEY_IS_DEFAULT = "is-default";
    field public static final java.lang.String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle";
    field public static final java.lang.String KEY_I_FRAME_INTERVAL = "i-frame-interval";
    field public static final java.lang.String KEY_LANGUAGE = "language";
    field public static final java.lang.String KEY_MAX_HEIGHT = "max-height";
+23 −13
Original line number Diff line number Diff line
@@ -222,26 +222,36 @@ public final class MediaFormat {
    public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";

    /**
     * A key for boolean AUTOSELECT field. Tracks with AUTOSELECT=true are
     * considered when automatically selecting a track without specific user
     * choice (as defined by HLS).
     * @hide
     * A key for boolean AUTOSELECT behavior for the track. Tracks with AUTOSELECT=true
     * are considered when automatically selecting a track without specific user
     * choice, based on the current locale.
     * This is currently only used for subtitle tracks, when the user selected
     * 'Default' for the captioning locale.
     * The associated value is an integer, where non-0 means TRUE.  This is an optional
     * field; if not specified, AUTOSELECT defaults to TRUE.
     */
    public static final String KEY_AUTOSELECT = "autoselect";
    public static final String KEY_IS_AUTOSELECT = "is-autoselect";

    /**
     * A key for boolean DEFAULT field. The track with DEFAULT=true is selected
     * in the absence of a specific user choice (as defined by HLS).
     * @hide
     * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is
     * selected in the absence of a specific user choice.
     * This is currently only used for subtitle tracks, when the user selected
     * 'Default' for the captioning locale.
     * The associated value is an integer, where non-0 means TRUE.  This is an optional
     * field; if not specified, DEFAULT is considered to be FALSE.
     */
    public static final String KEY_DEFAULT = "default";
    public static final String KEY_IS_DEFAULT = "is-default";


    /**
     * A key for boolean FORCED field for subtitle tracks. True if it is a
     * forced subtitle track.
     * @hide
     * A key for the FORCED field for subtitle tracks. True if it is a
     * forced subtitle track.  Forced subtitle tracks are essential for the
     * content and are shown even when the user turns off Captions.  They
     * are used for example to translate foreign/alien dialogs or signs.
     * The associated value is an integer, where non-0 means TRUE.  This is an
     * optional field; if not specified, FORCED defaults to FALSE.
     */
    public static final String KEY_FORCED = "forced";
    public static final String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle";

    /* package private */ MediaFormat(Map<String, Object> map) {
        mMap = map;
+36 −13
Original line number Diff line number Diff line
@@ -1606,9 +1606,9 @@ public class MediaPlayer implements SubtitleController.Listener
            } else if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
                mFormat = MediaFormat.createSubtitleFormat(
                    MEDIA_MIMETYPE_TEXT_VTT, language);
                mFormat.setInteger(MediaFormat.KEY_AUTOSELECT, in.readInt());
                mFormat.setInteger(MediaFormat.KEY_DEFAULT, in.readInt());
                mFormat.setInteger(MediaFormat.KEY_FORCED, in.readInt());
                mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt());
                mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt());
                mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt());
            } else {
                mFormat = new MediaFormat();
                mFormat.setString(MediaFormat.KEY_LANGUAGE, language);
@@ -1638,9 +1638,9 @@ public class MediaPlayer implements SubtitleController.Listener
            dest.writeString(getLanguage());

            if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
                dest.writeInt(mFormat.getInteger(MediaFormat.KEY_AUTOSELECT));
                dest.writeInt(mFormat.getInteger(MediaFormat.KEY_DEFAULT));
                dest.writeInt(mFormat.getInteger(MediaFormat.KEY_FORCED));
                dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT));
                dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT));
                dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE));
            }
        }

@@ -1765,15 +1765,21 @@ public class MediaPlayer implements SubtitleController.Listener
    @Override
    public void onSubtitleTrackSelected(SubtitleTrack track) {
        if (mSelectedSubtitleTrackIndex >= 0) {
            deselectTrack(mSelectedSubtitleTrackIndex);
            try {
                selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false);
            } catch (IllegalStateException e) {
            }
            mSelectedSubtitleTrackIndex = -1;
        }
        setOnSubtitleDataListener(null);
        for (int i = 0; i < mInbandSubtitleTracks.length; i++) {
            if (mInbandSubtitleTracks[i] == track) {
                Log.v(TAG, "Selecting subtitle track " + i);
                selectTrack(i);
                mSelectedSubtitleTrackIndex = i;
                try {
                    selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true);
                } catch (IllegalStateException e) {
                }
                setOnSubtitleDataListener(mSubtitleDataListener);
                break;
            }
@@ -2046,13 +2052,30 @@ public class MediaPlayer implements SubtitleController.Listener

    private void selectOrDeselectTrack(int index, boolean select)
            throws IllegalStateException {
        // ignore out-of-band tracks
        TrackInfo[] trackInfo = getInbandTrackInfo();
        if (index >= trackInfo.length &&
                index < trackInfo.length + mOutOfBandSubtitleTracks.size()) {
        // handle subtitle track through subtitle controller
        SubtitleTrack track = null;
        if (index < mInbandSubtitleTracks.length) {
            track = mInbandSubtitleTracks[index];
        } else if (index < mInbandSubtitleTracks.length + mOutOfBandSubtitleTracks.size()) {
            track = mOutOfBandSubtitleTracks.get(index - mInbandSubtitleTracks.length);
        }

        if (mSubtitleController != null && track != null) {
            if (select) {
                mSubtitleController.selectTrack(track);
            } else if (mSubtitleController.getSelectedTrack() == track) {
                mSubtitleController.selectTrack(null);
            } else {
                Log.w(TAG, "trying to deselect track that was not selected");
            }
            return;
        }

        selectOrDeselectInbandTrack(index, select);
    }

    private void selectOrDeselectInbandTrack(int index, boolean select)
            throws IllegalStateException {
        Parcel request = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
+144 −35
Original line number Diff line number Diff line
@@ -38,6 +38,21 @@ public class SubtitleController {
    private boolean mShowing;
    private CaptioningManager mCaptioningManager;

    private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener =
        new CaptioningManager.CaptioningChangeListener() {
            /** @hide */
            @Override
            public void onEnabledChanged(boolean enabled) {
                selectDefaultTrack();
            }

            /** @hide */
            @Override
            public void onLocaleChanged(Locale locale) {
                selectDefaultTrack();
            }
        };

    /**
     * Creates a subtitle controller for a media playback object that implements
     * the MediaTimeProvider interface.
@@ -58,16 +73,25 @@ public class SubtitleController {
            (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE);
    }

    @Override
    protected void finalize() throws Throwable {
        mCaptioningManager.removeCaptioningChangeListener(
                mCaptioningChangeListener);
        super.finalize();
    }

    /**
     * @return the available subtitle tracks for this media. These include
     * the tracks found by {@link MediaPlayer} as well as any tracks added
     * manually via {@link #addTrack}.
     */
    public SubtitleTrack[] getTracks() {
        synchronized(mTracks) {
            SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
            mTracks.toArray(tracks);
            return tracks;
        }
    }

    /**
     * @return the currently selected subtitle track
@@ -88,6 +112,8 @@ public class SubtitleController {
     * in-band data from the {@link MediaPlayer}.  However, this does
     * not change the subtitle visibility.
     *
     * Must be called from the UI thread.
     *
     * @param track The subtitle track to select.  This must be one of the
     *              tracks in {@link #getTracks}.
     * @return true if the track was successfully selected.
@@ -107,7 +133,9 @@ public class SubtitleController {
        }

        mSelectedTrack = track;
        if (mAnchor != null) {
            mAnchor.setSubtitleWidget(getRenderingWidget());
        }

        if (mSelectedTrack != null) {
            mSelectedTrack.setTimeProvider(mTimeProvider);
@@ -123,56 +151,123 @@ public class SubtitleController {
    /**
     * @return the default subtitle track based on system preferences, or null,
     * if no such track exists in this manager.
     *
     * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT.
     *
     * 1. If captioning is disabled, only consider FORCED tracks. Otherwise,
     * consider all tracks, but prefer non-FORCED ones.
     * 2. If user selected "Default" caption language:
     *   a. If there is a considered track with DEFAULT=yes, returns that track
     *      (favor the first one in the current language if there are more than
     *      one default tracks, or the first in general if none of them are in
     *      the current language).
     *   b. Otherwise, if there is a track with AUTOSELECT=yes in the current
     *      language, return that one.
     *   c. If there are no default tracks, and no autoselectable tracks in the
     *      current language, return null.
     * 3. If there is a track with the caption language, select that one.  Prefer
     * the one with AUTOSELECT=no.
     *
     * The default values for these flags are DEFAULT=no, AUTOSELECT=yes
     * and FORCED=no.
     *
     * Must be called from the UI thread.
     */
    public SubtitleTrack getDefaultTrack() {
        Locale locale = mCaptioningManager.getLocale();
        SubtitleTrack bestTrack = null;
        int bestScore = -1;

        Locale selectedLocale = mCaptioningManager.getLocale();
        Locale locale = selectedLocale;
        if (locale == null) {
            locale = Locale.getDefault();
        }
        boolean selectForced = !mCaptioningManager.isEnabled();

        synchronized(mTracks) {
            for (SubtitleTrack track: mTracks) {
                MediaFormat format = track.getFormat();
                String language = format.getString(MediaFormat.KEY_LANGUAGE);
            // TODO: select track with best renderer.  For now, we select first
            // track with local's language or first track if locale has none
            if (locale == null ||
                boolean forced =
                    format.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0;
                boolean autoselect =
                    format.getInteger(MediaFormat.KEY_IS_AUTOSELECT, 1) != 0;
                boolean is_default =
                    format.getInteger(MediaFormat.KEY_IS_DEFAULT, 0) != 0;

                boolean languageMatches =
                    (locale == null ||
                    locale.getLanguage().equals("") ||
                    locale.getISO3Language().equals(language) ||
                locale.getLanguage().equals(language)) {
                return track;
                    locale.getLanguage().equals(language));
                // is_default is meaningless unless caption language is 'default'
                int score = (forced ? 0 : 8) +
                    (((selectedLocale == null) && is_default) ? 4 : 0) +
                    (autoselect ? 0 : 2) + (languageMatches ? 1 : 0);

                if (selectForced && !forced) {
                    continue;
                }

                // we treat null locale/language as matching any language
                if ((selectedLocale == null && is_default) ||
                    (languageMatches &&
                     (autoselect || forced || selectedLocale != null))) {
                    if (score > bestScore) {
                        bestScore = score;
                        bestTrack = track;
                    }
        return null;
                }
            }
        }
        return bestTrack;
    }

    private boolean mTrackIsExplicit = false;
    private boolean mVisibilityIsExplicit = false;

    /** @hide */
    /** @hide - called from UI thread */
    public void selectDefaultTrack() {
        if (mTrackIsExplicit) {
            // If track selection is explicit, but visibility
            // is not, it falls back to the captioning setting
            if (!mVisibilityIsExplicit) {
                if (mCaptioningManager.isEnabled() ||
                    (mSelectedTrack != null &&
                     mSelectedTrack.getFormat().getInteger(
                            MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) {
                    show();
                } else {
                    hide();
                }
                mVisibilityIsExplicit = false;
            }
            return;
        }

        // We can have a default (forced) track even if captioning
        // is not enabled.  This is handled by getDefaultTrack().
        // Show this track unless subtitles were explicitly hidden.
        SubtitleTrack track = getDefaultTrack();
        if (track != null) {
            selectTrack(track);
            mTrackIsExplicit = false;
            if (!mVisibilityIsExplicit) {
                if (mCaptioningManager.isEnabled()) {
                show();
                } else {
                    hide();
                }
                mVisibilityIsExplicit = false;
            }
        }
    }

    /** @hide */
    /** @hide - called from UI thread */
    public void reset() {
        hide();
        selectTrack(null);
        mTracks.clear();
        mTrackIsExplicit = false;
        mVisibilityIsExplicit = false;
        mCaptioningManager.removeCaptioningChangeListener(
                mCaptioningChangeListener);
    }

    /**
@@ -183,20 +278,30 @@ public class SubtitleController {
     * @return the created {@link SubtitleTrack} object
     */
    public SubtitleTrack addTrack(MediaFormat format) {
        synchronized(mRenderers) {
            for (Renderer renderer: mRenderers) {
                if (renderer.supports(format)) {
                    SubtitleTrack track = renderer.createTrack(format);
                    if (track != null) {
                        synchronized(mTracks) {
                            if (mTracks.size() == 0) {
                                mCaptioningManager.addCaptioningChangeListener(
                                        mCaptioningChangeListener);
                            }
                            mTracks.add(track);
                        }
                        return track;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Show the selected (or default) subtitle track.
     *
     * Must be called from the UI thread.
     */
    public void show() {
        mShowing = true;
@@ -208,6 +313,8 @@ public class SubtitleController {

    /**
     * Hide the selected (or default) subtitle track.
     *
     * Must be called from the UI thread.
     */
    public void hide() {
        mVisibilityIsExplicit = true;
@@ -257,12 +364,14 @@ public class SubtitleController {
     *                 support for a subtitle format.
     */
    public void registerRenderer(Renderer renderer) {
        synchronized(mRenderers) {
            // TODO how to get available renderers in the system
            if (!mRenderers.contains(renderer)) {
                // TODO should added renderers override existing ones (to allow replacing?)
                mRenderers.add(renderer);
            }
        }
    }

    /**
     * Subtitle anchor, an object that is able to display a subtitle renderer,
@@ -279,7 +388,7 @@ public class SubtitleController {

    private Anchor mAnchor;

    /** @hide */
    /** @hide - called from UI thread */
    public void setAnchor(Anchor anchor) {
        if (mAnchor == anchor) {
            return;
+3 −3
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList
    }

    /** @hide */
    public MediaFormat getFormat() {
    public final MediaFormat getFormat() {
        return mFormat;
    }

@@ -201,7 +201,7 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList
    }

    /** @hide */
    public void scheduleTimedEvents() {
    protected void scheduleTimedEvents() {
        /* get times for the next event */
        if (mTimeProvider != null) {
            mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
@@ -363,7 +363,7 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList
    }

    /** @hide */
    public void setTimeProvider(MediaTimeProvider timeProvider) {
    public synchronized void setTimeProvider(MediaTimeProvider timeProvider) {
        if (mTimeProvider == timeProvider) {
            return;
        }