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

This commit is contained in:
Lajos Molnar
2013-09-18 16:46:38 +00:00
committed by Android (Google) Code Review
5 changed files with 211 additions and 66 deletions

View File

@@ -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";

View File

@@ -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;

View File

@@ -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;
}
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 {

View File

@@ -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,15 +73,24 @@ 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() {
SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
mTracks.toArray(tracks);
return tracks;
synchronized(mTracks) {
SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
mTracks.toArray(tracks);
return tracks;
}
}
/**
@@ -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;
mAnchor.setSubtitleWidget(getRenderingWidget());
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;
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 ||
locale.getLanguage().equals("") ||
locale.getISO3Language().equals(language) ||
locale.getLanguage().equals(language)) {
return track;
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);
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));
// 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) {
return;
}
SubtitleTrack track = getDefaultTrack();
if (track != null) {
selectTrack(track);
mTrackIsExplicit = false;
// If track selection is explicit, but visibility
// is not, it falls back to the captioning setting
if (!mVisibilityIsExplicit) {
if (mCaptioningManager.isEnabled()) {
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) {
show();
mVisibilityIsExplicit = false;
}
}
}
/** @hide */
/** @hide - called from UI thread */
public void reset() {
hide();
selectTrack(null);
mTracks.clear();
mTrackIsExplicit = false;
mVisibilityIsExplicit = false;
mCaptioningManager.removeCaptioningChangeListener(
mCaptioningChangeListener);
}
/**
@@ -183,12 +278,20 @@ public class SubtitleController {
* @return the created {@link SubtitleTrack} object
*/
public SubtitleTrack addTrack(MediaFormat format) {
for (Renderer renderer: mRenderers) {
if (renderer.supports(format)) {
SubtitleTrack track = renderer.createTrack(format);
if (track != null) {
mTracks.add(track);
return track;
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;
}
}
}
}
@@ -197,6 +300,8 @@ public class SubtitleController {
/**
* 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,10 +364,12 @@ public class SubtitleController {
* support for a subtitle format.
*/
public void registerRenderer(Renderer renderer) {
// 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);
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);
}
}
}
@@ -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;

View File

@@ -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;
}