Merge "For out of band timed text support (timed text in a separate file)."
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index cfa4cfd..50a378f 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -127,6 +127,7 @@
 
 enum media_set_parameter_keys {
     KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000,
+    KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001,
 };
 // ----------------------------------------------------------------------------
 // ref-counted object for callbacks
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index f731dfb..8c4b274 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -50,7 +50,6 @@
         ThrottledSource.cpp               \
         TimeSource.cpp                    \
         TimedEventQueue.cpp               \
-        TimedTextPlayer.cpp               \
         Utils.cpp                         \
         VBRISeeker.cpp                    \
         WAVExtractor.cpp                  \
@@ -89,6 +88,7 @@
         libstagefright_avcenc \
         libstagefright_m4vh263enc \
         libstagefright_matroska \
+        libstagefright_timedtext \
         libvpx \
         libstagefright_mpeg2ts \
         libstagefright_httplive \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 70053ea..3d270f8 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -29,9 +29,10 @@
 #include "include/NuCachedSource2.h"
 #include "include/ThrottledSource.h"
 #include "include/MPEG2TSExtractor.h"
-#include "include/TimedTextPlayer.h"
 #include "include/WVMExtractor.h"
 
+#include "timedtext/TimedTextPlayer.h"
+
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <media/IMediaPlayerService.h>
@@ -1282,6 +1283,7 @@
 }
 
 void AwesomePlayer::addTextSource(sp<MediaSource> source) {
+    Mutex::Autolock autoLock(mTimedTextLock);
     CHECK(source != NULL);
 
     if (mTextPlayer == NULL) {
@@ -2066,10 +2068,26 @@
 }
 
 status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
-    if (key == KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX) {
-        return setTimedTextTrackIndex(request.readInt32());
+    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 (mTextPlayer == NULL) {
+                mTextPlayer = new TimedTextPlayer(this, mListener, &mQueue);
+            }
+
+            return mTextPlayer->setParameter(key, request);
+        }
+        default:
+        {
+            return ERROR_UNSUPPORTED;
+        }
     }
