diff --git a/api/current.txt b/api/current.txt index 66e01fb874321..fc760cd6f6e13 100644 --- a/api/current.txt +++ b/api/current.txt @@ -25547,8 +25547,8 @@ package android.media { method @NonNull public Object clearNextDataSources(); method public void clearPendingCommands(); method public void close(); - method @NonNull public Object deselectTrack(int); - method @NonNull public Object deselectTrack(@NonNull android.media.DataSourceDesc, int); + method @NonNull public Object deselectTrack(@NonNull android.media.MediaPlayer2.TrackInfo); + method @NonNull public Object deselectTrack(@NonNull android.media.DataSourceDesc, @NonNull android.media.MediaPlayer2.TrackInfo); method @NonNull public android.media.AudioAttributes getAudioAttributes(); method public int getAudioSessionId(); method public long getBufferedPosition(); @@ -25563,8 +25563,8 @@ package android.media { method public float getPlayerVolume(); method @Nullable public android.media.AudioDeviceInfo getPreferredDevice(); method @Nullable public android.media.AudioDeviceInfo getRoutedDevice(); - method public int getSelectedTrack(int); - method public int getSelectedTrack(@NonNull android.media.DataSourceDesc, int); + method @Nullable public android.media.MediaPlayer2.TrackInfo getSelectedTrack(int); + method @Nullable public android.media.MediaPlayer2.TrackInfo getSelectedTrack(@NonNull android.media.DataSourceDesc, int); method public int getState(); method @NonNull public android.media.SyncParams getSyncParams(); method @Nullable public android.media.MediaTimestamp getTimestamp(); @@ -25582,8 +25582,8 @@ package android.media { method public void reset(); method @NonNull public Object seekTo(long); method @NonNull public Object seekTo(long, int); - method @NonNull public Object selectTrack(int); - method @NonNull public Object selectTrack(@NonNull android.media.DataSourceDesc, int); + method @NonNull public Object selectTrack(@NonNull android.media.MediaPlayer2.TrackInfo); + method @NonNull public Object selectTrack(@NonNull android.media.DataSourceDesc, @NonNull android.media.MediaPlayer2.TrackInfo); method @NonNull public Object setAudioAttributes(@NonNull android.media.AudioAttributes); method @NonNull public Object setAudioSessionId(int); method @NonNull public Object setAuxEffectSendLevel(float); @@ -25715,7 +25715,7 @@ package android.media { method public void onError(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, int, int); method public void onInfo(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, int, int); method public void onMediaTimeDiscontinuity(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.MediaTimestamp); - method public void onSubtitleData(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.SubtitleData); + method public void onSubtitleData(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.MediaPlayer2.SubtitleData); method public void onTimedMetaDataAvailable(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.media.TimedMetaData); method public void onVideoSizeChanged(@NonNull android.media.MediaPlayer2, @NonNull android.media.DataSourceDesc, @NonNull android.util.Size); } @@ -25739,6 +25739,13 @@ package android.media { ctor public MediaPlayer2.NoDrmSchemeException(@Nullable String); } + public static final class MediaPlayer2.SubtitleData { + method @NonNull public byte[] getData(); + method public long getDurationUs(); + method public long getStartTimeUs(); + method @NonNull public android.media.MediaPlayer2.TrackInfo getTrackInfo(); + } + public static class MediaPlayer2.TrackInfo { method @Nullable public android.media.MediaFormat getFormat(); method @NonNull public String getLanguage(); diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java index e81a5b9332d98..19bb2586af35e 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -1967,6 +1967,17 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { private native byte[] native_invoke(byte[] request); + /** + * @hide + */ + @IntDef(flag = false, prefix = "MEDIA_TRACK_TYPE", value = { + TrackInfo.MEDIA_TRACK_TYPE_VIDEO, + TrackInfo.MEDIA_TRACK_TYPE_AUDIO, + TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TrackType {} + /** * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. * @@ -2014,10 +2025,11 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; public static final int MEDIA_TRACK_TYPE_METADATA = 5; + final int mId; final int mTrackType; final MediaFormat mFormat; - static TrackInfo create(Iterator in) { + static TrackInfo create(int idx, Iterator in) { int trackType = in.next().getInt32Value(); // TODO: build the full MediaFormat; currently we are using createSubtitleFormat // even for audio/video tracks, meaning we only set the mime and language. @@ -2030,11 +2042,12 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { format.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value()); format.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value()); } - return new TrackInfo(trackType, format); + return new TrackInfo(idx, trackType, format); } /** @hide */ - TrackInfo(int type, MediaFormat format) { + TrackInfo(int id, int type, MediaFormat format) { + mId = id; mTrackType = type; mFormat = format; } @@ -2121,7 +2134,7 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { } TrackInfo[] trackInfo = new TrackInfo[size]; for (int i = 0; i < size; ++i) { - trackInfo[i] = TrackInfo.create(in); + trackInfo[i] = TrackInfo.create(i, in); } return trackInfo; } @@ -2129,54 +2142,56 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { /** * Returns the index of the audio, video, or subtitle track currently selected for playback. * The return value is an index into the array returned by {@link #getTrackInfo}, and can - * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. + * be used in calls to {@link #selectTrack(TrackInfo)} or {@link #deselectTrack(TrackInfo)}. * Same as {@link #getSelectedTrack(DataSourceDesc, int)} with * {@code dsd = getCurrentDataSource()}. * * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} - * @return index of the audio, video, or subtitle track currently selected for playback; - * a negative integer is returned when there is no selected track for {@code trackType} or + * @return metadata corresponding to the audio, video, or subtitle track currently selected for + * playback; {@code null} is returned when there is no selected track for {@code trackType} or * when {@code trackType} is not one of audio, video, or subtitle. * @throws IllegalStateException if called after {@link #close()} * @throws NullPointerException if current data source is null * * @see #getTrackInfo() - * @see #selectTrack(int) - * @see #deselectTrack(int) + * @see #selectTrack(TrackInfo) + * @see #deselectTrack(TrackInfo) */ - public int getSelectedTrack(int trackType) { + @Nullable + public TrackInfo getSelectedTrack(@TrackType int trackType) { return getSelectedTrack(getCurrentDataSource(), trackType); } /** * Returns the index of the audio, video, or subtitle track currently selected for playback. * The return value is an index into the array returned by {@link #getTrackInfo}, and can - * be used in calls to {@link #selectTrack(DataSourceDesc, int)} or - * {@link #deselectTrack(DataSourceDesc, int)}. + * be used in calls to {@link #selectTrack(DataSourceDesc, TrackInfo)} or + * {@link #deselectTrack(DataSourceDesc, TrackInfo)}. * * @param dsd the descriptor of data source of which you want to get selected track * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} - * @return index of the audio, video, or subtitle track currently selected for playback; - * a negative integer is returned when there is no selected track for {@code trackType} or + * @return metadata corresponding to the audio, video, or subtitle track currently selected for + * playback; {@code null} is returned when there is no selected track for {@code trackType} or * when {@code trackType} is not one of audio, video, or subtitle. * @throws IllegalStateException if called after {@link #close()} * @throws NullPointerException if dsd is null * * @see #getTrackInfo(DataSourceDesc) - * @see #selectTrack(DataSourceDesc, int) - * @see #deselectTrack(DataSourceDesc, int) + * @see #selectTrack(DataSourceDesc, TrackInfo) + * @see #deselectTrack(DataSourceDesc, TrackInfo) */ - public int getSelectedTrack(@NonNull DataSourceDesc dsd, int trackType) { + @Nullable + public TrackInfo getSelectedTrack(@NonNull DataSourceDesc dsd, @TrackType int trackType) { if (dsd == null) { throw new NullPointerException("non-null dsd is expected"); } SourceInfo sourceInfo = getSourceInfo(dsd); if (sourceInfo == null) { - return -1; + return null; } PlayerMessage request = PlayerMessage.newBuilder() @@ -2186,26 +2201,30 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { .build(); PlayerMessage response = invoke(request); if (response == null) { - return -1; + return null; } - return response.getValues(0).getInt32Value(); + // TODO: return full TrackInfo data from native player instead of index + final int idx = response.getValues(0).getInt32Value(); + final List trackInfos = getTrackInfo(dsd); + return trackInfos.isEmpty() ? null : trackInfos.get(idx); } /** * Selects a track of current data source. - * Same as {@link #selectTrack(DataSourceDesc, int)} with + * Same as {@link #selectTrack(DataSourceDesc, TrackInfo)} with * {@code dsd = getCurrentDataSource()}. * - * @param index the index of the track to be selected. The valid range of the index - * is 0..total number of track - 1. The total number of tracks as well as the type of - * each individual track can be found by calling {@link #getTrackInfo()} method. + * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} + * object can be obtained from {@link #getTrackInfo()}. * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. * + * This is an asynchronous call. + * * @see MediaPlayer2#getTrackInfo() */ - // This is an asynchronous call. - public @NonNull Object selectTrack(int index) { - return selectTrack(getCurrentDataSource(), index); + @NonNull + public Object selectTrack(@NonNull TrackInfo trackInfo) { + return selectTrack(getCurrentDataSource(), trackInfo); } /** @@ -2230,38 +2249,40 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { * in that an audio track can only be selected in the Prepared state. *

* @param dsd the descriptor of data source of which you want to select track - * @param index the index of the track to be selected. The valid range of the index - * is 0..total number of track - 1. The total number of tracks as well as the type of - * each individual track can be found by calling {@link #getTrackInfo(DataSourceDesc)} method. + * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} + * object can be obtained from {@link #getTrackInfo()}. * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. * + * This is an asynchronous call. + * * @see MediaPlayer2#getTrackInfo(DataSourceDesc) */ - // This is an asynchronous call. - public @NonNull Object selectTrack(@NonNull DataSourceDesc dsd, int index) { + @NonNull + public Object selectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) { return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { @Override void process() { - selectOrDeselectTrack(dsd, index, true /* select */); + selectOrDeselectTrack(dsd, trackInfo.mId, true /* select */); } }); } /** * Deselect a track of current data source. - * Same as {@link #deselectTrack(DataSourceDesc, int)} with + * Same as {@link #deselectTrack(DataSourceDesc, TrackInfo)} with * {@code dsd = getCurrentDataSource()}. * - * @param index the index of the track to be deselected. The valid range of the index - * is 0..total number of tracks - 1. The total number of tracks as well as the type of - * each individual track can be found by calling {@link #getTrackInfo()} method. + * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} + * object can be obtained from {@link #getTrackInfo()}. * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. * + * This is an asynchronous call. + * * @see MediaPlayer2#getTrackInfo() */ - // This is an asynchronous call. - public @NonNull Object deselectTrack(int index) { - return deselectTrack(getCurrentDataSource(), index); + @NonNull + public Object deselectTrack(@NonNull TrackInfo trackInfo) { + return deselectTrack(getCurrentDataSource(), trackInfo); } /** @@ -2272,19 +2293,20 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { * selected before, it throws an exception. *

* @param dsd the descriptor of data source of which you want to deselect track - * @param index the index of the track to be deselected. The valid range of the index - * is 0..total number of tracks - 1. The total number of tracks as well as the type of - * each individual track can be found by calling {@link #getTrackInfo} method. + * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo} + * object can be obtained from {@link #getTrackInfo()}. * @return a token which can be used to cancel the operation later with {@link #cancelCommand}. * + * This is an asynchronous call. + * * @see MediaPlayer2#getTrackInfo(DataSourceDesc) */ - // This is an asynchronous call. - public @NonNull Object deselectTrack(@NonNull DataSourceDesc dsd, int index) { + @NonNull + public Object deselectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) { return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { @Override void process() { - selectOrDeselectTrack(dsd, index, false /* select */); + selectOrDeselectTrack(dsd, trackInfo.mId, false /* select */); } }); } @@ -2645,11 +2667,13 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { return; } Iterator in = playerMsg.getValuesList().iterator(); - SubtitleData data = new SubtitleData( - in.next().getInt32Value(), // trackIndex - in.next().getInt64Value(), // startTimeUs - in.next().getInt64Value(), // durationUs - in.next().getBytesValue().toByteArray()); // data + final int trackIndex = in.next().getInt32Value(); + TrackInfo trackInfo = getTrackInfo(dsd).get(trackIndex); + final long startTimeUs = in.next().getInt64Value(); + final long durationTimeUs = in.next().getInt64Value(); + final byte[] subData = in.next().getBytesValue().toByteArray(); + SubtitleData data = new SubtitleData(trackInfo, + startTimeUs, durationTimeUs, subData); sendEvent(new EventNotifier() { @Override public void notify(EventCallback callback) { @@ -2769,6 +2793,74 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { } } + /** + * Class encapsulating subtitle data, as received through the + * {@link EventCallback#onSubtitleData} interface. + *

+ * A {@link SubtitleData} object includes: + *

    + *
  • track metadadta in a {@link TrackInfo} object
  • + *
  • the start time (in microseconds) of the data
  • + *
  • the duration (in microseconds) of the data
  • + *
  • the actual data.
  • + *
+ * The data is stored in a byte-array, and is encoded in one of the supported in-band + * subtitle formats. The subtitle encoding is determined by the MIME type of the + * {@link TrackInfo} of the subtitle track, one of + * {@link MediaFormat#MIMETYPE_TEXT_CEA_608}, {@link MediaFormat#MIMETYPE_TEXT_CEA_708}, + * {@link MediaFormat#MIMETYPE_TEXT_VTT}. + */ + public static final class SubtitleData { + + private TrackInfo mTrackInfo; + private long mStartTimeUs; + private long mDurationUs; + private byte[] mData; + + private SubtitleData(TrackInfo trackInfo, long startTimeUs, long durationUs, byte[] data) { + mTrackInfo = trackInfo; + mStartTimeUs = startTimeUs; + mDurationUs = durationUs; + mData = (data != null ? data : new byte[0]); + } + + /** + * @return metadata of track which contains this subtitle data + */ + @NonNull + public TrackInfo getTrackInfo() { + return mTrackInfo; + } + + /** + * @return media time at which the subtitle should start to be displayed in microseconds + */ + public long getStartTimeUs() { + return mStartTimeUs; + } + + /** + * @return the duration in microsecond during which the subtitle should be displayed + */ + public long getDurationUs() { + return mDurationUs; + } + + /** + * Returns the encoded data for the subtitle content. + * Encoding format depends on the subtitle type, refer to + * CEA 708, + * CEA/EIA 608 and + * WebVTT, defined by the MIME type + * of the subtitle track. + * @return the encoded subtitle data + */ + @NonNull + public byte[] getData() { + return mData; + } + } + /** * Interface definition for callbacks to be invoked when the player has the corresponding * events.