MediaPlayer: add listener for raw track data

Bug: 10326117

Change-Id: I2c0bdf8adc67b11f8dc633423bee66897548f181
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 1afd7f7..923c8b2 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -48,6 +48,7 @@
     MEDIA_TIMED_TEXT        = 99,
     MEDIA_ERROR             = 100,
     MEDIA_INFO              = 200,
+    MEDIA_SUBTITLE_DATA     = 201,
 };
 
 // Generic error codes for the media player framework.  Errors are fatal, the
@@ -176,6 +177,7 @@
     MEDIA_TRACK_TYPE_VIDEO = 1,
     MEDIA_TRACK_TYPE_AUDIO = 2,
     MEDIA_TRACK_TYPE_TIMEDTEXT = 3,
+    MEDIA_TRACK_TYPE_SUBTITLE = 4,
 };
 
 // ----------------------------------------------------------------------------
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 056cc0a..4323d0c 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -756,6 +756,9 @@
     case MEDIA_TIMED_TEXT:
         ALOGV("Received timed text message");
         break;
+    case MEDIA_SUBTITLE_DATA:
+        ALOGV("Received subtitle data message");
+        break;
     default:
         ALOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
         break;
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index c8901ce..d8b35d7 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -43,7 +43,8 @@
       mUID(uid),
       mFlags(0),
       mFinalResult(OK),
-      mOffset(0) {
+      mOffset(0),
+      mFetchSubtitleDataGeneration(0) {
     if (headers) {
         mExtraHeaders = *headers;
 
@@ -120,6 +121,28 @@
     return mLiveSession->getDuration(durationUs);
 }
 
+status_t NuPlayer::HTTPLiveSource::getTrackInfo(Parcel *reply) const {
+    return mLiveSession->getTrackInfo(reply);
+}
+
+status_t NuPlayer::HTTPLiveSource::selectTrack(size_t trackIndex, bool select) {
+    status_t err = mLiveSession->selectTrack(trackIndex, select);
+
+    if (err == OK) {
+        mFetchSubtitleDataGeneration++;
+        if (select) {
+            sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, id());
+            msg->setInt32("generation", mFetchSubtitleDataGeneration);
+            msg->post();
+        }
+    }
+
+    // LiveSession::selectTrack returns BAD_VALUE when selecting the currently
+    // selected track, or unselecting a non-selected track. In this case it's an
+    // no-op so we return OK.
+    return (err == OK || err == BAD_VALUE) ? OK : err;
+}
+
 status_t NuPlayer::HTTPLiveSource::seekTo(int64_t seekTimeUs) {
     return mLiveSession->seekTo(seekTimeUs);
 }
@@ -132,6 +155,39 @@
             break;
         }
 
+        case kWhatFetchSubtitleData:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mFetchSubtitleDataGeneration) {
+                // stale
+                break;
+            }
+
+            sp<ABuffer> buffer;
+            if (mLiveSession->dequeueAccessUnit(
+                    LiveSession::STREAMTYPE_SUBTITLES, &buffer) == OK) {
+                sp<AMessage> notify = dupNotify();
+                notify->setInt32("what", kWhatSubtitleData);
+                notify->setBuffer("buffer", buffer);
+                notify->post();
+
+                int64_t timeUs, baseUs, durationUs, delayUs;
+                CHECK(buffer->meta()->findInt64("baseUs", &baseUs));
+                CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+                CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
+                delayUs = baseUs + timeUs - ALooper::GetNowUs();
+
+                msg->post(delayUs > 0ll ? delayUs : 0ll);
+            } else {
+                // try again in 1 second
+                msg->post(1000000ll);
+            }
+
+            break;
+        }
+
         default:
             Source::onMessageReceived(msg);
             break;
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
index aa9434b..bcc3f8b 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
@@ -41,6 +41,8 @@
 
     virtual status_t feedMoreTSData();
     virtual status_t getDuration(int64_t *durationUs);
+    virtual status_t getTrackInfo(Parcel *reply) const;
+    virtual status_t selectTrack(size_t trackIndex, bool select);
     virtual status_t seekTo(int64_t seekTimeUs);
 
 protected:
