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() { }