-    return ERROR_UNSUPPORTED;
 }
 
 status_t AwesomePlayer::getParameter(int key, Parcel *reply) {
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 6692809..98ac044 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -20,7 +20,7 @@
 #include "include/MPEG4Extractor.h"
 #include "include/SampleTable.h"
 #include "include/ESDS.h"
-#include "include/TimedTextPlayer.h"
+#include "timedtext/TimedTextPlayer.h"
 
 #include <arpa/inet.h>
 
diff --git a/media/libstagefright/TimedTextPlayer.cpp b/media/libstagefright/TimedTextPlayer.cpp
deleted file mode 100644
index 1ac22cb..0000000
--- a/media/libstagefright/TimedTextPlayer.cpp
+++ /dev/null
@@ -1,252 +0,0 @@
- /*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "TimedTextPlayer"
-#include <utils/Log.h>
-
-#include <binder/IPCThreadState.h>
-#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/MetaData.h>
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/Utils.h>
-#include "include/AwesomePlayer.h"
-#include "include/TimedTextPlayer.h"
-
-namespace android {
-
-struct TimedTextEvent : public TimedEventQueue::Event {
-    TimedTextEvent(
-            TimedTextPlayer *player,
-            void (TimedTextPlayer::*method)())
-        : mPlayer(player),
-          mMethod(method) {
-    }
-
-protected:
-    virtual ~TimedTextEvent() {}
-
-    virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
-        (mPlayer->*mMethod)();
-    }
-
-private:
-    TimedTextPlayer *mPlayer;
-    void (TimedTextPlayer::*mMethod)();
-
-    TimedTextEvent(const TimedTextEvent &);
-    TimedTextEvent &operator=(const TimedTextEvent &);
-};
-
-TimedTextPlayer::TimedTextPlayer(
-        AwesomePlayer *observer,
-        const wp<MediaPlayerBase> &listener,
-        TimedEventQueue *queue)
-    : mSource(NULL),
-      mSeekTimeUs(0),
-      mStarted(false),
-      mTextEventPending(false),
-      mQueue(queue),
-      mListener(listener),
-      mObserver(observer),
-      mTextBuffer(NULL) {
-    mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent);
-}
-
-TimedTextPlayer::~TimedTextPlayer() {
-    if (mStarted) {
-        reset();
-    }
-
-    mTextTrackVector.clear();
-}
-
-status_t TimedTextPlayer::start(uint8_t index) {
-    CHECK(!mStarted);
-
-    if (index >= mTextTrackVector.size()) {
-        LOGE("Incorrect text track index");
-        return BAD_VALUE;
-    }
-
-    mSource = mTextTrackVector.itemAt(index);
-
-    status_t err = mSource->start();
-
-    if (err != OK) {
-        return err;
-    }
-
-    int64_t positionUs;
-    mObserver->getPosition(&positionUs);
-    seekTo(positionUs);
-
-    postTextEvent();
-
-    mStarted = true;
-
-    return OK;
-}
-
-void TimedTextPlayer::pause() {
-    CHECK(mStarted);
-
-    cancelTextEvent();
-}
-
-void TimedTextPlayer::resume() {
-    CHECK(mStarted);
-
-    postTextEvent();
-}
-
-void TimedTextPlayer::reset() {
-    CHECK(mStarted);
-
-    // send an empty text to clear the screen
-    notifyListener(MEDIA_TIMED_TEXT);
-
-    cancelTextEvent();
-
-    mSeeking = false;
-    mStarted = false;
-
-    if (mTextBuffer != NULL) {
-        mTextBuffer->release();
-        mTextBuffer = NULL;
-    }
-
-    if (mSource != NULL) {
-        mSource->stop();
-        mSource.clear();
-        mSource = NULL;
-    }
-}
-
-status_t TimedTextPlayer::seekTo(int64_t time_us) {
-    Mutex::Autolock autoLock(mLock);
-
-    mSeeking = true;
-    mSeekTimeUs = time_us;
-
-    return OK;
-}
-
-status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) {
-    if (index >= (int)(mTextTrackVector.size())) {
-        return BAD_VALUE;
-    }
-
-    if (mStarted) {
-        reset();
-    }
-
-    if (index >= 0) {
-        return start(index);
-    }
-    return OK;
-}
-
-void TimedTextPlayer::onTextEvent() {
-    Mutex::Autolock autoLock(mLock);
-
-    if (!mTextEventPending) {
-        return;
-    }
-    mTextEventPending = false;
-
-    MediaSource::ReadOptions options;
-    if (mSeeking) {
-        options.setSeekTo(mSeekTimeUs,
-                MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
-        mSeeking = false;
-
-        if (mTextBuffer != NULL) {
-            mTextBuffer->release();
-            mTextBuffer = NULL;
-        }
-
-        notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen
-    }
-
-    if (mTextBuffer != NULL) {
-        uint8_t *tmp = (uint8_t *)(mTextBuffer->data());
-        size_t len = (*tmp) << 8 | (*(tmp + 1));
-
-        notifyListener(MEDIA_TIMED_TEXT,
-                       tmp + 2,
-                       len);
-
-        mTextBuffer->release();
-        mTextBuffer = NULL;
-
-    }
-
-    if (mSource->read(&mTextBuffer, &options) != OK) {
-        return;
-    }
-
-    int64_t positionUs, timeUs;
-    mObserver->getPosition(&positionUs);
-    mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs);
-
-    //send the text now
-    if (timeUs <= positionUs + 100000ll) {
-        postTextEvent();
-    } else {
-        postTextEvent(timeUs - positionUs - 100000ll);
-    }
-}
-
-void TimedTextPlayer::postTextEvent(int64_t delayUs) {
-    if (mTextEventPending) {
-        return;
-    }
-
-    mTextEventPending = true;
-    mQueue->postEventWithDelay(mTextEvent, delayUs < 0 ? 10000 : delayUs);
-}
-
-void TimedTextPlayer::cancelTextEvent() {
-    mQueue->cancelEvent(mTextEvent->eventID());
-    mTextEventPending = false;
-}
-
-void TimedTextPlayer::addTextSource(sp<MediaSource> source) {
-    mTextTrackVector.add(source);
-}
-
-void TimedTextPlayer::notifyListener(
-        int msg, const void *data, size_t size) {
-    if (mListener != NULL) {
-        sp<MediaPlayerBase> listener = mListener.promote();
-
-        if (listener != NULL) {
-            if (size > 0) {
-                mData.freeData();
-                mData.write(data, size);
-
-                listener->sendEvent(msg, 0, 0, &mData);
-            } else { // send an empty timed text to clear the screen
-                listener->sendEvent(msg);
-            }
-        }
-    }
-}
-}
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 3c9a121..a9e8e95 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -231,6 +231,7 @@
 
     int64_t mLastVideoTimeUs;
     TimedTextPlayer *mTextPlayer;
+    mutable Mutex mTimedTextLock;
 
     sp<WVMExtractor> mWVMExtractor;
 
diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk
new file mode 100644
index 0000000..9a6062c
--- /dev/null
+++ b/media/libstagefright/timedtext/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=                 \
+        TimedTextParser.cpp       \
+        TimedTextPlayer.cpp
+
+LOCAL_CFLAGS += -Wno-multichar
+LOCAL_C_INCLUDES:= \
+        $(JNI_H_INCLUDE) \
+        $(TOP)/frameworks/base/media/libstagefright \
+        $(TOP)/frameworks/base/include/media/stagefright/openmax
+
+LOCAL_MODULE:= libstagefright_timedtext
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libstagefright/timedtext/TimedTextParser.cpp b/media/libstagefright/timedtext/TimedTextParser.cpp
new file mode 100644
index 0000000..0bada16
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextParser.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TimedTextParser.h"
+#include <media/stagefright/DataSource.h>
+
+namespace android {
+
+TimedTextParser::TimedTextParser()
+    : mDataSource(NULL),
+      mOffset(0),
+      mIndex(0) {
+}
+
+TimedTextParser::~TimedTextParser() {
+    reset();
+}
+
+status_t TimedTextParser::init(
+        const sp<DataSource> &dataSource, FileType fileType) {
+    mDataSource = dataSource;
+    mFileType = fileType;
+
+    status_t err;
+    if ((err = scanFile()) != OK) {
+        reset();
+        return err;
+    }
+
+    return OK;
+}
+
+void TimedTextParser::reset() {
+    mDataSource.clear();
+    mTextVector.clear();
+    mOffset = 0;
+    mIndex = 0;
+}
+
+// scan the text file to get start/stop time and the
+// offset of each piece of text content
+status_t TimedTextParser::scanFile() {
+    if (mFileType != OUT_OF_BAND_FILE_SRT) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    off64_t offset = 0;
+    int64_t startTimeUs;
+    bool endOfFile = false;
+
+    while (!endOfFile) {
+        TextInfo info;
+        status_t err = getNextInSrtFileFormat(&offset, &startTimeUs, &info);
+
+        if (err != OK) {
+            if (err == ERROR_END_OF_STREAM) {
+                endOfFile = true;
+            } else {
+                return err;
+            }
+        } else {
+            mTextVector.add(startTimeUs, info);
+        }
+    }
+
+    if (mTextVector.isEmpty()) {
+        return ERROR_MALFORMED;
+    }
+    return OK;
+}
+
+// read one line started from *offset and store it into data.
+status_t TimedTextParser::readNextLine(off64_t *offset, AString *data) {
+    char character;
+
+    data->clear();
+
+    while (true) {
+        ssize_t err;
+        if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) {
+            if (err == 0) {
+                return ERROR_END_OF_STREAM;
+            }
+            return ERROR_IO;
+        }
+
+        (*offset) ++;
+
+        // a line could end with CR, LF or CR + LF
+        if (character == 10) {
+            break;
+        } else if (character == 13) {
+            if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) {
+                if (err == 0) { // end of the stream
+                    return OK;
+                }
+                return ERROR_IO;
+            }
+
+            (*offset) ++;
+
+            if (character != 10) {
+                (*offset) --;
+            }
+            break;
+        }
+
+        data->append(character);
+    }
+
+    return OK;
+}
+
+/* SRT format:
+ *  Subtitle number
+ *  Start time --> End time
+ *  Text of subtitle (one or more lines)
+ *  Blank line
+ *
+ * .srt file example:
+ *  1
+ *  00:00:20,000 --> 00:00:24,400
+ *  Altocumulus clouds occur between six thousand
+ *
+ *  2
+ *  00:00:24,600 --> 00:00:27,800
+ *  and twenty thousand feet above ground level.
+ */
+status_t TimedTextParser::getNextInSrtFileFormat(
+        off64_t *offset, int64_t *startTimeUs, TextInfo *info) {
+    AString data;
+    status_t err;
+    if ((err = readNextLine(offset, &data)) != OK) {
+        return err;
+    }
+
+    // to skip the first line
+    if ((err = readNextLine(offset, &data)) != OK) {
+        return err;
+    }
+
+    int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
+    // the start time format is: hours:minutes:seconds,milliseconds
+    // 00:00:24,600 --> 00:00:27,800
+    if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d",
+                &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) {
+        return ERROR_MALFORMED;
+    }
+
+    *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll;
+    info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll;
+    if (info->endTimeUs <= *startTimeUs) {
+        return ERROR_MALFORMED;
+    }
+
+    info->offset = *offset;
+
+    bool needMoreData = true;
+    while (needMoreData) {
+        if ((err = readNextLine(offset, &data)) != OK) {
+            if (err == ERROR_END_OF_STREAM) {
+                needMoreData = false;
+            } else {
+                return err;
+            }
+        }
+
+        if (needMoreData) {
+            data.trim();
+            if (data.empty()) {
+                // it's an empty line used to separate two subtitles
+                needMoreData = false;
+            }
+        }
+    }
+
+    info->textLen = *offset - info->offset;
+
+    return OK;
+}
+
+status_t TimedTextParser::getText(
+        AString *text, int64_t *startTimeUs, int64_t *endTimeUs,
+        const MediaSource::ReadOptions *options) {
+    Mutex::Autolock autoLock(mLock);
+
+    text->clear();
+
+    int64_t seekTimeUs;
+    MediaSource::ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+        int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
+        int64_t firstStartTimeUs = mTextVector.keyAt(0);
+
+        if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) {
+            return ERROR_OUT_OF_RANGE;
+        } else if (seekTimeUs < firstStartTimeUs) {
+            mIndex = 0;
+        } else {
+            // binary search
+            ssize_t low = 0;
+            ssize_t high = mTextVector.size() - 1;
+            ssize_t mid = 0;
+            int64_t currTimeUs;
+
+            while (low <= high) {
+                mid = low + (high - low)/2;
+                currTimeUs = mTextVector.keyAt(mid);
+                const int diff = currTimeUs - seekTimeUs;
+
+                if (diff == 0) {
+                    break;
+                } else if (diff < 0) {
+                    low = mid + 1;
+                } else {
+                    if ((high == mid + 1)
+                            && (seekTimeUs < mTextVector.keyAt(high))) {
+                        break;
+                    }
+                    high = mid - 1;
+                }
+            }
+
+            mIndex = mid;
+        }
+    }
+
+    TextInfo info = mTextVector.valueAt(mIndex);
+    *startTimeUs = mTextVector.keyAt(mIndex);
+    *endTimeUs = info.endTimeUs;
+    mIndex ++;
+
+    char *str = new char[info.textLen];
+    if (mDataSource->readAt(info.offset, str, info.textLen) < info.textLen) {
+        delete[] str;
+        return ERROR_IO;
+    }
+
+    text->append(str, info.textLen);
+    delete[] str;
+    return OK;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/timedtext/TimedTextParser.h b/media/libstagefright/timedtext/TimedTextParser.h
new file mode 100644
index 0000000..44774c2
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextParser.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TIMED_TEXT_PARSER_H_
+
+#define TIMED_TEXT_PARSER_H_
+
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/MediaSource.h>
+
+namespace android {
+
+class DataSource;
+
+class TimedTextParser : public RefBase {
+public:
+    TimedTextParser();
+    virtual ~TimedTextParser();
+
+    enum FileType {
+        OUT_OF_BAND_FILE_SRT = 1,
+    };
+
+    status_t getText(AString *text, int64_t *startTimeUs, int64_t *endTimeUs,
+                     const MediaSource::ReadOptions *options = NULL);
+    status_t init(const sp<DataSource> &dataSource, FileType fileType);
+    void reset();
+
+private:
+    Mutex mLock;
+
+    sp<DataSource> mDataSource;
+    off64_t mOffset;
+
+    struct TextInfo {
+        int64_t endTimeUs;
+        // the offset of the text in the original file
+        off64_t offset;
+        int textLen;
+    };
+
+    int mIndex;
+    FileType mFileType;
+
+    // the key indicated the start time of the text
+    KeyedVector<int64_t, TextInfo> mTextVector;
+
+    status_t getNextInSrtFileFormat(
+            off64_t *offset, int64_t *startTimeUs, TextInfo *info);
+    status_t readNextLine(off64_t *offset, AString *data);
+
+    status_t scanFile();
+
+    DISALLOW_EVIL_CONSTRUCTORS(TimedTextParser);
+};
+
+}  // namespace android
+
+#endif  // TIMED_TEXT_PARSER_H_
+
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
new file mode 100644
index 0000000..50bb16d
--- /dev/null
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -0,0 +1,347 @@
+ /*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TimedTextPlayer"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/Utils.h>
+#include "include/AwesomePlayer.h"
+#include "TimedTextPlayer.h"
+#include "TimedTextParser.h"
+
+namespace android {
+
+struct TimedTextEvent : public TimedEventQueue::Event {
+    TimedTextEvent(
+            TimedTextPlayer *player,
+            void (TimedTextPlayer::*method)())
+        : mPlayer(player),
+          mMethod(method) {
+    }
+
+protected:
+    virtual ~TimedTextEvent() {}
+
+    virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+        (mPlayer->*mMethod)();
+    }
+
+private:
+    TimedTextPlayer *mPlayer;
+    void (TimedTextPlayer::*mMethod)();
+
+    TimedTextEvent(const TimedTextEvent &);
+    TimedTextEvent &operator=(const TimedTextEvent &);
+};
+
+TimedTextPlayer::TimedTextPlayer(
+        AwesomePlayer *observer,
+        const wp<MediaPlayerBase> &listener,
+        TimedEventQueue *queue)
+    : mSource(NULL),
+      mOutOfBandSource(NULL),
+      mSeekTimeUs(0),
+      mStarted(false),
+      mTextEventPending(false),
+      mQueue(queue),
+      mListener(listener),
+      mObserver(observer),
+      mTextBuffer(NULL),
+      mTextParser(NULL),
+      mTextType(kNoText) {
+    mTextEvent = new TimedTextEvent(this, &TimedTextPlayer::onTextEvent);
+}
+
+TimedTextPlayer::~TimedTextPlayer() {
+    if (mStarted) {
+        reset();
+    }
+
+    mTextTrackVector.clear();
+    mTextOutOfBandVector.clear();
+}
+
+status_t TimedTextPlayer::start(uint8_t index) {
+    CHECK(!mStarted);
+
+    if (index >=
+            mTextTrackVector.size() + mTextOutOfBandVector.size()) {
+        LOGE("Incorrect text track index: %d", index);
+        return BAD_VALUE;
+    }
+
+    if (index < mTextTrackVector.size()) { // start an in-band text
+        mSource = mTextTrackVector.itemAt(index);
+
+        status_t err = mSource->start();
+
+        if (err != OK) {
+            return err;
+        }
+        mTextType = kInBandText;
+    } else { // start an out-of-band text
+        OutOfBandText text =
+            mTextOutOfBandVector.itemAt(index - mTextTrackVector.size());
+
+        mOutOfBandSource = text.source;
+        TimedTextParser::FileType fileType = text.type;
+
+        if (mTextParser == NULL) {
+            mTextParser = new TimedTextParser();
+        }
+
+        status_t err;
+        if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) {
+            return err;
+        }
+        mTextType = kOutOfBandText;
+    }
+
+    int64_t positionUs;
+    mObserver->getPosition(&positionUs);
+    seekTo(positionUs);
+
+    postTextEvent();
+
+    mStarted = true;
+
+    return OK;
+}
+
+void TimedTextPlayer::pause() {
+    CHECK(mStarted);
+
+    cancelTextEvent();
+}
+
+void TimedTextPlayer::resume() {
+    CHECK(mStarted);
+
+    postTextEvent();
+}
+
+void TimedTextPlayer::reset() {
+    CHECK(mStarted);
+
+    // send an empty text to clear the screen
+    notifyListener(MEDIA_TIMED_TEXT);
+
+    cancelTextEvent();
+
+    mSeeking = false;
+    mStarted = false;
+
+    if (mTextType == kInBandText) {
+        if (mTextBuffer != NULL) {
+            mTextBuffer->release();
+            mTextBuffer = NULL;
+        }
+
+        if (mSource != NULL) {
+            mSource->stop();
+            mSource.clear();
+            mSource = NULL;
+        }
+    } else {
+        if (mTextParser != NULL) {
+            mTextParser.clear();
+            mTextParser = NULL;
+        }
+        if (mOutOfBandSource != NULL) {
+            mOutOfBandSource.clear();
+            mOutOfBandSource = NULL;
+        }
+    }
+}
+
+status_t TimedTextPlayer::seekTo(int64_t time_us) {
+    Mutex::Autolock autoLock(mLock);
+
+    mSeeking = true;
+    mSeekTimeUs = time_us;
+
+    postTextEvent();
+
+    return OK;
+}
+
+status_t TimedTextPlayer::setTimedTextTrackIndex(int32_t index) {
+    if (index >=
+            (int)(mTextTrackVector.size() + mTextOutOfBandVector.size())) {
+        return BAD_VALUE;
+    }
+
+    if (mStarted) {
+        reset();
+    }
+
+    if (index >= 0) {
+        return start(index);
+    }
+    return OK;
+}
+
+void TimedTextPlayer::onTextEvent() {
+    Mutex::Autolock autoLock(mLock);
+
+    if (!mTextEventPending) {
+        return;
+    }
+    mTextEventPending = false;
+
+    MediaSource::ReadOptions options;
+    if (mSeeking) {
+        options.setSeekTo(mSeekTimeUs,
+                MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
+        mSeeking = false;
+
+        if (mTextType == kInBandText) {
+            if (mTextBuffer != NULL) {
+                mTextBuffer->release();
+                mTextBuffer = NULL;
+            }
+        } else {
+            mText.clear();
+        }
+
+        notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen
+    }
+
+    int64_t positionUs, timeUs;
+    mObserver->getPosition(&positionUs);
+
+    if (mTextType == kInBandText) {
+        if (mTextBuffer != NULL) {
+            uint8_t *tmp = (uint8_t *)(mTextBuffer->data());
+            size_t len = (*tmp) << 8 | (*(tmp + 1));
+
+            notifyListener(MEDIA_TIMED_TEXT,
+                           tmp + 2,
+                           len);
+
+            mTextBuffer->release();
+            mTextBuffer = NULL;
+
+        }
+
+        if (mSource->read(&mTextBuffer, &options) != OK) {
+            return;
+        }
+
+        mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs);
+    } else {
+        if (mText.size() > 0) {
+            notifyListener(MEDIA_TIMED_TEXT,
+                           mText.c_str(),
+                           mText.size());
+            mText.clear();
+        }
+
+        int64_t endTimeUs;
+        if (mTextParser->getText(
+                    &mText, &timeUs, &endTimeUs, &options) != OK) {
+            return;
+        }
+    }
+
+    //send the text now
+    if (timeUs <= positionUs + 100000ll) {
+        postTextEvent();
+    } else {
+        postTextEvent(timeUs - positionUs - 100000ll);
+    }
+}
+
+void TimedTextPlayer::postTextEvent(int64_t delayUs) {
+    if (mTextEventPending) {
+        return;
+    }
+
+    mTextEventPending = true;
+    mQueue->postEventWithDelay(mTextEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void TimedTextPlayer::cancelTextEvent() {
+    mQueue->cancelEvent(mTextEvent->eventID());
+    mTextEventPending = false;
+}
+
+void TimedTextPlayer::addTextSource(sp<MediaSource> source) {
+    Mutex::Autolock autoLock(mLock);
+    mTextTrackVector.add(source);
+}
+
+status_t TimedTextPlayer::setParameter(int key, const Parcel &request) {
+    Mutex::Autolock autoLock(mLock);
+
+    if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) {
+        String8 uri = request.readString8();
+        KeyedVector<String8, String8> headers;
+
+        // To support local subtitle file only for now
+        if (strncasecmp("file://", uri.string(), 7)) {
+            return INVALID_OPERATION;
+        }
+        sp<DataSource> dataSource =
+            DataSource::CreateFromURI(uri, &headers);
+        status_t err = dataSource->initCheck();
+
+        if (err != OK) {
+            return err;
+        }
+
+        OutOfBandText text;
+        text.source = dataSource;
+        if (uri.getPathExtension() == String8(".srt")) {
+            text.type = TimedTextParser::OUT_OF_BAND_FILE_SRT;
+        } else {
+            return ERROR_UNSUPPORTED;
+        }
+
+        mTextOutOfBandVector.add(text);
+
+        return OK;
+    }
+    return INVALID_OPERATION;
+}
+
+void TimedTextPlayer::notifyListener(
+        int msg, const void *data, size_t size) {
+    if (mListener != NULL) {
+        sp<MediaPlayerBase> listener = mListener.promote();
+
+        if (listener != NULL) {
+            if (size > 0) {
+                mData.freeData();
+                mData.write(data, size);
+
+                listener->sendEvent(msg, 0, 0, &mData);
+            } else { // send an empty timed text to clear the screen
+                listener->sendEvent(msg);
+            }
+        }
+    }
+}
+}
diff --git a/media/libstagefright/include/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
similarity index 77%
rename from media/libstagefright/include/TimedTextPlayer.h
rename to media/libstagefright/timedtext/TimedTextPlayer.h
index ac41b4f..590760b 100644
--- a/media/libstagefright/include/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -20,8 +20,10 @@
 
 #include <media/MediaPlayerInterface.h>
 #include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
 
 #include "include/TimedEventQueue.h"
+#include "TimedTextParser.h"
 
 namespace android {
 
@@ -50,11 +52,19 @@
     void addTextSource(sp<MediaSource> source);
 
     status_t setTimedTextTrackIndex(int32_t index);
+    status_t setParameter(int key, const Parcel &request);
 
 private:
+    enum TextType {
+        kNoText        = 0,
+        kInBandText    = 1,
+        kOutOfBandText = 2,
+    };
+
     Mutex mLock;
 
     sp<MediaSource> mSource;
+    sp<DataSource> mOutOfBandSource;
 
     bool mSeeking;
     int64_t mSeekTimeUs;
@@ -72,8 +82,21 @@
     MediaBuffer *mTextBuffer;
     Parcel mData;
 
+    // for in-band timed text
     Vector<sp<MediaSource> > mTextTrackVector;
 
+    // for out-of-band timed text
+    struct OutOfBandText {
+        TimedTextParser::FileType type;
+        sp<DataSource> source;
+    };
+    Vector<OutOfBandText > mTextOutOfBandVector;
+
+    sp<TimedTextParser> mTextParser;
+    AString mText;
+
+    TextType mTextType;
+
     void reset();
 
     void onTextEvent();