@@ -56,6 +58,7 @@
 
     enum {
         kWhatSessionNotify,
+        kWhatFetchSubtitleData,
     };
 
     AString mURL;
@@ -67,6 +70,7 @@
     off64_t mOffset;
     sp<ALooper> mLiveLooper;
     sp<LiveSession> mLiveSession;
+    int32_t mFetchSubtitleDataGeneration;
 
     void onSessionNotify(const sp<AMessage> &msg);
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index b411f34..e1735fa 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -340,6 +340,46 @@
             break;
         }
 
+        case kWhatGetTrackInfo:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            status_t err = INVALID_OPERATION;
+            if (mSource != NULL) {
+                Parcel* reply;
+                CHECK(msg->findPointer("reply", (void**)&reply));
+                err = mSource->getTrackInfo(reply);
+            }
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatSelectTrack:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            status_t err = INVALID_OPERATION;
+            if (mSource != NULL) {
+                size_t trackIndex;
+                int32_t select;
+                CHECK(msg->findSize("trackIndex", &trackIndex));
+                CHECK(msg->findInt32("select", &select));
+                err = mSource->selectTrack(trackIndex, select);
+            }
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+
+            response->postReply(replyID);
+            break;
+        }
+
         case kWhatPollDuration:
         {
             int32_t generation;
@@ -1045,7 +1085,7 @@
     mRenderer->queueBuffer(audio, buffer, reply);
 }
 
-void NuPlayer::notifyListener(int msg, int ext1, int ext2) {
+void NuPlayer::notifyListener(int msg, int ext1, int ext2, const Parcel *in) {
     if (mDriver == NULL) {
         return;
     }
@@ -1056,7 +1096,7 @@
         return;
     }
 
-    driver->notifyListener(msg, ext1, ext2);
+    driver->notifyListener(msg, ext1, ext2, in);
 }
 
 void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
@@ -1132,6 +1172,26 @@
     return OK;
 }
 
+status_t NuPlayer::getTrackInfo(Parcel* reply) const {
+    sp<AMessage> msg = new AMessage(kWhatGetTrackInfo, id());
+    msg->setPointer("reply", reply);
+
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+    return err;
+}
+
+status_t NuPlayer::selectTrack(size_t trackIndex, bool select) {
+    sp<AMessage> msg = new AMessage(kWhatSelectTrack, id());
+    msg->setSize("trackIndex", trackIndex);
+    msg->setInt32("select", select);
+
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+
+    return err;
+}
+
 void NuPlayer::schedulePollDuration() {
     sp<AMessage> msg = new AMessage(kWhatPollDuration, id());
     msg->setInt32("generation", mPollDurationGeneration);
@@ -1371,6 +1431,29 @@
             break;
         }
 
+        case Source::kWhatSubtitleData:
+        {
+            sp<ABuffer> buffer;
+            CHECK(msg->findBuffer("buffer", &buffer));
+
+            int32_t trackIndex;
+            int64_t timeUs, durationUs;
+            CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex));
+            CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+            CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
+
+            Parcel in;
+            in.writeInt32(trackIndex);
+            in.writeInt64(timeUs);
+            in.writeInt64(durationUs);
+            in.writeInt32(buffer->size());
+            in.writeInt32(buffer->size());
+            in.write(buffer->data(), buffer->size());
+
+            notifyListener(MEDIA_SUBTITLE_DATA, 0, 0, &in);
+            break;
+        }
+
         case Source::kWhatQueueDecoderShutdown:
         {
             int32_t audio, video;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 8b6c8c1..13350f3 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -60,6 +60,8 @@
     void seekToAsync(int64_t seekTimeUs);
 
     status_t setVideoScalingMode(int32_t mode);
+    status_t getTrackInfo(Parcel* reply) const;
+    status_t selectTrack(size_t trackIndex, bool select);
 
 protected:
     virtual ~NuPlayer();
@@ -101,6 +103,8 @@
         kWhatResume                     = 'rsme',
         kWhatPollDuration               = 'polD',
         kWhatSourceNotify               = 'srcN',
+        kWhatGetTrackInfo               = 'gTrI',
+        kWhatSelectTrack                = 'selT',
     };
 
     wp<NuPlayerDriver> mDriver;
