Merge "Defines MediaPlayer APIs to support multiple audio/video/timedtext tracks."
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 662dd13..a68ab4e 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -120,6 +120,9 @@
MEDIA_INFO_NOT_SEEKABLE = 801,
// New media metadata is available.
MEDIA_INFO_METADATA_UPDATE = 802,
+
+ //9xx
+ MEDIA_INFO_TIMED_TEXT_ERROR = 900,
};
@@ -140,9 +143,6 @@
// The same enum space is used for both set and get, in case there are future keys that
// can be both set and get. But as of now, all parameters are either set only or get only.
enum media_parameter_keys {
- KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000, // set only
- KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001, // set only
-
// Streaming/buffering parameters
KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS = 1100, // set only
@@ -155,6 +155,23 @@
KEY_PARAMETER_PLAYBACK_RATE_PERMILLE = 1300, // set only
};
+// Keep INVOKE_ID_* in sync with MediaPlayer.java.
+enum media_player_invoke_ids {
+ INVOKE_ID_GET_TRACK_INFO = 1,
+ INVOKE_ID_ADD_EXTERNAL_SOURCE = 2,
+ INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3,
+ INVOKE_ID_SELECT_TRACK = 4,
+ INVOKE_ID_UNSELECT_TRACK = 5,
+};
+
+// Keep MEDIA_TRACK_TYPE_* in sync with MediaPlayer.java.
+enum media_track_type {
+ MEDIA_TRACK_TYPE_UNKNOWN = 0,
+ MEDIA_TRACK_TYPE_VIDEO = 1,
+ MEDIA_TRACK_TYPE_AUDIO = 2,
+ MEDIA_TRACK_TYPE_TIMEDTEXT = 3,
+};
+
// ----------------------------------------------------------------------------
// ref-counted object for callbacks
class MediaPlayerListener: virtual public RefBase
diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h
index 2eb259e..457d5d7 100644
--- a/include/media/stagefright/MediaDefs.h
+++ b/include/media/stagefright/MediaDefs.h
@@ -54,6 +54,7 @@
extern const char *MEDIA_MIMETYPE_CONTAINER_WVM;
extern const char *MEDIA_MIMETYPE_TEXT_3GPP;
+extern const char *MEDIA_MIMETYPE_TEXT_SUBRIP;
} // namespace android
diff --git a/include/media/stagefright/timedtext/TimedTextDriver.h b/include/media/stagefright/timedtext/TimedTextDriver.h
index efedb6e..b9752df 100644
--- a/include/media/stagefright/timedtext/TimedTextDriver.h
+++ b/include/media/stagefright/timedtext/TimedTextDriver.h
@@ -37,26 +37,26 @@
~TimedTextDriver();
- // TODO: pause-resume pair seems equivalent to stop-start pair.
- // Check if it is replaceable with stop-start.
status_t start();
- status_t stop();
status_t pause();
- status_t resume();
+ status_t selectTrack(int32_t index);
+ status_t unselectTrack(int32_t index);
status_t seekToAsync(int64_t timeUs);
status_t addInBandTextSource(const sp<MediaSource>& source);
- status_t addOutOfBandTextSource(const Parcel &request);
+ status_t addOutOfBandTextSource(const char *uri, const char *mimeType);
+ // Caller owns the file desriptor and caller is responsible for closing it.
+ status_t addOutOfBandTextSource(
+ int fd, off64_t offset, size_t length, const char *mimeType);
- status_t setTimedTextTrackIndex(int32_t index);
+ void getTrackInfo(Parcel *parcel);
private:
Mutex mLock;
enum State {
UNINITIALIZED,
- STOPPED,
PLAYING,
PAUSED,
};
@@ -67,11 +67,11 @@
// Variables to be guarded by mLock.
State mState;
- Vector<sp<TimedTextSource> > mTextInBandVector;
- Vector<sp<TimedTextSource> > mTextOutOfBandVector;
+ int32_t mCurrentTrackIndex;
+ Vector<sp<TimedTextSource> > mTextSourceVector;
// -- End of variables to be guarded by mLock
- status_t setTimedTextTrackIndex_l(int32_t index);
+ status_t selectTrack_l(int32_t index);
DISALLOW_EVIL_CONSTRUCTORS(TimedTextDriver);
};
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index f5fa877..d92180d 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -24,6 +24,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.util.Log;
@@ -455,6 +456,22 @@
* <td>Successful invoke of this method in a valid state transfers the
* object to the <em>Stopped</em> state. Calling this method in an
* invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>getTrackInfo </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>addExternalSource </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>selectTrack </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>disableTrack </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
*
* </table>
*
@@ -572,6 +589,15 @@
*/
private native void _setVideoSurface(Surface surface);
+ /* Do not change these values (starting with INVOKE_ID) without updating
+ * their counterparts in include/media/mediaplayer.h!
+ */
+ private static final int INVOKE_ID_GET_TRACK_INFO = 1;
+ private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
+ private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
+ private static final int INVOKE_ID_SELECT_TRACK = 4;
+ private static final int INVOKE_ID_UNSELECT_TRACK = 5;
+
/**
* Create a request parcel which can be routed to the native media
* player using {@link #invoke(Parcel, Parcel)}. The Parcel
@@ -1312,23 +1338,6 @@
/* Do not change these values (starting with KEY_PARAMETER) without updating
* their counterparts in include/media/mediaplayer.h!
*/
- /*
- * Key used in setParameter method.
- * Indicates the index of the timed text track to be enabled/disabled.
- * The index includes both the in-band and out-of-band timed text.
- * The index should start from in-band text if any. Application can retrieve the number
- * of in-band text tracks by using MediaMetadataRetriever::extractMetadata().
- * Note it might take a few hundred ms to scan an out-of-band text file
- * before displaying it.
- */
- private static final int KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000;
- /*
- * Key used in setParameter method.
- * Used to add out-of-band timed text source path.
- * Application can add multiple text sources by calling setParameter() with
- * KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE multiple times.
- */
- private static final int KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001;
// There are currently no defined keys usable from Java with get*Parameter.
// But if any keys are defined, the order must be kept in sync with include/media/mediaplayer.h.
@@ -1373,7 +1382,7 @@
return ret;
}
- /**
+ /*
* Gets the value of the parameter indicated by key.
* @param key key indicates the parameter to get.
* @param reply value of the parameter to get.
@@ -1435,7 +1444,7 @@
*/
public native void setAuxEffectSendLevel(float level);
- /**
+ /*
* @param request Parcel destinated to the media player. The
* Interface token must be set to the IMediaPlayer
* one to be routed correctly through the system.
@@ -1445,7 +1454,7 @@
private native final int native_invoke(Parcel request, Parcel reply);
- /**
+ /*
* @param update_only If true fetch only the set of metadata that have
* changed since the last invocation of getMetadata.
* The set is built using the unfiltered
@@ -1462,7 +1471,7 @@
boolean apply_filter,
Parcel reply);
- /**
+ /*
* @param request Parcel with the 2 serialized lists of allowed
* metadata types followed by the one to be
* dropped. Each list starts with an integer
@@ -1476,33 +1485,289 @@
private native final void native_finalize();
/**
- * @param index The index of the text track to be turned on.
- * @return true if the text track is enabled successfully.
+ * Class for MediaPlayer to return each audio/video/subtitle track's metadata.
+ *
+ * {@see #getTrackInfo()}.
* {@hide}
*/
- public boolean enableTimedTextTrackIndex(int index) {
- if (index < 0) {
- return false;
+ static public class TrackInfo implements Parcelable {
+ /**
+ * Gets the track type.
+ * @return TrackType which indicates if the track is video, audio, timed text.
+ */
+ public int getTrackType() {
+ return mTrackType;
}
- return setParameter(KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX, index);
+
+ /**
+ * Gets the language code of the track.
+ * @return a language code in either way of ISO-639-1 or ISO-639-2.
+ * When the language is unknown or could not be determined,
+ * ISO-639-2 language code, "und", is returned.
+ */
+ public String getLanguage() {
+ return mLanguage;
+ }
+
+ public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
+ public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
+ public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
+ public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
+
+ final int mTrackType;
+ final String mLanguage;
+
+ TrackInfo(Parcel in) {
+ mTrackType = in.readInt();
+ mLanguage = in.readString();
+ }
+
+ /*
+ * No special parcel contents. Keep it as hide.
+ * {@hide}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /*
+ * {@hide}
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTrackType);
+ dest.writeString(mLanguage);
+ }
+
+ /**
+ * Used to read a TrackInfo from a Parcel.
+ */
+ static final Parcelable.Creator<TrackInfo> CREATOR
+ = new Parcelable.Creator<TrackInfo>() {
+ @Override
+ public TrackInfo createFromParcel(Parcel in) {
+ return new TrackInfo(in);
+ }
+
+ @Override
+ public TrackInfo[] newArray(int size) {
+ return new TrackInfo[size];
+ }
+ };
+
+ };
+
+ /**
+ * Returns an array of track information.
+ *
+ * @return Array of track info. null if an error occured.
+ * {@hide}
+ */
+ // FIXME: It returns timed text tracks' information for now. Other types of tracks will be
+ // supported in future.
+ public TrackInfo[] getTrackInfo() {
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ request.writeInterfaceToken(IMEDIA_PLAYER);
+ request.writeInt(INVOKE_ID_GET_TRACK_INFO);
+ invoke(request, reply);
+ TrackInfo trackInfo[] = reply.createTypedArray(TrackInfo.CREATOR);
+ return trackInfo;
+ }
+
+ /*
+ * A helper function to check if the mime type is supported by media framework.
+ */
+ private boolean availableMimeTypeForExternalSource(String mimeType) {
+ if (mimeType == MEDIA_MIMETYPE_TEXT_SUBRIP) {
+ return true;
+ }
+ return false;
+ }
+
+ /* TODO: Limit the total number of external timed text source to a reasonable number.
+ */
+ /**
+ * Adds an external source file.
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param path The file path of external source file.
+ * {@hide}
+ */
+ // FIXME: define error codes and throws exceptions according to the error codes.
+ // (IllegalStateException, IOException).
+ public void addExternalSource(String path, String mimeType)
+ throws IllegalArgumentException {
+ if (!availableMimeTypeForExternalSource(mimeType)) {
+ throw new IllegalArgumentException("Illegal mimeType for external source: " + mimeType);
+ }
+
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ request.writeInterfaceToken(IMEDIA_PLAYER);
+ request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE);
+ request.writeString(path);
+ request.writeString(mimeType);
+ invoke(request, reply);
}
/**
- * Enables the first timed text track if any.
- * @return true if the text track is enabled successfully
+ * Adds an external source file (Uri).
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
* {@hide}
*/
- public boolean enableTimedText() {
- return enableTimedTextTrackIndex(0);
+ // FIXME: define error codes and throws exceptions according to the error codes.
+ // (IllegalStateException, IOException).
+ public void addExternalSource(Context context, Uri uri, String mimeType)
+ throws IOException, IllegalArgumentException {
+ String scheme = uri.getScheme();
+ if(scheme == null || scheme.equals("file")) {
+ addExternalSource(uri.getPath(), mimeType);
+ return;
+ }
+
+ AssetFileDescriptor fd = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ fd = resolver.openAssetFileDescriptor(uri, "r");
+ if (fd == null) {
+ return;
+ }
+ addExternalSource(fd.getFileDescriptor(), mimeType);
+ return;
+ } catch (SecurityException ex) {
+ } catch (IOException ex) {
+ } finally {
+ if (fd != null) {
+ fd.close();
+ }
+ }
+
+ // TODO: try server side.
+ }
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp!
+ */
+ /**
+ * MIME type for SubRip (SRT) container. Used in {@link #addExternalSource()} APIs.
+ * {@hide}
+ */
+ public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+
+ /**
+ * Adds an external source file (FileDescriptor).
+ * It is the caller's responsibility to close the file descriptor.
+ * It is safe to do so as soon as this call returns.
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param mimeType A MIME type for the content. It can be null.
+ * <ul>
+ * <li>{@link #MEDIA_MIMETYPE_TEXT_SUBRIP}
+ * </ul>
+ * {@hide}
+ */
+ // FIXME: define error codes and throws exceptions according to the error codes.
+ // (IllegalStateException, IOException).
+ public void addExternalSource(FileDescriptor fd, String mimeType)
+ throws IllegalArgumentException {
+ // intentionally less than LONG_MAX
+ addExternalSource(fd, 0, 0x7ffffffffffffffL, mimeType);
}
/**
- * Disables timed text display.
- * @return true if the text track is disabled successfully.
+ * Adds an external timed text file (FileDescriptor).
+ * It is the caller's responsibility to close the file descriptor.
+ * It is safe to do so as soon as this call returns.
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param offset the offset into the file where the data to be played starts, in bytes
+ * @param length the length in bytes of the data to be played
+ * @param mimeType A MIME type for the content. It can be null.
* {@hide}
*/
- public boolean disableTimedText() {
- return setParameter(KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX, -1);
+ // FIXME: define error codes and throws exceptions according to the error codes.
+ // (IllegalStateException, IOException).
+ public void addExternalSource(FileDescriptor fd, long offset, long length, String mimeType)
+ throws IllegalArgumentException {
+ if (!availableMimeTypeForExternalSource(mimeType)) {
+ throw new IllegalArgumentException("Illegal mimeType for external source: " + mimeType);
+ }
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ request.writeInterfaceToken(IMEDIA_PLAYER);
+ request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE_FD);
+ request.writeFileDescriptor(fd);
+ request.writeLong(offset);
+ request.writeLong(length);
+ request.writeString(mimeType);
+ invoke(request, reply);
+ }
+
+ /**
+ * Selects a track.
+ * <p>
+ * If a MediaPlayer is in invalid state, it throws exception.
+ * If a MediaPlayer is in Started state, the selected track will be presented immediately.
+ * If a MediaPlayer is not in Started state, it just marks the track to be played.
+ * </p>
+ * <p>
+ * In any valid state, if it is called multiple times on the same type of track (ie. Video,
+ * Audio, Timed Text), the most recent one will be chosen.
+ * </p>
+ * <p>
+ * The first audio and video tracks will be selected by default, even though this function is not
+ * called. However, no timed text track will be selected until this function is called.
+ * </p>
+ * {@hide}
+ */
+ // FIXME: define error codes and throws exceptions according to the error codes.
+ // (IllegalStateException, IOException, IllegalArgumentException).
+ public void selectTrack(int index) {
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ request.writeInterfaceToken(IMEDIA_PLAYER);
+ request.writeInt(INVOKE_ID_SELECT_TRACK);
+ request.writeInt(index);
+ invoke(request, reply);
+ }
+
+ /**
+ * Unselect a track.
+ * If the track identified by index has not been selected before, it throws an exception.
+ * {@hide}
+ */
+ // FIXME: define error codes and throws exceptions according to the error codes.
+ // (IllegalStateException, IOException, IllegalArgumentException).
+ public void unselectTrack(int index) {
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ request.writeInterfaceToken(IMEDIA_PLAYER);
+ request.writeInt(INVOKE_ID_UNSELECT_TRACK);
+ request.writeInt(index);
+ invoke(request, reply);
}
/**
@@ -1641,14 +1906,14 @@
// No real default action so far.
return;
case MEDIA_TIMED_TEXT:
- if (mOnTimedTextListener != null) {
- if (msg.obj == null) {
- mOnTimedTextListener.onTimedText(mMediaPlayer, null);
- } else {
- if (msg.obj instanceof byte[]) {
- TimedText text = new TimedText((byte[])(msg.obj));
- mOnTimedTextListener.onTimedText(mMediaPlayer, text);
- }
+ if (mOnTimedTextListener == null)
+ return;
+ if (msg.obj == null) {
+ mOnTimedTextListener.onTimedText(mMediaPlayer, null);
+ } else {
+ if (msg.obj instanceof byte[]) {
+ TimedText text = new TimedText((byte[])(msg.obj));
+ mOnTimedTextListener.onTimedText(mMediaPlayer, text);
}
}
return;
@@ -1663,7 +1928,7 @@
}
}
- /**
+ /*
* Called from native code when an interesting event happens. This method
* just uses the EventHandler system to post the event back to the main app thread.
* We use a weak reference to the original MediaPlayer object so that the native
@@ -1977,6 +2242,13 @@
*/
public static final int MEDIA_INFO_METADATA_UPDATE = 802;
+ /** Failed to handle timed text track properly.
+ * @see android.media.MediaPlayer.OnInfoListener
+ *
+ * {@hide}
+ */
+ public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
+
/**
* Interface definition of a callback to be invoked to communicate some
* info and/or warning about the media or its playback.
diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp
index 052ebf0..619c149 100644
--- a/media/libmediaplayerservice/StagefrightPlayer.cpp
+++ b/media/libmediaplayerservice/StagefrightPlayer.cpp
@@ -166,7 +166,8 @@
}
status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) {
- return INVALID_OPERATION;
+ ALOGV("invoke()");
+ return mPlayer->invoke(request, reply);
}
void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) {
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 9e00bb3..b4cb1ab 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -1114,7 +1114,7 @@
modifyFlags(AUDIO_RUNNING, CLEAR);
}
- if (mFlags & TEXTPLAYER_STARTED) {
+ if (mFlags & TEXTPLAYER_INITIALIZED) {
mTextDriver->pause();
modifyFlags(TEXT_RUNNING, CLEAR);
}
@@ -1268,32 +1268,6 @@
return OK;
}
-status_t AwesomePlayer::setTimedTextTrackIndex(int32_t index) {
- if (mTextDriver != NULL) {
- if (index >= 0) { // to turn on a text track
- status_t err = mTextDriver->setTimedTextTrackIndex(index);
- if (err != OK) {
- return err;
- }
-
- modifyFlags(TEXT_RUNNING, SET);
- modifyFlags(TEXTPLAYER_STARTED, SET);
- return OK;
- } else { // to turn off the text track display
- if (mFlags & TEXT_RUNNING) {
- modifyFlags(TEXT_RUNNING, CLEAR);
- }
- if (mFlags & TEXTPLAYER_STARTED) {
- modifyFlags(TEXTPLAYER_STARTED, CLEAR);
- }
-
- return mTextDriver->setTimedTextTrackIndex(index);
- }
- } else {
- return INVALID_OPERATION;
- }
-}
-
status_t AwesomePlayer::seekTo_l(int64_t timeUs) {
if (mFlags & CACHE_UNDERRUN) {
modifyFlags(CACHE_UNDERRUN, CLEAR);
@@ -1315,7 +1289,7 @@
seekAudioIfNecessary_l();
- if (mFlags & TEXTPLAYER_STARTED) {
+ if (mFlags & TEXTPLAYER_INITIALIZED) {
mTextDriver->seekToAsync(mSeekTimeUs);
}
@@ -1691,8 +1665,8 @@
}
}
- if ((mFlags & TEXTPLAYER_STARTED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) {
- mTextDriver->resume();
+ if ((mFlags & TEXTPLAYER_INITIALIZED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) {
+ mTextDriver->start();
modifyFlags(TEXT_RUNNING, SET);
}
@@ -2232,20 +2206,6 @@
status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
switch (key) {
- case KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX:
- {
- Mutex::Autolock autoLock(mTimedTextLock);
- return setTimedTextTrackIndex(request.readInt32());
- }
- case KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE:
- {
- Mutex::Autolock autoLock(mTimedTextLock);
- if (mTextDriver == NULL) {
- mTextDriver = new TimedTextDriver(mListener);
- }
-
- return mTextDriver->addOutOfBandTextSource(request);
- }
case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS:
{
return setCacheStatCollectFreq(request);
@@ -2294,6 +2254,103 @@
}
}
+status_t AwesomePlayer::invoke(const Parcel &request, Parcel *reply) {
+ if (NULL == reply) {
+ return android::BAD_VALUE;
+ }
+ int32_t methodId;
+ status_t ret = request.readInt32(&methodId);
+ if (ret != android::OK) {
+ return ret;
+ }
+ switch(methodId) {
+ case INVOKE_ID_GET_TRACK_INFO:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ return INVALID_OPERATION;
+ }
+ mTextDriver->getTrackInfo(reply);
+ return OK;
+ }
+ case INVOKE_ID_ADD_EXTERNAL_SOURCE:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ mTextDriver = new TimedTextDriver(mListener);
+ }
+ // String values written in Parcel are UTF-16 values.
+ String16 uri16 = request.readString16();
+ const char *uri = NULL;
+ if (uri16 != NULL) {
+ uri = String8(uri16).string();
+ }
+ String16 mimeType16 = request.readString16();
+ const char *mimeType = NULL;
+ if (mimeType16 != NULL) {
+ mimeType = String8(mimeType16).string();
+ }
+ return mTextDriver->addOutOfBandTextSource(uri, mimeType);
+ }
+ case INVOKE_ID_ADD_EXTERNAL_SOURCE_FD:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ mTextDriver = new TimedTextDriver(mListener);
+ }
+ int fd = request.readFileDescriptor();
+ off64_t offset = request.readInt64();
+ size_t length = request.readInt64();
+ String16 mimeType16 = request.readString16();
+ const char *mimeType = NULL;
+ if (mimeType16 != NULL) {
+ mimeType = String8(mimeType16).string();
+ }
+
+ return mTextDriver->addOutOfBandTextSource(
+ fd, offset, length, mimeType);
+ }
+ case INVOKE_ID_SELECT_TRACK:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ return INVALID_OPERATION;
+ }
+
+ status_t err = mTextDriver->selectTrack(
+ request.readInt32());
+ if (err == OK) {
+ modifyFlags(TEXTPLAYER_INITIALIZED, SET);
+ if (mFlags & PLAYING && !(mFlags & TEXT_RUNNING)) {
+ mTextDriver->start();
+ modifyFlags(TEXT_RUNNING, SET);
+ }
+ }
+ return err;
+ }
+ case INVOKE_ID_UNSELECT_TRACK:
+ {
+ Mutex::Autolock autoLock(mTimedTextLock);
+ if (mTextDriver == NULL) {
+ return INVALID_OPERATION;
+ }
+ status_t err = mTextDriver->unselectTrack(
+ request.readInt32());
+ if (err == OK) {
+ modifyFlags(TEXTPLAYER_INITIALIZED, CLEAR);
+ modifyFlags(TEXT_RUNNING, CLEAR);
+ }
+ return err;
+ }
+ default:
+ {
+ return ERROR_UNSUPPORTED;
+ }
+ }
+ // It will not reach here.
+ return OK;
+}
+
bool AwesomePlayer::isStreamingHTTP() const {
return mCachedSource != NULL || mWVMExtractor != NULL;
}
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 444e823..2549de6 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -52,5 +52,6 @@
const char *MEDIA_MIMETYPE_CONTAINER_WVM = "video/wvm";
const char *MEDIA_MIMETYPE_TEXT_3GPP = "text/3gpp-tt";
+const char *MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
} // namespace android
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 4c7bfa6..06e9468 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -90,6 +90,7 @@
status_t setParameter(int key, const Parcel &request);
status_t getParameter(int key, Parcel *reply);
+ status_t invoke(const Parcel &request, Parcel *reply);
status_t setCacheStatCollectFreq(const Parcel &request);
status_t seekTo(int64_t timeUs);
@@ -100,8 +101,6 @@
void postAudioEOS(int64_t delayUs = 0ll);
void postAudioSeekComplete();
- status_t setTimedTextTrackIndex(int32_t index);
-
status_t dump(int fd, const Vector<String16> &args) const;
private:
@@ -136,7 +135,7 @@
INCOGNITO = 0x8000,
TEXT_RUNNING = 0x10000,
- TEXTPLAYER_STARTED = 0x20000,
+ TEXTPLAYER_INITIALIZED = 0x20000,
SLOW_DECODER_HACK = 0x40000,
};
diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.cpp b/media/libstagefright/timedtext/TimedText3GPPSource.cpp
index 4a3bfd3..c423ef0 100644
--- a/media/libstagefright/timedtext/TimedText3GPPSource.cpp
+++ b/media/libstagefright/timedtext/TimedText3GPPSource.cpp
@@ -110,4 +110,8 @@
return OK;
}
+sp<MetaData> TimedText3GPPSource::getFormat() {
+ return mSource->getFormat();
+}
+
} // namespace android
diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.h b/media/libstagefright/timedtext/TimedText3GPPSource.h
index dfc6418..4ec3d8a 100644
--- a/media/libstagefright/timedtext/TimedText3GPPSource.h
+++ b/media/libstagefright/timedtext/TimedText3GPPSource.h
@@ -37,6 +37,7 @@
Parcel *parcel,
const MediaSource::ReadOptions *options = NULL);
virtual status_t extractGlobalDescriptions(Parcel *parcel);
+ virtual sp<MetaData> getFormat();
protected:
virtual ~TimedText3GPPSource();
diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp
index c70870e..ed83894 100644
--- a/media/libstagefright/timedtext/TimedTextDriver.cpp
+++ b/media/libstagefright/timedtext/TimedTextDriver.cpp
@@ -20,10 +20,13 @@
#include <binder/IPCThreadState.h>
+#include <media/mediaplayer.h>
#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
@@ -47,24 +50,22 @@
}
TimedTextDriver::~TimedTextDriver() {
- mTextInBandVector.clear();
- mTextOutOfBandVector.clear();
+ mTextSourceVector.clear();
mLooper->stop();
}
-status_t TimedTextDriver::setTimedTextTrackIndex_l(int32_t index) {
- if (index >=
- (int)(mTextInBandVector.size() + mTextOutOfBandVector.size())) {
+status_t TimedTextDriver::selectTrack_l(int32_t index) {
+ if (index >= (int)(mTextSourceVector.size())) {
return BAD_VALUE;
}
sp<TimedTextSource> source;
- if (index < mTextInBandVector.size()) {
- source = mTextInBandVector.itemAt(index);
- } else {
- source = mTextOutOfBandVector.itemAt(index - mTextInBandVector.size());
- }
+ source = mTextSourceVector.itemAt(index);
mPlayer->setDataSource(source);
+ if (mState == UNINITIALIZED) {
+ mState = PAUSED;
+ }
+ mCurrentTrackIndex = index;
return OK;
}
@@ -73,13 +74,10 @@
switch (mState) {
case UNINITIALIZED:
return INVALID_OPERATION;
- case STOPPED:
- mPlayer->start();
- break;
case PLAYING:
return OK;
case PAUSED:
- mPlayer->resume();
+ mPlayer->start();
break;
default:
TRESPASS();
@@ -88,10 +86,6 @@
return OK;
}
-status_t TimedTextDriver::stop() {
- return pause();
-}
-
// TODO: Test if pause() works properly.
// Scenario 1: start - pause - resume
// Scenario 2: start - seek
@@ -101,8 +95,6 @@
switch (mState) {
case UNINITIALIZED:
return INVALID_OPERATION;
- case STOPPED:
- return OK;
case PLAYING:
mPlayer->pause();
break;
@@ -115,45 +107,17 @@
return OK;
}
-status_t TimedTextDriver::resume() {
- return start();
-}
-
-status_t TimedTextDriver::seekToAsync(int64_t timeUs) {
- mPlayer->seekToAsync(timeUs);
- return OK;
-}
-
-status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) {
- // TODO: This is current implementation for MediaPlayer::disableTimedText().
- // Find better way for readability.
- if (index < 0) {
- mPlayer->pause();
- return OK;
- }
-
+status_t TimedTextDriver::selectTrack(int32_t index) {
status_t ret = OK;
Mutex::Autolock autoLock(mLock);
switch (mState) {
case UNINITIALIZED:
- ret = INVALID_OPERATION;
- break;
case PAUSED:
- ret = setTimedTextTrackIndex_l(index);
+ ret = selectTrack_l(index);
break;
case PLAYING:
mPlayer->pause();
- ret = setTimedTextTrackIndex_l(index);
- if (ret != OK) {
- break;
- }
- mPlayer->start();
- break;
- case STOPPED:
- // TODO: The only difference between STOPPED and PAUSED is this
- // part. Revise the flow from "MediaPlayer::enableTimedText()" and
- // remove one of the status, PAUSED and STOPPED, if possible.
- ret = setTimedTextTrackIndex_l(index);
+ ret = selectTrack_l(index);
if (ret != OK) {
break;
}
@@ -165,6 +129,24 @@
return ret;
}
+status_t TimedTextDriver::unselectTrack(int32_t index) {
+ if (mCurrentTrackIndex != index) {
+ return INVALID_OPERATION;
+ }
+ status_t err = pause();
+ if (err != OK) {
+ return err;
+ }
+ Mutex::Autolock autoLock(mLock);
+ mState = UNINITIALIZED;
+ return OK;
+}
+
+status_t TimedTextDriver::seekToAsync(int64_t timeUs) {
+ mPlayer->seekToAsync(timeUs);
+ return OK;
+}
+
status_t TimedTextDriver::addInBandTextSource(
const sp<MediaSource>& mediaSource) {
sp<TimedTextSource> source =
@@ -173,25 +155,17 @@
return ERROR_UNSUPPORTED;
}
Mutex::Autolock autoLock(mLock);
- mTextInBandVector.add(source);
- if (mState == UNINITIALIZED) {
- mState = STOPPED;
- }
+ mTextSourceVector.add(source);
return OK;
}
status_t TimedTextDriver::addOutOfBandTextSource(
- const Parcel &request) {
+ const char *uri, const char *mimeType) {
// TODO: Define "TimedTextSource::CreateFromURI(uri)"
// and move below lines there..?
- // String values written in Parcel are UTF-16 values.
- const String16 uri16 = request.readString16();
- String8 uri = String8(request.readString16());
-
- uri.toLower();
// To support local subtitle file only for now
- if (strncasecmp("file://", uri.string(), 7)) {
+ if (strncasecmp("file://", uri, 7)) {
return ERROR_UNSUPPORTED;
}
sp<DataSource> dataSource =
@@ -201,7 +175,7 @@
}
sp<TimedTextSource> source;
- if (uri.getPathExtension() == String8(".srt")) {
+ if (strcasecmp(mimeType, MEDIA_MIMETYPE_TEXT_SUBRIP)) {
source = TimedTextSource::CreateTimedTextSource(
dataSource, TimedTextSource::OUT_OF_BAND_FILE_SRT);
}
@@ -211,12 +185,38 @@
}
Mutex::Autolock autoLock(mLock);
-
- mTextOutOfBandVector.add(source);
- if (mState == UNINITIALIZED) {
- mState = STOPPED;
- }
+ mTextSourceVector.add(source);
return OK;
}
+status_t TimedTextDriver::addOutOfBandTextSource(
+ int fd, off64_t offset, size_t length, const char *mimeType) {
+ // Not supported yet. This requires DataSource::sniff to detect various text
+ // formats such as srt/smi/ttml.
+ return ERROR_UNSUPPORTED;
+}
+
+void TimedTextDriver::getTrackInfo(Parcel *parcel) {
+ Mutex::Autolock autoLock(mLock);
+ Vector<sp<TimedTextSource> >::const_iterator iter;
+ parcel->writeInt32(mTextSourceVector.size());
+ for (iter = mTextSourceVector.begin();
+ iter != mTextSourceVector.end(); ++iter) {
+ sp<MetaData> meta = (*iter)->getFormat();
+ if (meta != NULL) {
+ // There are two fields.
+ parcel->writeInt32(2);
+
+ // track type.
+ parcel->writeInt32(MEDIA_TRACK_TYPE_TIMEDTEXT);
+
+ const char *lang = "und";
+ meta->findCString(kKeyMediaLanguage, &lang);
+ parcel->writeString16(String16(lang));
+ } else {
+ parcel->writeInt32(0);
+ }
+ }
+}
+
} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
index bda7b46..8717914 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.cpp
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -56,10 +56,6 @@
(new AMessage(kWhatPause, id()))->post();
}
-void TimedTextPlayer::resume() {
- start();
-}
-
void TimedTextPlayer::seekToAsync(int64_t timeUs) {
sp<AMessage> msg = new AMessage(kWhatSeek, id());
msg->setInt64("seekTimeUs", timeUs);
@@ -104,9 +100,9 @@
if (obj != NULL) {
sp<ParcelEvent> parcelEvent;
parcelEvent = static_cast<ParcelEvent*>(obj.get());
- notifyListener(MEDIA_TIMED_TEXT, &(parcelEvent->parcel));
+ notifyListener(&(parcelEvent->parcel));
} else {
- notifyListener(MEDIA_TIMED_TEXT);
+ notifyListener();
}
doRead();
break;
@@ -119,14 +115,18 @@
mSource->stop();
}
mSource = static_cast<TimedTextSource*>(obj.get());
- mSource->start();
- Parcel parcel;
- if (mSource->extractGlobalDescriptions(&parcel) == OK &&
- parcel.dataSize() > 0) {
- notifyListener(MEDIA_TIMED_TEXT, &parcel);
- } else {
- notifyListener(MEDIA_TIMED_TEXT);
+ status_t err = mSource->start();
+ if (err != OK) {
+ notifyError(err);
+ break;
}
+ Parcel parcel;
+ err = mSource->extractGlobalDescriptions(&parcel);
+ if (err != OK) {
+ notifyError(err);
+ break;
+ }
+ notifyListener(&parcel);
break;
}
}
@@ -141,8 +141,12 @@
void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) {
int64_t timeUs = 0;
sp<ParcelEvent> parcelEvent = new ParcelEvent();
- mSource->read(&timeUs, &(parcelEvent->parcel), options);
- postTextEvent(parcelEvent, timeUs);
+ status_t err = mSource->read(&timeUs, &(parcelEvent->parcel), options);
+ if (err != OK) {
+ notifyError(err);
+ } else {
+ postTextEvent(parcelEvent, timeUs);
+ }
}
void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) {
@@ -151,7 +155,7 @@
int64_t positionUs, delayUs;
int32_t positionMs = 0;
listener->getCurrentPosition(&positionMs);
- positionUs = positionMs * 1000;
+ positionUs = positionMs * 1000ll;
if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) {
delayUs = 0;
@@ -167,13 +171,20 @@
}
}
-void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) {
+void TimedTextPlayer::notifyError(int error) {
+ sp<MediaPlayerBase> listener = mListener.promote();
+ if (listener != NULL) {
+ listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error);
+ }
+}
+
+void TimedTextPlayer::notifyListener(const Parcel *parcel) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
if (parcel != NULL && (parcel->dataSize() > 0)) {
- listener->sendEvent(msg, 0, 0, parcel);
+ listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel);
} else { // send an empty timed text to clear the screen
- listener->sendEvent(msg);
+ listener->sendEvent(MEDIA_TIMED_TEXT);
}
}
}
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
index 837beeb..b869f18 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -40,7 +40,6 @@
void start();
void pause();
- void resume();
void seekToAsync(int64_t timeUs);
void setDataSource(sp<TimedTextSource> source);
@@ -68,7 +67,8 @@
void doRead(MediaSource::ReadOptions* options = NULL);
void onTextEvent();
void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1);
- void notifyListener(int msg, const Parcel *parcel = NULL);
+ void notifyError(int error = 0);
+ void notifyListener(const Parcel *parcel = NULL);
DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer);
};
diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp
index 3752d34..c44a99b 100644
--- a/media/libstagefright/timedtext/TimedTextSRTSource.cpp
+++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp
@@ -21,8 +21,10 @@
#include <binder/Parcel.h>
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
#include "TimedTextSRTSource.h"
#include "TextDescriptions.h"
@@ -31,6 +33,7 @@
TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource)
: mSource(dataSource),
+ mMetaData(new MetaData),
mIndex(0) {
}
@@ -42,10 +45,14 @@
if (err != OK) {
reset();
}
+ // TODO: Need to detect the language, because SRT doesn't give language
+ // information explicitly.
+ mMetaData->setCString(kKeyMediaLanguage, "");
return err;
}
void TimedTextSRTSource::reset() {
+ mMetaData->clear();
mTextVector.clear();
mIndex = 0;
}
@@ -272,4 +279,8 @@
return OK;
}
+sp<MetaData> TimedTextSRTSource::getFormat() {
+ return mMetaData;
+}
+
} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h
index acc01f9..62710a0 100644
--- a/media/libstagefright/timedtext/TimedTextSRTSource.h
+++ b/media/libstagefright/timedtext/TimedTextSRTSource.h
@@ -39,12 +39,14 @@
int64_t *timeUs,
Parcel *parcel,
const MediaSource::ReadOptions *options = NULL);
+ virtual sp<MetaData> getFormat();
protected:
virtual ~TimedTextSRTSource();
private:
sp<DataSource> mSource;
+ sp<MetaData> mMetaData;
struct TextInfo {
int64_t endTimeUs;
diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp
index ffbe1c3..953f7b5 100644
--- a/media/libstagefright/timedtext/TimedTextSource.cpp
+++ b/media/libstagefright/timedtext/TimedTextSource.cpp
@@ -59,4 +59,8 @@
return NULL;
}
+sp<MetaData> TimedTextSource::getFormat() {
+ return NULL;
+}
+
} // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextSource.h b/media/libstagefright/timedtext/TimedTextSource.h
index 06bae71..9349342 100644
--- a/media/libstagefright/timedtext/TimedTextSource.h
+++ b/media/libstagefright/timedtext/TimedTextSource.h
@@ -25,6 +25,7 @@
namespace android {
class DataSource;
+class MetaData;
class Parcel;
class TimedTextSource : public RefBase {
@@ -48,6 +49,7 @@
virtual status_t extractGlobalDescriptions(Parcel *parcel) {
return INVALID_OPERATION;
}
+ virtual sp<MetaData> getFormat();
protected:
virtual ~TimedTextSource() { }