@@ -157,7 +161,7 @@
     status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg);
     void renderBuffer(bool audio, const sp<AMessage> &msg);
 
-    void notifyListener(int msg, int ext1, int ext2);
+    void notifyListener(int msg, int ext1, int ext2, const Parcel *in = NULL);
 
     void finishFlushIfPossible();
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index cf0373c..47834fd 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -392,6 +392,23 @@
             return mPlayer->setVideoScalingMode(mode);
         }
 
+        case INVOKE_ID_GET_TRACK_INFO:
+        {
+            return mPlayer->getTrackInfo(reply);
+        }
+
+        case INVOKE_ID_SELECT_TRACK:
+        {
+            int trackIndex = request.readInt32();
+            return mPlayer->selectTrack(trackIndex, true /* select */);
+        }
+
+        case INVOKE_ID_UNSELECT_TRACK:
+        {
+            int trackIndex = request.readInt32();
+            return mPlayer->selectTrack(trackIndex, false /* select */);
+        }
+
         default:
         {
             return INVALID_OPERATION;
@@ -495,12 +512,13 @@
     return OK;
 }
 
-void NuPlayerDriver::notifyListener(int msg, int ext1, int ext2) {
+void NuPlayerDriver::notifyListener(
+        int msg, int ext1, int ext2, const Parcel *in) {
     if (msg == MEDIA_PLAYBACK_COMPLETE || msg == MEDIA_ERROR) {
         mAtEOS = true;
     }
 
-    sendEvent(msg, ext1, ext2);
+    sendEvent(msg, ext1, ext2, in);
 }
 
 void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 5df0cfb..99f72a6 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -69,7 +69,7 @@
     void notifyPosition(int64_t positionUs);
     void notifySeekComplete();
     void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped);
-    void notifyListener(int msg, int ext1 = 0, int ext2 = 0);
+    void notifyListener(int msg, int ext1 = 0, int ext2 = 0, const Parcel *in = NULL);
     void notifyFlagsChanged(uint32_t flags);
 
 protected:
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
index 81ffd21..e50533a 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
@@ -42,6 +42,7 @@
         kWhatVideoSizeChanged,
         kWhatBufferingStart,
         kWhatBufferingEnd,
+        kWhatSubtitleData,
         kWhatQueueDecoderShutdown,
     };
 
@@ -71,6 +72,14 @@
         return INVALID_OPERATION;
     }
 
+    virtual status_t getTrackInfo(Parcel* reply) const {
+        return INVALID_OPERATION;
+    }
+
+    virtual status_t selectTrack(size_t trackIndex, bool select) {
+        return INVALID_OPERATION;
+    }
+
     virtual status_t seekTo(int64_t seekTimeUs) {
         return INVALID_OPERATION;
     }
diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk
index 85bd492..f3529f9 100644
--- a/media/libstagefright/httplive/Android.mk
+++ b/media/libstagefright/httplive/Android.mk
@@ -14,6 +14,7 @@
 	$(TOP)/external/openssl/include
 
 LOCAL_SHARED_LIBRARIES := \
+        libbinder \
         libcrypto \
         libcutils \
         libmedia \
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index e91c60b..bd12ddc 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -59,6 +59,7 @@
       mStreamMask(0),
       mCheckBandwidthGeneration(0),
       mLastDequeuedTimeUs(0ll),
+      mRealTimeBaseUs(0ll),
       mReconfigurationInProgress(false),
       mDisconnectReplyID(0) {
     if (mUIDValid) {
@@ -122,11 +123,18 @@
               type,
               extra == NULL ? "NULL" : extra->debugString().c_str());
     } else if (err == OK) {
-        int64_t timeUs;
-        CHECK((*accessUnit)->meta()->findInt64("timeUs",  &timeUs));
-        ALOGV("[%s] read buffer at time %lld us", streamStr, timeUs);
+        if (stream == STREAMTYPE_AUDIO || stream == STREAMTYPE_VIDEO) {
+            int64_t timeUs;
+            CHECK((*accessUnit)->meta()->findInt64("timeUs",  &timeUs));
+            ALOGV("[%s] read buffer at time %lld us", streamStr, timeUs);
 
-        mLastDequeuedTimeUs = timeUs;
+            mLastDequeuedTimeUs = timeUs;
+            mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
+        } else if (stream == STREAMTYPE_SUBTITLES) {
+            (*accessUnit)->meta()->setInt32(
+                    "trackIndex", mPlaylist->getSelectedIndex());
+            (*accessUnit)->meta()->setInt64("baseUs", mRealTimeBaseUs);
+        }
     } else {
         ALOGI("[%s] encountered error %d", streamStr, err);
     }
@@ -325,6 +333,12 @@
             break;
         }
 
+        case kWhatChangeConfiguration:
+        {
+            onChangeConfiguration(msg);
+            break;
+        }
+
         case kWhatChangeConfiguration2:
         {
             onChangeConfiguration2(msg);
@@ -438,7 +452,8 @@
         mBandwidthItems.push(item);
     }
 
-    changeConfiguration(0ll /* timeUs */, initialBandwidthIndex);
+    changeConfiguration(
+            0ll /* timeUs */, initialBandwidthIndex, true /* pickTrack */);
 }
 
 void LiveSession::finishDisconnect() {
@@ -783,16 +798,31 @@
     return false;
 }
 
-void LiveSession::changeConfiguration(int64_t timeUs, size_t bandwidthIndex) {
+status_t LiveSession::getTrackInfo(Parcel *reply) const {
+    return mPlaylist->getTrackInfo(reply);
+}
+
+status_t LiveSession::selectTrack(size_t index, bool select) {
+    status_t err = mPlaylist->selectTrack(index, select);
+    if (err == OK) {
+        (new AMessage(kWhatChangeConfiguration, id()))->post();
+    }
+    return err;
+}
+
+void LiveSession::changeConfiguration(
+        int64_t timeUs, size_t bandwidthIndex, bool pickTrack) {
     CHECK(!mReconfigurationInProgress);
     mReconfigurationInProgress = true;
 
     mPrevBandwidthIndex = bandwidthIndex;
 
-    ALOGV("changeConfiguration => timeUs:%lld us, bwIndex:%d",
-          timeUs, bandwidthIndex);
+    ALOGV("changeConfiguration => timeUs:%lld us, bwIndex:%d, pickTrack:%d",
+          timeUs, bandwidthIndex, pickTrack);
 
-    mPlaylist->pickRandomMediaItems();
+    if (pickTrack) {
+        mPlaylist->pickRandomMediaItems();
+    }
 
     CHECK_LT(bandwidthIndex, mBandwidthItems.size());
     const BandwidthItem &item = mBandwidthItems.itemAt(bandwidthIndex);
@@ -862,6 +892,14 @@
     }
 }
 
+void LiveSession::onChangeConfiguration(const sp<AMessage> &msg) {
+    if (!mReconfigurationInProgress) {
+        changeConfiguration(-1ll /* timeUs */, getBandwidthIndex());
+    } else {
+        msg->post(1000000ll); // retry in 1 sec
+    }
+}
+
 void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
     mContinuation.clear();
 
@@ -948,6 +986,7 @@
     if (timeUs < 0ll) {
         timeUs = mLastDequeuedTimeUs;
     }
+    mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
 
     mStreamMask = streamMask;
     mAudioURI = audioURI;
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index b134725..99b480a8 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -31,6 +31,7 @@
 struct LiveDataSource;
 struct M3UParser;
 struct PlaylistFetcher;
+struct Parcel;
 
 struct LiveSession : public AHandler {
     enum Flags {
@@ -60,6 +61,8 @@
     status_t seekTo(int64_t timeUs);
 
     status_t getDuration(int64_t *durationUs) const;
+    status_t getTrackInfo(Parcel *reply) const;
+    status_t selectTrack(size_t index, bool select);
 
     bool isSeekable() const;
     bool hasDynamicDuration() const;
@@ -85,6 +88,7 @@
         kWhatSeek                       = 'seek',
         kWhatFetcherNotify              = 'notf',
         kWhatCheckBandwidth             = 'bndw',
+        kWhatChangeConfiguration        = 'chC0',
         kWhatChangeConfiguration2       = 'chC2',
         kWhatChangeConfiguration3       = 'chC3',
         kWhatFinishDisconnect2          = 'fin2',
@@ -130,6 +134,7 @@
     sp<AMessage> mContinuation;
 
     int64_t mLastDequeuedTimeUs;
+    int64_t mRealTimeBaseUs;
 
     bool mReconfigurationInProgress;
     uint32_t mDisconnectReplyID;
@@ -151,7 +156,9 @@
 
     static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
 
-    void changeConfiguration(int64_t timeUs, size_t bandwidthIndex);
+    void changeConfiguration(
+            int64_t timeUs, size_t bandwidthIndex, bool pickTrack = false);
+    void onChangeConfiguration(const sp<AMessage> &msg);
     void onChangeConfiguration2(const sp<AMessage> &msg);
     void onChangeConfiguration3(const sp<AMessage> &msg);
 
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index be66252..bc6d629 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -19,11 +19,12 @@
 #include <utils/Log.h>
 
 #include "M3UParser.h"
-
+#include <binder/Parcel.h>
 #include <cutils/properties.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/mediaplayer.h>
 
 namespace android {
 
@@ -55,6 +56,9 @@
     bool getActiveURI(AString *uri) const;
 
     void pickRandomMediaItems();
+    status_t selectTrack(size_t index, bool select);
+    void getTrackInfo(Parcel* reply) const;
+    size_t countTracks() const;
 
 protected:
     virtual ~MediaGroup();
@@ -150,6 +154,59 @@
 #endif
 }
 
+status_t M3UParser::MediaGroup::selectTrack(size_t index, bool select) {
+    if (mType != TYPE_SUBS) {
+        ALOGE("only select subtitile tracks for now!");
+        return INVALID_OPERATION;
+    }
+
+    if (select) {
+        if (index >= mMediaItems.size()) {
+            ALOGE("track %d does not exist", index);
+            return INVALID_OPERATION;
+        }
+        if (mSelectedIndex == index) {
+            ALOGE("track %d already selected", index);
+            return BAD_VALUE;
+        }
+        ALOGV("selected track %d", index);
+        mSelectedIndex = index;
+    } else {
+        if (mSelectedIndex != index) {
+            ALOGE("track %d is not selected", index);
+            return BAD_VALUE;
+        }
+        ALOGV("unselected track %d", index);
+        mSelectedIndex = -1;
+    }
+
+    return OK;
+}
+
+void M3UParser::MediaGroup::getTrackInfo(Parcel* reply) const {
+    for (size_t i = 0; i < mMediaItems.size(); ++i) {
+        reply->writeInt32(2); // 2 fields
+
+        if (mType == TYPE_AUDIO) {
+            reply->writeInt32(MEDIA_TRACK_TYPE_AUDIO);
+        } else if (mType == TYPE_VIDEO) {
+            reply->writeInt32(MEDIA_TRACK_TYPE_VIDEO);
+        } else if (mType == TYPE_SUBS) {
+            reply->writeInt32(MEDIA_TRACK_TYPE_SUBTITLE);
+        } else {
+            reply->writeInt32(MEDIA_TRACK_TYPE_UNKNOWN);
+        }
+
+        const Media &item = mMediaItems.itemAt(i);
+        const char *lang = item.mLanguage.empty() ? "und" : item.mLanguage.c_str();
+        reply->writeString16(String16(lang));
+    }
+}
+
+size_t M3UParser::MediaGroup::countTracks() const {
+    return mMediaItems.size();
+}
+
 bool M3UParser::MediaGroup::getActiveURI(AString *uri) const {
     for (size_t i = 0; i < mMediaItems.size(); ++i) {
         if (mSelectedIndex >= 0 && i == (size_t)mSelectedIndex) {
@@ -172,7 +229,8 @@
       mIsExtM3U(false),
       mIsVariantPlaylist(false),
       mIsComplete(false),
-      mIsEvent(false) {
+      mIsEvent(false),
+      mSelectedIndex(-1) {
     mInitCheck = parse(data, size);
 }
 
@@ -237,6 +295,39 @@
     }
 }
 
+status_t M3UParser::selectTrack(size_t index, bool select) {
+    for (size_t i = 0, ii = index; i < mMediaGroups.size(); ++i) {
+        sp<MediaGroup> group = mMediaGroups.valueAt(i);
+        size_t tracks = group->countTracks();
+        if (ii < tracks) {
+            status_t err = group->selectTrack(ii, select);
+            if (err == OK) {
+                mSelectedIndex = select ? index : -1;
+            }
+            return err;
+        }
+        ii -= tracks;
+    }
+    return INVALID_OPERATION;
+}
+
+status_t M3UParser::getTrackInfo(Parcel* reply) const {
+    size_t trackCount = 0;
+    for (size_t i = 0; i < mMediaGroups.size(); ++i) {
+        trackCount += mMediaGroups.valueAt(i)->countTracks();
+    }
+    reply->writeInt32(trackCount);
+
+    for (size_t i = 0; i < mMediaGroups.size(); ++i) {
+        mMediaGroups.valueAt(i)->getTrackInfo(reply);
+    }
+    return OK;
+}
+
+ssize_t M3UParser::getSelectedIndex() const {
+    return mSelectedIndex;
+}
+
 bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
     if (!mIsVariantPlaylist) {
         *uri = mBaseURI;
diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index abea286..5248004 100644
--- a/media/libstagefright/httplive/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -41,6 +41,9 @@
     bool itemAt(size_t index, AString *uri, sp<AMessage> *meta = NULL);
 
     void pickRandomMediaItems();
+    status_t selectTrack(size_t index, bool select);
+    status_t getTrackInfo(Parcel* reply) const;
+    ssize_t getSelectedIndex() const;
 
     bool getAudioURI(size_t index, AString *uri) const;
     bool getVideoURI(size_t index, AString *uri) const;
@@ -67,6 +70,7 @@
 
     sp<AMessage> mMeta;
     Vector<Item> mItems;
+    ssize_t mSelectedIndex;
 
     // Media groups keyed by group ID.
     KeyedVector<AString, sp<MediaGroup> > mMediaGroups;
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index 8ae70b7..973b779 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -462,7 +462,11 @@
         sp<AnotherPacketSource> packetSource =
             mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
 
-        downloadMore = packetSource->hasBufferAvailable(&finalResult);
+        int64_t bufferedDurationUs =
+                packetSource->getBufferedDurationUs(&finalResult);
+
+        downloadMore = (bufferedDurationUs < kMinBufferedDurationUs);
+        finalResult = OK;
     } else {
         bool first = true;
         int64_t minBufferedDurationUs = 0ll;
@@ -659,7 +663,7 @@
         }
     }
 
-    err = extractAndQueueAccessUnits(buffer);
+    err = extractAndQueueAccessUnits(buffer, itemMeta);
 
     if (err != OK) {
         notifyError(err);
@@ -706,7 +710,7 @@
 }
 
 status_t PlaylistFetcher::extractAndQueueAccessUnits(
-        const sp<ABuffer> &buffer) {
+        const sp<ABuffer> &buffer, const sp<AMessage> &itemMeta) {
     if (buffer->size() > 0 && buffer->data()[0] == 0x47) {
         // Let's assume this is an MPEG2 transport stream.
 
@@ -802,7 +806,10 @@
         const sp<AnotherPacketSource> packetSource =
             mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
 
-        buffer->meta()->setInt64("timeUs", 0ll);
+        int64_t durationUs;
+        CHECK(itemMeta->findInt64("durationUs", &durationUs));
+        buffer->meta()->setInt64("timeUs", getSegmentStartTimeUs(mSeqNumber));
+        buffer->meta()->setInt64("durationUs", durationUs);
 
         packetSource->queueAccessUnit(buffer);
         return OK;
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
index 5a2b901..1648e02 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.h
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -135,7 +135,8 @@
     void onMonitorQueue();
     void onDownloadNext();
 
-    status_t extractAndQueueAccessUnits(const sp<ABuffer> &buffer);
+    status_t extractAndQueueAccessUnits(
+            const sp<ABuffer> &buffer, const sp<AMessage> &itemMeta);
 
     void notifyError(status_t err);