OSDN Git Service

New HLS implementation supporting independent stream sources, audio-only streams
authorAndreas Huber <andih@google.com>
Tue, 15 Jan 2013 17:04:18 +0000 (09:04 -0800)
committerAndreas Huber <andih@google.com>
Fri, 31 May 2013 17:30:45 +0000 (10:30 -0700)
and more.

Change-Id: Icfc45a0100243b2f7a14a9e65696be45b67d6495

24 files changed:
cmds/stagefright/stagefright.cpp
media/libmediaplayerservice/Android.mk
media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
media/libmediaplayerservice/nuplayer/NuPlayer.cpp
media/libmediaplayerservice/nuplayer/NuPlayer.h
media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
media/libmediaplayerservice/nuplayer/NuPlayerSource.h
media/libstagefright/ACodec.cpp
media/libstagefright/Android.mk
media/libstagefright/foundation/AHierarchicalStateMachine.cpp
media/libstagefright/httplive/Android.mk
media/libstagefright/httplive/LiveSession.cpp
media/libstagefright/httplive/LiveSession.h [moved from media/libstagefright/include/LiveSession.h with 53% similarity]
media/libstagefright/httplive/M3UParser.cpp
media/libstagefright/httplive/M3UParser.h [moved from media/libstagefright/include/M3UParser.h with 81% similarity]
media/libstagefright/httplive/PlaylistFetcher.cpp [new file with mode: 0644]
media/libstagefright/httplive/PlaylistFetcher.h [new file with mode: 0644]
media/libstagefright/id3/ID3.cpp
media/libstagefright/include/ID3.h
media/libstagefright/include/MPEG2TSExtractor.h
media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
media/libstagefright/mpeg2ts/AnotherPacketSource.h
media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp

index 115b07c..924cf6d 100644 (file)
@@ -30,8 +30,6 @@
 #include <binder/ProcessState.h>
 #include <media/IMediaPlayerService.h>
 #include <media/stagefright/foundation/ALooper.h>
-#include <media/stagefright/foundation/AMessage.h>
-#include "include/LiveSession.h"
 #include "include/NuCachedSource2.h"
 #include <media/stagefright/AudioPlayer.h>
 #include <media/stagefright/DataSource.h>
@@ -678,7 +676,6 @@ int main(int argc, char **argv) {
     gDisplayHistogram = false;
 
     sp<ALooper> looper;
-    sp<LiveSession> liveSession;
 
     int res;
     while ((res = getopt(argc, argv, "han:lm:b:ptsrow:kxSTd:D:")) >= 0) {
@@ -961,9 +958,7 @@ int main(int argc, char **argv) {
 
         sp<DataSource> dataSource = DataSource::CreateFromURI(filename);
 
-        if (strncasecmp(filename, "sine:", 5)
-                && strncasecmp(filename, "httplive://", 11)
-                && dataSource == NULL) {
+        if (strncasecmp(filename, "sine:", 5) && dataSource == NULL) {
             fprintf(stderr, "Unable to create data source.\n");
             return 1;
         }
@@ -995,44 +990,21 @@ int main(int argc, char **argv) {
                 mediaSources.push(mediaSource);
             }
         } else {
-            sp<MediaExtractor> extractor;
+            sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
 
-            if (!strncasecmp("httplive://", filename, 11)) {
-                String8 uri("http://");
-                uri.append(filename + 11);
-
-                if (looper == NULL) {
-                    looper = new ALooper;
-                    looper->start();
-                }
-                liveSession = new LiveSession(NULL /* notify */);
-                looper->registerHandler(liveSession);
-
-                liveSession->connect(uri.string());
-                dataSource = liveSession->getDataSource();
-
-                extractor =
-                    MediaExtractor::Create(
-                            dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
-
-                syncInfoPresent = false;
-            } else {
-                extractor = MediaExtractor::Create(dataSource);
-
-                if (extractor == NULL) {
-                    fprintf(stderr, "could not create extractor.\n");
-                    return -1;
-                }
+            if (extractor == NULL) {
+                fprintf(stderr, "could not create extractor.\n");
+                return -1;
+            }
 
-                sp<MetaData> meta = extractor->getMetaData();
+            sp<MetaData> meta = extractor->getMetaData();
 
-                if (meta != NULL) {
-                    const char *mime;
-                    CHECK(meta->findCString(kKeyMIMEType, &mime));
+            if (meta != NULL) {
+                const char *mime;
+                CHECK(meta->findCString(kKeyMIMEType, &mime));
 
-                    if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
-                        syncInfoPresent = false;
-                    }
+                if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
+                    syncInfoPresent = false;
                 }
             }
 
index d87bc7f..8f21632 100644 (file)
@@ -34,6 +34,7 @@ LOCAL_SHARED_LIBRARIES :=       \
     libsonivox                  \
     libstagefright              \
     libstagefright_foundation   \
+    libstagefright_httplive     \
     libstagefright_omx          \
     libstagefright_wfd          \
     libutils                    \
index 655ee55..c8901ce 100644 (file)
@@ -20,7 +20,6 @@
 
 #include "HTTPLiveSource.h"
 
-#include "ATSParser.h"
 #include "AnotherPacketSource.h"
 #include "LiveDataSource.h"
 #include "LiveSession.h"
@@ -62,7 +61,10 @@ NuPlayer::HTTPLiveSource::HTTPLiveSource(
 NuPlayer::HTTPLiveSource::~HTTPLiveSource() {
     if (mLiveSession != NULL) {
         mLiveSession->disconnect();
+        mLiveSession.clear();
+
         mLiveLooper->stop();
+        mLiveLooper.clear();
     }
 }
 
@@ -76,112 +78,42 @@ void NuPlayer::HTTPLiveSource::prepareAsync() {
     mLiveSession = new LiveSession(
             notify,
             (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0,
-            mUIDValid, mUID);
+            mUIDValid,
+            mUID);
 
     mLiveLooper->registerHandler(mLiveSession);
 
-    mLiveSession->connect(
+    mLiveSession->connectAsync(
             mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
-
-    mTSParser = new ATSParser;
 }
 
 void NuPlayer::HTTPLiveSource::start() {
 }
 
-sp<MetaData> NuPlayer::HTTPLiveSource::getFormatMeta(bool audio) {
-    ATSParser::SourceType type =
-        audio ? ATSParser::AUDIO : ATSParser::VIDEO;
-
-    sp<AnotherPacketSource> source =
-        static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
+sp<AMessage> NuPlayer::HTTPLiveSource::getFormat(bool audio) {
+    sp<AMessage> format;
+    status_t err = mLiveSession->getStreamFormat(
+            audio ? LiveSession::STREAMTYPE_AUDIO
+                  : LiveSession::STREAMTYPE_VIDEO,
+            &format);
 
-    if (source == NULL) {
+    if (err != OK) {
         return NULL;
     }
 
-    return source->getFormat();
+    return format;
 }
 
 status_t NuPlayer::HTTPLiveSource::feedMoreTSData() {
-    if (mFinalResult != OK) {
-        return mFinalResult;
-    }
-
-    sp<LiveDataSource> source =
-        static_cast<LiveDataSource *>(mLiveSession->getDataSource().get());
-
-    for (int32_t i = 0; i < 50; ++i) {
-        char buffer[188];
-        ssize_t n = source->readAtNonBlocking(mOffset, buffer, sizeof(buffer));
-
-        if (n == -EWOULDBLOCK) {
-            break;
-        } else if (n < 0) {
-            if (n != ERROR_END_OF_STREAM) {
-                ALOGI("input data EOS reached, error %ld", n);
-            } else {
-                ALOGI("input data EOS reached.");
-            }
-            mTSParser->signalEOS(n);
-            mFinalResult = n;
-            break;
-        } else {
-            if (buffer[0] == 0x00) {
-                // XXX legacy
-
-                uint8_t type = buffer[1];
-
-                sp<AMessage> extra = new AMessage;
-
-                if (type & 2) {
-                    int64_t mediaTimeUs;
-                    memcpy(&mediaTimeUs, &buffer[2], sizeof(mediaTimeUs));
-
-                    extra->setInt64(IStreamListener::kKeyMediaTimeUs, mediaTimeUs);
-                }
-
-                mTSParser->signalDiscontinuity(
-                        ((type & 1) == 0)
-                            ? ATSParser::DISCONTINUITY_SEEK
-                            : ATSParser::DISCONTINUITY_FORMATCHANGE,
-                        extra);
-            } else {
-                status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer));
-
-                if (err != OK) {
-                    ALOGE("TS Parser returned error %d", err);
-                    mTSParser->signalEOS(err);
-                    mFinalResult = err;
-                    break;
-                }
-            }
-
-            mOffset += n;
-        }
-    }
-
     return OK;
 }
 
 status_t NuPlayer::HTTPLiveSource::dequeueAccessUnit(
         bool audio, sp<ABuffer> *accessUnit) {
-    ATSParser::SourceType type =
-        audio ? ATSParser::AUDIO : ATSParser::VIDEO;
-
-    sp<AnotherPacketSource> source =
-        static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get());
-
-    if (source == NULL) {
-        return -EWOULDBLOCK;
-    }
-
-    status_t finalResult;
-    if (!source->hasBufferAvailable(&finalResult)) {
-        return finalResult == OK ? -EWOULDBLOCK : finalResult;
-    }
-
-    return source->dequeueAccessUnit(accessUnit);
+    return mLiveSession->dequeueAccessUnit(
+            audio ? LiveSession::STREAMTYPE_AUDIO
+                  : LiveSession::STREAMTYPE_VIDEO,
+            accessUnit);
 }
 
 status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) {
@@ -189,15 +121,7 @@ status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) {
 }
 
 status_t NuPlayer::HTTPLiveSource::seekTo(int64_t seekTimeUs) {
-    // We need to make sure we're not seeking until we have seen the very first
-    // PTS timestamp in the whole stream (from the beginning of the stream).
-    while (!mTSParser->PTSTimeDeltaEstablished() && feedMoreTSData() == OK) {
-        usleep(100000);
-    }
-
-    mLiveSession->seekTo(seekTimeUs);
-
-    return OK;
+    return mLiveSession->seekTo(seekTimeUs);
 }
 
 void NuPlayer::HTTPLiveSource::onMessageReceived(const sp<AMessage> &msg) {
@@ -249,6 +173,32 @@ void NuPlayer::HTTPLiveSource::onSessionNotify(const sp<AMessage> &msg) {
             break;
         }
 
+        case LiveSession::kWhatStreamsChanged:
+        {
+            uint32_t changedMask;
+            CHECK(msg->findInt32(
+                        "changedMask", (int32_t *)&changedMask));
+
+            bool audio = changedMask & LiveSession::STREAMTYPE_AUDIO;
+            bool video = changedMask & LiveSession::STREAMTYPE_VIDEO;
+
+            sp<AMessage> reply;
+            CHECK(msg->findMessage("reply", &reply));
+
+            sp<AMessage> notify = dupNotify();
+            notify->setInt32("what", kWhatQueueDecoderShutdown);
+            notify->setInt32("audio", audio);
+            notify->setInt32("video", video);
+            notify->setMessage("reply", reply);
+            notify->post();
+            break;
+        }
+
+        case LiveSession::kWhatError:
+        {
+            break;
+        }
+
         default:
             TRESPASS();
     }
index 067d1da..aa9434b 100644 (file)
@@ -23,7 +23,6 @@
 
 namespace android {
 
-struct ATSParser;
 struct LiveSession;
 
 struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
@@ -37,18 +36,16 @@ struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
     virtual void prepareAsync();
     virtual void start();
 
-    virtual status_t feedMoreTSData();
-
     virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+    virtual sp<AMessage> getFormat(bool audio);
 
+    virtual status_t feedMoreTSData();
     virtual status_t getDuration(int64_t *durationUs);
     virtual status_t seekTo(int64_t seekTimeUs);
 
 protected:
     virtual ~HTTPLiveSource();
 
-    virtual sp<MetaData> getFormatMeta(bool audio);
-
     virtual void onMessageReceived(const sp<AMessage> &msg);
 
 private:
@@ -70,7 +67,6 @@ private:
     off64_t mOffset;
     sp<ALooper> mLiveLooper;
     sp<LiveSession> mLiveSession;
-    sp<ATSParser> mTSParser;
 
     void onSessionNotify(const sp<AMessage> &msg);
 
index b89b1c8..7e81035 100644 (file)
@@ -89,6 +89,38 @@ private:
     DISALLOW_EVIL_CONSTRUCTORS(SetSurfaceAction);
 };
 
+struct NuPlayer::ShutdownDecoderAction : public Action {
+    ShutdownDecoderAction(bool audio, bool video)
+        : mAudio(audio),
+          mVideo(video) {
+    }
+
+    virtual void execute(NuPlayer *player) {
+        player->performDecoderShutdown(mAudio, mVideo);
+    }
+
+private:
+    bool mAudio;
+    bool mVideo;
+
+    DISALLOW_EVIL_CONSTRUCTORS(ShutdownDecoderAction);
+};
+
+struct NuPlayer::PostMessageAction : public Action {
+    PostMessageAction(const sp<AMessage> &msg)
+        : mMessage(msg) {
+    }
+
+    virtual void execute(NuPlayer *) {
+        mMessage->post();
+    }
+
+private:
+    sp<AMessage> mMessage;
+
+    DISALLOW_EVIL_CONSTRUCTORS(PostMessageAction);
+};
+
 // Use this if there's no state necessary to save in order to execute
 // the action.
 struct NuPlayer::SimpleAction : public Action {
@@ -335,7 +367,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
             ALOGV("kWhatSetVideoNativeWindow");
 
             mDeferredActions.push_back(
-                    new SimpleAction(&NuPlayer::performDecoderShutdown));
+                    new ShutdownDecoderAction(
+                        false /* audio */, true /* video */));
 
             sp<RefBase> obj;
             CHECK(msg->findObject("native-window", &obj));
@@ -712,7 +745,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
             ALOGV("kWhatReset");
 
             mDeferredActions.push_back(
-                    new SimpleAction(&NuPlayer::performDecoderShutdown));
+                    new ShutdownDecoderAction(
+                        true /* audio */, true /* video */));
 
             mDeferredActions.push_back(
                     new SimpleAction(&NuPlayer::performReset));
@@ -1023,6 +1057,9 @@ void NuPlayer::notifyListener(int msg, int ext1, int ext2) {
 }
 
 void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
+    ALOGV("[%s] flushDecoder needShutdown=%d",
+          audio ? "audio" : "video", needShutdown);
+
     if ((audio && mAudioDecoder == NULL) || (!audio && mVideoDecoder == NULL)) {
         ALOGI("flushDecoder %s without decoder present",
              audio ? "audio" : "video");
@@ -1173,20 +1210,29 @@ void NuPlayer::performDecoderFlush() {
     }
 }
 
-void NuPlayer::performDecoderShutdown() {
-    ALOGV("performDecoderShutdown");
+void NuPlayer::performDecoderShutdown(bool audio, bool video) {
+    ALOGV("performDecoderShutdown audio=%d, video=%d", audio, video);
 
-    if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
+    if ((!audio || mAudioDecoder == NULL)
+            && (!video || mVideoDecoder == NULL)) {
         return;
     }
 
     mTimeDiscontinuityPending = true;
 
-    if (mAudioDecoder != NULL) {
+    if (mFlushingAudio == NONE && (!audio || mAudioDecoder == NULL)) {
+        mFlushingAudio = FLUSHED;
+    }
+
+    if (mFlushingVideo == NONE && (!video || mVideoDecoder == NULL)) {
+        mFlushingVideo = FLUSHED;
+    }
+
+    if (audio && mAudioDecoder != NULL) {
         flushDecoder(true /* audio */, true /* needShutdown */);
     }
 
-    if (mVideoDecoder != NULL) {
+    if (video && mVideoDecoder != NULL) {
         flushDecoder(false /* audio */, true /* needShutdown */);
     }
 }
@@ -1322,6 +1368,19 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) {
             break;
         }
 
+        case Source::kWhatQueueDecoderShutdown:
+        {
+            int32_t audio, video;
+            CHECK(msg->findInt32("audio", &audio));
+            CHECK(msg->findInt32("video", &video));
+
+            sp<AMessage> reply;
+            CHECK(msg->findMessage("reply", &reply));
+
+            queueDecoderShutdown(audio, video, reply);
+            break;
+        }
+
         default:
             TRESPASS();
     }
@@ -1355,4 +1414,19 @@ void NuPlayer::Source::onMessageReceived(const sp<AMessage> &msg) {
     TRESPASS();
 }
 
+void NuPlayer::queueDecoderShutdown(
+        bool audio, bool video, const sp<AMessage> &reply) {
+    ALOGI("queueDecoderShutdown audio=%d, video=%d", audio, video);
+
+    mDeferredActions.push_back(
+            new ShutdownDecoderAction(audio, video));
+
+    mDeferredActions.push_back(
+            new SimpleAction(&NuPlayer::performScanSources));
+
+    mDeferredActions.push_back(new PostMessageAction(reply));
+
+    processDeferredActions();
+}
+
 }  // namespace android
index 50d0462..8b6c8c1 100644 (file)
@@ -80,6 +80,8 @@ private:
     struct Action;
     struct SeekAction;
     struct SetSurfaceAction;
+    struct ShutdownDecoderAction;
+    struct PostMessageAction;
     struct SimpleAction;
 
     enum {
@@ -172,13 +174,16 @@ private:
 
     void performSeek(int64_t seekTimeUs);
     void performDecoderFlush();
-    void performDecoderShutdown();
+    void performDecoderShutdown(bool audio, bool video);
     void performReset();
     void performScanSources();
     void performSetSurface(const sp<NativeWindowWrapper> &wrapper);
 
     void onSourceNotify(const sp<AMessage> &msg);
 
+    void queueDecoderShutdown(
+            bool audio, bool video, const sp<AMessage> &reply);
+
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
 };
 
index 404b56f..b543d9d 100644 (file)
@@ -95,11 +95,11 @@ void NuPlayer::Renderer::flush(bool audio) {
 }
 
 void NuPlayer::Renderer::signalTimeDiscontinuity() {
-    CHECK(mAudioQueue.empty());
-    CHECK(mVideoQueue.empty());
+    // CHECK(mAudioQueue.empty());
+    // CHECK(mVideoQueue.empty());
     mAnchorTimeMediaUs = -1;
     mAnchorTimeRealUs = -1;
-    mSyncQueues = mHasAudio && mHasVideo;
+    mSyncQueues = false;
 }
 
 void NuPlayer::Renderer::pause() {
index 1cbf575..81ffd21 100644 (file)
@@ -42,6 +42,7 @@ struct NuPlayer::Source : public AHandler {
         kWhatVideoSizeChanged,
         kWhatBufferingStart,
         kWhatBufferingEnd,
+        kWhatQueueDecoderShutdown,
     };
 
     // The provides message is used to notify the player about various
index d3ac734..a60c320 100644 (file)
@@ -2348,10 +2348,15 @@ void ACodec::sendFormatChange() {
                                 &params, sizeof(params)),
                              (status_t)OK);
 
+                    CHECK_GT(params.nChannels, 0);
                     CHECK(params.nChannels == 1 || params.bInterleaved);
                     CHECK_EQ(params.nBitPerSample, 16u);
-                    CHECK_EQ((int)params.eNumData, (int)OMX_NumericalDataSigned);
-                    CHECK_EQ((int)params.ePCMMode, (int)OMX_AUDIO_PCMModeLinear);
+
+                    CHECK_EQ((int)params.eNumData,
+                             (int)OMX_NumericalDataSigned);
+
+                    CHECK_EQ((int)params.ePCMMode,
+                             (int)OMX_AUDIO_PCMModeLinear);
 
                     notify->setString("mime", MEDIA_MIMETYPE_AUDIO_RAW);
                     notify->setInt32("channel-count", params.nChannels);
@@ -2361,11 +2366,14 @@ void ACodec::sendFormatChange() {
                         if (mSkipCutBuffer != NULL) {
                             size_t prevbufsize = mSkipCutBuffer->size();
                             if (prevbufsize != 0) {
-                                ALOGW("Replacing SkipCutBuffer holding %d bytes", prevbufsize);
+                                ALOGW("Replacing SkipCutBuffer holding %d "
+                                      "bytes",
+                                      prevbufsize);
                             }
                         }
-                        mSkipCutBuffer = new SkipCutBuffer(mEncoderDelay * frameSize,
-                                                           mEncoderPadding * frameSize);
+                        mSkipCutBuffer = new SkipCutBuffer(
+                                mEncoderDelay * frameSize,
+                                mEncoderPadding * frameSize);
                     }
 
                     if (mChannelMaskPresent) {
index acc3abf..9544dbc 100644 (file)
@@ -69,7 +69,6 @@ LOCAL_C_INCLUDES:= \
 LOCAL_SHARED_LIBRARIES := \
         libbinder \
         libcamera_client \
-        libcrypto \
         libcutils \
         libdl \
         libdrmframework \
@@ -97,7 +96,6 @@ LOCAL_STATIC_LIBRARIES := \
         libvpx \
         libwebm \
         libstagefright_mpeg2ts \
-        libstagefright_httplive \
         libstagefright_id3 \
         libFLAC \
 
index 40c5a3c..f7a00d8 100644 (file)
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+#define LOG_TAG "AHierarchicalStateMachine"
+#include <utils/Log.h>
+
 #include <media/stagefright/foundation/AHierarchicalStateMachine.h>
 
 #include <media/stagefright/foundation/ADebug.h>
index a3fa7a3..85bd492 100644 (file)
@@ -6,16 +6,25 @@ LOCAL_SRC_FILES:=               \
         LiveDataSource.cpp      \
         LiveSession.cpp         \
         M3UParser.cpp           \
+        PlaylistFetcher.cpp     \
 
 LOCAL_C_INCLUDES:= \
        $(TOP)/frameworks/av/media/libstagefright \
        $(TOP)/frameworks/native/include/media/openmax \
        $(TOP)/external/openssl/include
 
+LOCAL_SHARED_LIBRARIES := \
+        libcrypto \
+        libcutils \
+        libmedia \
+        libstagefright \
+        libstagefright_foundation \
+        libutils \
+
 LOCAL_MODULE:= libstagefright_httplive
 
 ifeq ($(TARGET_ARCH),arm)
     LOCAL_CFLAGS += -Wno-psabi
 endif
 
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_SHARED_LIBRARY)
index 505bdb3..fff13eb 100644 (file)
 #define LOG_TAG "LiveSession"
 #include <utils/Log.h>
 
-#include "include/LiveSession.h"
+#include "LiveSession.h"
 
-#include "LiveDataSource.h"
+#include "M3UParser.h"
+#include "PlaylistFetcher.h"
 
-#include "include/M3UParser.h"
 #include "include/HTTPBase.h"
+#include "mpeg2ts/AnotherPacketSource.h"
 
 #include <cutils/properties.h>
 #include <media/stagefright/foundation/hexdump.h>
@@ -33,6 +34,8 @@
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/FileSource.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
 
 #include <ctype.h>
 #include <openssl/aes.h>
@@ -47,37 +50,107 @@ LiveSession::LiveSession(
       mUIDValid(uidValid),
       mUID(uid),
       mInPreparationPhase(true),
-      mDataSource(new LiveDataSource),
       mHTTPDataSource(
               HTTPBase::Create(
                   (mFlags & kFlagIncognito)
                     ? HTTPBase::kFlagIncognito
                     : 0)),
       mPrevBandwidthIndex(-1),
-      mLastPlaylistFetchTimeUs(-1),
-      mSeqNumber(-1),
-      mSeekTimeUs(-1),
-      mNumRetries(0),
-      mStartOfPlayback(true),
-      mDurationUs(-1),
-      mDurationFixed(false),
-      mSeekDone(false),
-      mDisconnectPending(false),
-      mMonitorQueueGeneration(0),
-      mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) {
+      mStreamMask(0),
+      mCheckBandwidthGeneration(0),
+      mLastDequeuedTimeUs(0ll),
+      mReconfigurationInProgress(false),
+      mDisconnectReplyID(0) {
     if (mUIDValid) {
         mHTTPDataSource->setUID(mUID);
     }
+
+    mPacketSources.add(
+            STREAMTYPE_AUDIO, new AnotherPacketSource(NULL /* meta */));
+
+    mPacketSources.add(
+            STREAMTYPE_VIDEO, new AnotherPacketSource(NULL /* meta */));
+
+    mPacketSources.add(
+            STREAMTYPE_SUBTITLES, new AnotherPacketSource(NULL /* meta */));
 }
 
 LiveSession::~LiveSession() {
 }
 
-sp<DataSource> LiveSession::getDataSource() {
-    return mDataSource;
+status_t LiveSession::dequeueAccessUnit(
+        StreamType stream, sp<ABuffer> *accessUnit) {
+    if (!(mStreamMask & stream)) {
+        return UNKNOWN_ERROR;
+    }
+
+    sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream);
+
+    status_t finalResult;
+    if (!packetSource->hasBufferAvailable(&finalResult)) {
+        return finalResult == OK ? -EAGAIN : finalResult;
+    }
+
+    status_t err = packetSource->dequeueAccessUnit(accessUnit);
+
+    const char *streamStr;
+    switch (stream) {
+        case STREAMTYPE_AUDIO:
+            streamStr = "audio";
+            break;
+        case STREAMTYPE_VIDEO:
+            streamStr = "video";
+            break;
+        case STREAMTYPE_SUBTITLES:
+            streamStr = "subs";
+            break;
+        default:
+            TRESPASS();
+    }
+
+    if (err == INFO_DISCONTINUITY) {
+        int32_t type;
+        CHECK((*accessUnit)->meta()->findInt32("discontinuity", &type));
+
+        sp<AMessage> extra;
+        if (!(*accessUnit)->meta()->findMessage("extra", &extra)) {
+            extra.clear();
+        }
+
+        ALOGI("[%s] read discontinuity of type %d, extra = %s",
+              streamStr,
+              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);
+
+        mLastDequeuedTimeUs = timeUs;
+    } else {
+        ALOGI("[%s] encountered error %d", streamStr, err);
+    }
+
+    return err;
+}
+
+status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) {
+    if (!(mStreamMask & stream)) {
+        return UNKNOWN_ERROR;
+    }
+
+    sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream);
+
+    sp<MetaData> meta = packetSource->getFormat();
+
+    if (meta == NULL) {
+        return -EAGAIN;
+    }
+
+    return convertMetaDataToMessage(meta, format);
 }
 
-void LiveSession::connect(
+void LiveSession::connectAsync(
         const char *url, const KeyedVector<String8, String8> *headers) {
     sp<AMessage> msg = new AMessage(kWhatConnect, id());
     msg->setString("url", url);
@@ -91,55 +164,184 @@ void LiveSession::connect(
     msg->post();
 }
 
-void LiveSession::disconnect() {
-    Mutex::Autolock autoLock(mLock);
-    mDisconnectPending = true;
+status_t LiveSession::disconnect() {
+    sp<AMessage> msg = new AMessage(kWhatDisconnect, id());
 
-    mHTTPDataSource->disconnect();
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
 
-    (new AMessage(kWhatDisconnect, id()))->post();
+    return err;
 }
 
-void LiveSession::seekTo(int64_t timeUs) {
-    Mutex::Autolock autoLock(mLock);
-    mSeekDone = false;
-
+status_t LiveSession::seekTo(int64_t timeUs) {
     sp<AMessage> msg = new AMessage(kWhatSeek, id());
     msg->setInt64("timeUs", timeUs);
-    msg->post();
 
-    while (!mSeekDone) {
-        mCondition.wait(mLock);
-    }
+    sp<AMessage> response;
+    status_t err = msg->postAndAwaitResponse(&response);
+
+    return err;
 }
 
 void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatConnect:
+        {
             onConnect(msg);
             break;
+        }
 
         case kWhatDisconnect:
-            onDisconnect();
+        {
+            CHECK(msg->senderAwaitsResponse(&mDisconnectReplyID));
+
+            if (mReconfigurationInProgress) {
+                break;
+            }
+
+            finishDisconnect();
             break;
+        }
 
-        case kWhatMonitorQueue:
+        case kWhatSeek:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            status_t err = onSeek(msg);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatFetcherNotify:
+        {
+            int32_t what;
+            CHECK(msg->findInt32("what", &what));
+
+            switch (what) {
+                case PlaylistFetcher::kWhatStarted:
+                    break;
+                case PlaylistFetcher::kWhatPaused:
+                case PlaylistFetcher::kWhatStopped:
+                {
+                    if (what == PlaylistFetcher::kWhatStopped) {
+                        AString uri;
+                        CHECK(msg->findString("uri", &uri));
+                        mFetcherInfos.removeItem(uri);
+                    }
+
+                    if (mContinuation != NULL) {
+                        CHECK_GT(mContinuationCounter, 0);
+                        if (--mContinuationCounter == 0) {
+                            mContinuation->post();
+                        }
+                    }
+                    break;
+                }
+
+                case PlaylistFetcher::kWhatDurationUpdate:
+                {
+                    AString uri;
+                    CHECK(msg->findString("uri", &uri));
+
+                    int64_t durationUs;
+                    CHECK(msg->findInt64("durationUs", &durationUs));
+
+                    FetcherInfo *info = &mFetcherInfos.editValueFor(uri);
+                    info->mDurationUs = durationUs;
+                    break;
+                }
+
+                case PlaylistFetcher::kWhatError:
+                {
+                    status_t err;
+                    CHECK(msg->findInt32("err", &err));
+
+                    ALOGE("XXX Received error %d from PlaylistFetcher.", err);
+
+                    if (mInPreparationPhase) {
+                        postPrepared(err);
+                    }
+
+                    mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(err);
+
+                    mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(err);
+
+                    mPacketSources.valueFor(
+                            STREAMTYPE_SUBTITLES)->signalEOS(err);
+
+                    sp<AMessage> notify = mNotify->dup();
+                    notify->setInt32("what", kWhatError);
+                    notify->setInt32("err", err);
+                    notify->post();
+                    break;
+                }
+
+                case PlaylistFetcher::kWhatTemporarilyDoneFetching:
+                {
+                    AString uri;
+                    CHECK(msg->findString("uri", &uri));
+
+                    FetcherInfo *info = &mFetcherInfos.editValueFor(uri);
+                    info->mIsPrepared = true;
+
+                    if (mInPreparationPhase) {
+                        bool allFetchersPrepared = true;
+                        for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+                            if (!mFetcherInfos.valueAt(i).mIsPrepared) {
+                                allFetchersPrepared = false;
+                                break;
+                            }
+                        }
+
+                        if (allFetchersPrepared) {
+                            postPrepared(OK);
+                        }
+                    }
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+
+            break;
+        }
+
+        case kWhatCheckBandwidth:
         {
             int32_t generation;
             CHECK(msg->findInt32("generation", &generation));
 
-            if (generation != mMonitorQueueGeneration) {
-                // Stale event
+            if (generation != mCheckBandwidthGeneration) {
                 break;
             }
 
-            onMonitorQueue();
+            onCheckBandwidth();
             break;
         }
 
-        case kWhatSeek:
-            onSeek(msg);
+        case kWhatChangeConfiguration2:
+        {
+            onChangeConfiguration2(msg);
+            break;
+        }
+
+        case kWhatChangeConfiguration3:
+        {
+            onChangeConfiguration3(msg);
+            break;
+        }
+
+        case kWhatFinishDisconnect2:
+        {
+            onFinishDisconnect2();
             break;
+        }
 
         default:
             TRESPASS();
@@ -172,48 +374,127 @@ void LiveSession::onConnect(const sp<AMessage> &msg) {
         headers = NULL;
     }
 
+#if 1
     ALOGI("onConnect <URL suppressed>");
+#else
+    ALOGI("onConnect %s", url.c_str());
+#endif
 
     mMasterURL = url;
 
     bool dummy;
-    sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &dummy);
+    mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy);
 
-    if (playlist == NULL) {
+    if (mPlaylist == NULL) {
         ALOGE("unable to fetch master playlist '%s'.", url.c_str());
 
-        signalEOS(ERROR_IO);
+        postPrepared(ERROR_IO);
         return;
     }
 
-    if (playlist->isVariantPlaylist()) {
-        for (size_t i = 0; i < playlist->size(); ++i) {
+    // We trust the content provider to make a reasonable choice of preferred
+    // initial bandwidth by listing it first in the variant playlist.
+    // At startup we really don't have a good estimate on the available
+    // network bandwidth since we haven't tranferred any data yet. Once
+    // we have we can make a better informed choice.
+    size_t initialBandwidth = 0;
+    size_t initialBandwidthIndex = 0;
+
+    if (mPlaylist->isVariantPlaylist()) {
+        for (size_t i = 0; i < mPlaylist->size(); ++i) {
             BandwidthItem item;
 
+            item.mPlaylistIndex = i;
+
             sp<AMessage> meta;
-            playlist->itemAt(i, &item.mURI, &meta);
+            AString uri;
+            mPlaylist->itemAt(i, &uri, &meta);
 
             unsigned long bandwidth;
             CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
 
+            if (initialBandwidth == 0) {
+                initialBandwidth = item.mBandwidth;
+            }
+
             mBandwidthItems.push(item);
         }
 
         CHECK_GT(mBandwidthItems.size(), 0u);
 
         mBandwidthItems.sort(SortByBandwidth);
+
+        for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
+            if (mBandwidthItems.itemAt(i).mBandwidth == initialBandwidth) {
+                initialBandwidthIndex = i;
+                break;
+            }
+        }
+    } else {
+        // dummy item.
+        BandwidthItem item;
+        item.mPlaylistIndex = 0;
+        item.mBandwidth = 0;
+        mBandwidthItems.push(item);
     }
 
-    postMonitorQueue();
+    changeConfiguration(0ll /* timeUs */, initialBandwidthIndex);
 }
 
-void LiveSession::onDisconnect() {
-    ALOGI("onDisconnect");
+void LiveSession::finishDisconnect() {
+    // No reconfiguration is currently pending, make sure none will trigger
+    // during disconnection either.
+    cancelCheckBandwidthEvent();
+
+    for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+        mFetcherInfos.valueAt(i).mFetcher->stopAsync();
+    }
+
+    sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, id());
 
-    signalEOS(ERROR_END_OF_STREAM);
+    mContinuationCounter = mFetcherInfos.size();
+    mContinuation = msg;
 
-    Mutex::Autolock autoLock(mLock);
-    mDisconnectPending = false;
+    if (mContinuationCounter == 0) {
+        msg->post();
+    }
+}
+
+void LiveSession::onFinishDisconnect2() {
+    mContinuation.clear();
+
+    mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM);
+    mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM);
+
+    mPacketSources.valueFor(
+            STREAMTYPE_SUBTITLES)->signalEOS(ERROR_END_OF_STREAM);
+
+    sp<AMessage> response = new AMessage;
+    response->setInt32("err", OK);
+
+    response->postReply(mDisconnectReplyID);
+    mDisconnectReplyID = 0;
+}
+
+sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) {
+    ssize_t index = mFetcherInfos.indexOfKey(uri);
+
+    if (index >= 0) {
+        return NULL;
+    }
+
+    sp<AMessage> notify = new AMessage(kWhatFetcherNotify, id());
+    notify->setString("uri", uri);
+
+    FetcherInfo info;
+    info.mFetcher = new PlaylistFetcher(notify, this, uri);
+    info.mDurationUs = -1ll;
+    info.mIsPrepared = false;
+    looper()->registerHandler(info.mFetcher);
+
+    mFetcherInfos.add(uri, info);
+
+    return info.mFetcher;
 }
 
 status_t LiveSession::fetchFile(
@@ -229,14 +510,6 @@ status_t LiveSession::fetchFile(
             && strncasecmp(url, "https://", 8)) {
         return ERROR_UNSUPPORTED;
     } else {
-        {
-            Mutex::Autolock autoLock(mLock);
-
-            if (mDisconnectPending) {
-                return ERROR_IO;
-            }
-        }
-
         KeyedVector<String8, String8> headers = mExtraHeaders;
         if (range_offset > 0 || range_length >= 0) {
             headers.add(
@@ -315,7 +588,8 @@ status_t LiveSession::fetchFile(
     return OK;
 }
 
-sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
+sp<M3UParser> LiveSession::fetchPlaylist(
+        const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
     ALOGV("fetchPlaylist '%s'", url);
 
     *unchanged = false;
@@ -339,13 +613,8 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
 
     MD5_Final(hash, &m);
 
-    if (mPlaylist != NULL && !memcmp(hash, mPlaylistHash, 16)) {
+    if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
         // playlist unchanged
-
-        if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) {
-            mRefreshState = (RefreshState)(mRefreshState + 1);
-        }
-
         *unchanged = true;
 
         ALOGV("Playlist unchanged, refresh state is now %d",
@@ -354,9 +623,9 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
         return NULL;
     }
 
-    memcpy(mPlaylistHash, hash, sizeof(hash));
-
-    mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
+    if (curPlaylistHash != NULL) {
+        memcpy(curPlaylistHash, hash, sizeof(hash));
+    }
 #endif
 
     sp<M3UParser> playlist =
@@ -371,37 +640,6 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
     return playlist;
 }
 
-int64_t LiveSession::getSegmentStartTimeUs(int32_t seqNumber) const {
-    CHECK(mPlaylist != NULL);
-
-    int32_t firstSeqNumberInPlaylist;
-    if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
-                "media-sequence", &firstSeqNumberInPlaylist)) {
-        firstSeqNumberInPlaylist = 0;
-    }
-
-    int32_t lastSeqNumberInPlaylist =
-        firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
-
-    CHECK_GE(seqNumber, firstSeqNumberInPlaylist);
-    CHECK_LE(seqNumber, lastSeqNumberInPlaylist);
-
-    int64_t segmentStartUs = 0ll;
-    for (int32_t index = 0;
-            index < seqNumber - firstSeqNumberInPlaylist; ++index) {
-        sp<AMessage> itemMeta;
-        CHECK(mPlaylist->itemAt(
-                    index, NULL /* uri */, &itemMeta));
-
-        int64_t itemDurationUs;
-        CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
-
-        segmentStartUs += itemDurationUs;
-    }
-
-    return segmentStartUs;
-}
-
 static double uniformRand() {
     return (double)rand() / RAND_MAX;
 }
@@ -412,36 +650,50 @@ size_t LiveSession::getBandwidthIndex() {
     }
 
 #if 1
-    int32_t bandwidthBps;
-    if (mHTTPDataSource != NULL
-            && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) {
-        ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
-    } else {
-        ALOGV("no bandwidth estimate.");
-        return 0;  // Pick the lowest bandwidth stream by default.
-    }
-
     char value[PROPERTY_VALUE_MAX];
-    if (property_get("media.httplive.max-bw", value, NULL)) {
+    ssize_t index;
+    if (property_get("media.httplive.bw-index", value, NULL)) {
         char *end;
-        long maxBw = strtoul(value, &end, 10);
-        if (end > value && *end == '\0') {
-            if (maxBw > 0 && bandwidthBps > maxBw) {
-                ALOGV("bandwidth capped to %ld bps", maxBw);
-                bandwidthBps = maxBw;
-            }
+        index = strtol(value, &end, 10);
+        CHECK(end > value && *end == '\0');
+
+        if (index >= 0 && (size_t)index >= mBandwidthItems.size()) {
+            index = mBandwidthItems.size() - 1;
         }
     }
 
-    // Consider only 80% of the available bandwidth usable.
-    bandwidthBps = (bandwidthBps * 8) / 10;
+    if (index < 0) {
+        int32_t bandwidthBps;
+        if (mHTTPDataSource != NULL
+                && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) {
+            ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
+        } else {
+            ALOGV("no bandwidth estimate.");
+            return 0;  // Pick the lowest bandwidth stream by default.
+        }
 
-    // Pick the highest bandwidth stream below or equal to estimated bandwidth.
+        char value[PROPERTY_VALUE_MAX];
+        if (property_get("media.httplive.max-bw", value, NULL)) {
+            char *end;
+            long maxBw = strtoul(value, &end, 10);
+            if (end > value && *end == '\0') {
+                if (maxBw > 0 && bandwidthBps > maxBw) {
+                    ALOGV("bandwidth capped to %ld bps", maxBw);
+                    bandwidthBps = maxBw;
+                }
+            }
+        }
 
-    size_t index = mBandwidthItems.size() - 1;
-    while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth
-                            > (size_t)bandwidthBps) {
-        --index;
+        // Consider only 80% of the available bandwidth usable.
+        bandwidthBps = (bandwidthBps * 8) / 10;
+
+        // Pick the highest bandwidth stream below or equal to estimated bandwidth.
+
+        index = mBandwidthItems.size() - 1;
+        while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth
+                                > (size_t)bandwidthBps) {
+            --index;
+        }
     }
 #elif 0
     // Change bandwidth at random()
@@ -452,6 +704,8 @@ size_t LiveSession::getBandwidthIndex() {
     // to lowest)
     const size_t kMinIndex = 0;
 
+    static ssize_t mPrevBandwidthIndex = -1;
+
     size_t index;
     if (mPrevBandwidthIndex < 0) {
         index = kMinIndex;
@@ -463,6 +717,7 @@ size_t LiveSession::getBandwidthIndex() {
             index = kMinIndex;
         }
     }
+    mPrevBandwidthIndex = index;
 #elif 0
     // Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec
 
@@ -470,570 +725,381 @@ size_t LiveSession::getBandwidthIndex() {
     while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth > 1200000) {
         --index;
     }
+#elif 1
+    char value[PROPERTY_VALUE_MAX];
+    size_t index;
+    if (property_get("media.httplive.bw-index", value, NULL)) {
+        char *end;
+        index = strtoul(value, &end, 10);
+        CHECK(end > value && *end == '\0');
+
+        if (index >= mBandwidthItems.size()) {
+            index = mBandwidthItems.size() - 1;
+        }
+    } else {
+        index = 0;
+    }
 #else
     size_t index = mBandwidthItems.size() - 1;  // Highest bandwidth stream
 #endif
 
+    CHECK_GE(index, 0);
+
     return index;
 }
 
-bool LiveSession::timeToRefreshPlaylist(int64_t nowUs) const {
-    if (mPlaylist == NULL) {
-        CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY);
-        return true;
-    }
-
-    int32_t targetDurationSecs;
-    CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
-
-    int64_t targetDurationUs = targetDurationSecs * 1000000ll;
-
-    int64_t minPlaylistAgeUs;
-
-    switch (mRefreshState) {
-        case INITIAL_MINIMUM_RELOAD_DELAY:
-        {
-            size_t n = mPlaylist->size();
-            if (n > 0) {
-                sp<AMessage> itemMeta;
-                CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta));
-
-                int64_t itemDurationUs;
-                CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
-
-                minPlaylistAgeUs = itemDurationUs;
-                break;
-            }
-
-            // fall through
-        }
-
-        case FIRST_UNCHANGED_RELOAD_ATTEMPT:
-        {
-            minPlaylistAgeUs = targetDurationUs / 2;
-            break;
-        }
-
-        case SECOND_UNCHANGED_RELOAD_ATTEMPT:
-        {
-            minPlaylistAgeUs = (targetDurationUs * 3) / 2;
-            break;
-        }
-
-        case THIRD_UNCHANGED_RELOAD_ATTEMPT:
-        {
-            minPlaylistAgeUs = targetDurationUs * 3;
-            break;
-        }
+status_t LiveSession::onSeek(const sp<AMessage> &msg) {
+    int64_t timeUs;
+    CHECK(msg->findInt64("timeUs", &timeUs));
 
-        default:
-            TRESPASS();
-            break;
+    if (!mReconfigurationInProgress) {
+        changeConfiguration(timeUs, getBandwidthIndex());
     }
 
-    return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs;
+    return OK;
 }
 
-void LiveSession::onDownloadNext() {
-    size_t bandwidthIndex = getBandwidthIndex();
-
-rinse_repeat:
-    int64_t nowUs = ALooper::GetNowUs();
-
-    if (mLastPlaylistFetchTimeUs < 0
-            || (ssize_t)bandwidthIndex != mPrevBandwidthIndex
-            || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
-        AString url;
-        if (mBandwidthItems.size() > 0) {
-            url = mBandwidthItems.editItemAt(bandwidthIndex).mURI;
-        } else {
-            url = mMasterURL;
-        }
-
-        if ((ssize_t)bandwidthIndex != mPrevBandwidthIndex) {
-            // If we switch bandwidths, do not pay any heed to whether
-            // playlists changed since the last time...
-            mPlaylist.clear();
-        }
-
-        bool unchanged;
-        sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &unchanged);
-        if (playlist == NULL) {
-            if (unchanged) {
-                // We succeeded in fetching the playlist, but it was
-                // unchanged from the last time we tried.
-            } else {
-                ALOGE("failed to load playlist at url '%s'", url.c_str());
-                signalEOS(ERROR_IO);
-
-                return;
-            }
-        } else {
-            mPlaylist = playlist;
-        }
-
-        if (!mDurationFixed) {
-            Mutex::Autolock autoLock(mLock);
-
-            if (!mPlaylist->isComplete() && !mPlaylist->isEvent()) {
-                mDurationUs = -1;
-                mDurationFixed = true;
-            } else {
-                mDurationUs = 0;
-                for (size_t i = 0; i < mPlaylist->size(); ++i) {
-                    sp<AMessage> itemMeta;
-                    CHECK(mPlaylist->itemAt(
-                                i, NULL /* uri */, &itemMeta));
-
-                    int64_t itemDurationUs;
-                    CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
-
-                    mDurationUs += itemDurationUs;
-                }
+status_t LiveSession::getDuration(int64_t *durationUs) const {
+    int64_t maxDurationUs = 0ll;
+    for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+        int64_t fetcherDurationUs = mFetcherInfos.valueAt(i).mDurationUs;
 
-                mDurationFixed = mPlaylist->isComplete();
-            }
+        if (fetcherDurationUs >= 0ll && fetcherDurationUs > maxDurationUs) {
+            maxDurationUs = fetcherDurationUs;
         }
-
-        mLastPlaylistFetchTimeUs = ALooper::GetNowUs();
     }
 
-    int32_t firstSeqNumberInPlaylist;
-    if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
-                "media-sequence", &firstSeqNumberInPlaylist)) {
-        firstSeqNumberInPlaylist = 0;
-    }
+    *durationUs = maxDurationUs;
 
-    bool seekDiscontinuity = false;
-    bool explicitDiscontinuity = false;
-    bool bandwidthChanged = false;
-
-    if (mSeekTimeUs >= 0) {
-        if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
-            size_t index = 0;
-            int64_t segmentStartUs = 0;
-            while (index < mPlaylist->size()) {
-                sp<AMessage> itemMeta;
-                CHECK(mPlaylist->itemAt(
-                            index, NULL /* uri */, &itemMeta));
-
-                int64_t itemDurationUs;
-                CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+    return OK;
+}
 
-                if (mSeekTimeUs < segmentStartUs + itemDurationUs) {
-                    break;
-                }
+bool LiveSession::isSeekable() const {
+    int64_t durationUs;
+    return getDuration(&durationUs) == OK && durationUs >= 0;
+}
 
-                segmentStartUs += itemDurationUs;
-                ++index;
-            }
+bool LiveSession::hasDynamicDuration() const {
+    return false;
+}
 
-            if (index < mPlaylist->size()) {
-                int32_t newSeqNumber = firstSeqNumberInPlaylist + index;
+void LiveSession::changeConfiguration(int64_t timeUs, size_t bandwidthIndex) {
+    CHECK(!mReconfigurationInProgress);
+    mReconfigurationInProgress = true;
 
-                ALOGI("seeking to seq no %d", newSeqNumber);
+    mPrevBandwidthIndex = bandwidthIndex;
 
-                mSeqNumber = newSeqNumber;
+    ALOGV("changeConfiguration => timeUs:%lld us, bwIndex:%d",
+          timeUs, bandwidthIndex);
 
-                mDataSource->reset();
+    mPlaylist->pickRandomMediaItems();
 
-                // reseting the data source will have had the
-                // side effect of discarding any previously queued
-                // bandwidth change discontinuity.
-                // Therefore we'll need to treat these seek
-                // discontinuities as involving a bandwidth change
-                // even if they aren't directly.
-                seekDiscontinuity = true;
-                bandwidthChanged = true;
-            }
-        }
+    CHECK_LT(bandwidthIndex, mBandwidthItems.size());
+    const BandwidthItem &item = mBandwidthItems.itemAt(bandwidthIndex);
 
-        mSeekTimeUs = -1;
+    uint32_t streamMask = 0;
 
-        Mutex::Autolock autoLock(mLock);
-        mSeekDone = true;
-        mCondition.broadcast();
+    AString audioURI;
+    if (mPlaylist->getAudioURI(item.mPlaylistIndex, &audioURI)) {
+        streamMask |= STREAMTYPE_AUDIO;
     }
 
-    const int32_t lastSeqNumberInPlaylist =
-        firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
-
-    if (mSeqNumber < 0) {
-        if (mPlaylist->isComplete()) {
-            mSeqNumber = firstSeqNumberInPlaylist;
-        } else {
-            // If this is a live session, start 3 segments from the end.
-            mSeqNumber = lastSeqNumberInPlaylist - 3;
-            if (mSeqNumber < firstSeqNumberInPlaylist) {
-                mSeqNumber = firstSeqNumberInPlaylist;
-            }
-        }
+    AString videoURI;
+    if (mPlaylist->getVideoURI(item.mPlaylistIndex, &videoURI)) {
+        streamMask |= STREAMTYPE_VIDEO;
     }
 
-    if (mSeqNumber < firstSeqNumberInPlaylist
-            || mSeqNumber > lastSeqNumberInPlaylist) {
-        if (mPrevBandwidthIndex != (ssize_t)bandwidthIndex) {
-            // Go back to the previous bandwidth.
+    AString subtitleURI;
+    if (mPlaylist->getSubtitleURI(item.mPlaylistIndex, &subtitleURI)) {
+        streamMask |= STREAMTYPE_SUBTITLES;
+    }
 
-            ALOGI("new bandwidth does not have the sequence number "
-                 "we're looking for, switching back to previous bandwidth");
+    // Step 1, stop and discard fetchers that are no longer needed.
+    // Pause those that we'll reuse.
+    for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+        const AString &uri = mFetcherInfos.keyAt(i);
 
-            mLastPlaylistFetchTimeUs = -1;
-            bandwidthIndex = mPrevBandwidthIndex;
-            goto rinse_repeat;
-        }
+        bool discardFetcher = true;
 
-        if (!mPlaylist->isComplete() && mNumRetries < kMaxNumRetries) {
-            ++mNumRetries;
-
-            if (mSeqNumber > lastSeqNumberInPlaylist) {
-                mLastPlaylistFetchTimeUs = -1;
-                postMonitorQueue(3000000ll);
-                return;
+        // If we're seeking all current fetchers are discarded.
+        if (timeUs < 0ll) {
+            if (((streamMask & STREAMTYPE_AUDIO) && uri == audioURI)
+                    || ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI)
+                    || ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI)) {
+                discardFetcher = false;
             }
+        }
 
-            // we've missed the boat, let's start from the lowest sequence
-            // number available and signal a discontinuity.
-
-            ALOGI("We've missed the boat, restarting playback.");
-            mSeqNumber = lastSeqNumberInPlaylist;
-            explicitDiscontinuity = true;
-
-            // fall through
+        if (discardFetcher) {
+            mFetcherInfos.valueAt(i).mFetcher->stopAsync();
         } else {
-            ALOGE("Cannot find sequence number %d in playlist "
-                 "(contains %d - %d)",
-                 mSeqNumber, firstSeqNumberInPlaylist,
-                 firstSeqNumberInPlaylist + mPlaylist->size() - 1);
-
-            signalEOS(ERROR_END_OF_STREAM);
-            return;
+            mFetcherInfos.valueAt(i).mFetcher->pauseAsync();
         }
     }
 
-    mNumRetries = 0;
-
-    AString uri;
-    sp<AMessage> itemMeta;
-    CHECK(mPlaylist->itemAt(
-                mSeqNumber - firstSeqNumberInPlaylist,
-                &uri,
-                &itemMeta));
-
-    int32_t val;
-    if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
-        explicitDiscontinuity = true;
+    sp<AMessage> msg = new AMessage(kWhatChangeConfiguration2, id());
+    msg->setInt32("streamMask", streamMask);
+    msg->setInt64("timeUs", timeUs);
+    if (streamMask & STREAMTYPE_AUDIO) {
+        msg->setString("audioURI", audioURI.c_str());
     }
-
-    int64_t range_offset, range_length;
-    if (!itemMeta->findInt64("range-offset", &range_offset)
-            || !itemMeta->findInt64("range-length", &range_length)) {
-        range_offset = 0;
-        range_length = -1;
+    if (streamMask & STREAMTYPE_VIDEO) {
+        msg->setString("videoURI", videoURI.c_str());
     }
-
-    ALOGV("fetching segment %d from (%d .. %d)",
-          mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
-
-    sp<ABuffer> buffer;
-    status_t err = fetchFile(uri.c_str(), &buffer, range_offset, range_length);
-    if (err != OK) {
-        ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
-        signalEOS(err);
-        return;
+    if (streamMask & STREAMTYPE_SUBTITLES) {
+        msg->setString("subtitleURI", subtitleURI.c_str());
     }
 
-    CHECK(buffer != NULL);
-
-    err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer);
+    // Every time a fetcher acknowledges the stopAsync or pauseAsync request
+    // we'll decrement mContinuationCounter, once it reaches zero, i.e. all
+    // fetchers have completed their asynchronous operation, we'll post
+    // mContinuation, which then is handled below in onChangeConfiguration2.
+    mContinuationCounter = mFetcherInfos.size();
+    mContinuation = msg;
 
-    if (err != OK) {
-        ALOGE("decryptBuffer failed w/ error %d", err);
-
-        signalEOS(err);
-        return;
+    if (mContinuationCounter == 0) {
+        msg->post();
     }
+}
 
-    if (buffer->size() == 0 || buffer->data()[0] != 0x47) {
-        // Not a transport stream???
-
-        ALOGE("This doesn't look like a transport stream...");
-
-        mBandwidthItems.removeAt(bandwidthIndex);
-
-        if (mBandwidthItems.isEmpty()) {
-            signalEOS(ERROR_UNSUPPORTED);
-            return;
-        }
+void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
+    mContinuation.clear();
 
-        ALOGI("Retrying with a different bandwidth stream.");
+    // All fetchers are either suspended or have been removed now.
 
-        mLastPlaylistFetchTimeUs = -1;
-        bandwidthIndex = getBandwidthIndex();
-        mPrevBandwidthIndex = bandwidthIndex;
-        mSeqNumber = -1;
+    uint32_t streamMask;
+    CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
 
-        goto rinse_repeat;
+    AString audioURI, videoURI, subtitleURI;
+    if (streamMask & STREAMTYPE_AUDIO) {
+        CHECK(msg->findString("audioURI", &audioURI));
+        ALOGV("audioURI = '%s'", audioURI.c_str());
     }
-
-    if ((size_t)mPrevBandwidthIndex != bandwidthIndex) {
-        bandwidthChanged = true;
+    if (streamMask & STREAMTYPE_VIDEO) {
+        CHECK(msg->findString("videoURI", &videoURI));
+        ALOGV("videoURI = '%s'", videoURI.c_str());
     }
-
-    if (mPrevBandwidthIndex < 0) {
-        // Don't signal a bandwidth change at the very beginning of
-        // playback.
-        bandwidthChanged = false;
+    if (streamMask & STREAMTYPE_SUBTITLES) {
+        CHECK(msg->findString("subtitleURI", &subtitleURI));
+        ALOGV("subtitleURI = '%s'", subtitleURI.c_str());
     }
 
-    if (mStartOfPlayback) {
-        seekDiscontinuity = true;
-        mStartOfPlayback = false;
+    // Determine which decoders to shutdown on the player side,
+    // a decoder has to be shutdown if either
+    // 1) its streamtype was active before but now longer isn't.
+    // or
+    // 2) its streamtype was already active and still is but the URI
+    //    has changed.
+    uint32_t changedMask = 0;
+    if (((mStreamMask & streamMask & STREAMTYPE_AUDIO)
+                && !(audioURI == mAudioURI))
+        || (mStreamMask & ~streamMask & STREAMTYPE_AUDIO)) {
+        changedMask |= STREAMTYPE_AUDIO;
+    }
+    if (((mStreamMask & streamMask & STREAMTYPE_VIDEO)
+                && !(videoURI == mVideoURI))
+        || (mStreamMask & ~streamMask & STREAMTYPE_VIDEO)) {
+        changedMask |= STREAMTYPE_VIDEO;
     }
 
-    if (seekDiscontinuity || explicitDiscontinuity || bandwidthChanged) {
-        // Signal discontinuity.
-
-        ALOGI("queueing discontinuity (seek=%d, explicit=%d, bandwidthChanged=%d)",
-             seekDiscontinuity, explicitDiscontinuity, bandwidthChanged);
+    if (changedMask == 0) {
+        // If nothing changed as far as the audio/video decoders
+        // are concerned we can proceed.
+        onChangeConfiguration3(msg);
+        return;
+    }
 
-        sp<ABuffer> tmp = new ABuffer(188);
-        memset(tmp->data(), 0, tmp->size());
+    // Something changed, inform the player which will shutdown the
+    // corresponding decoders and will post the reply once that's done.
+    // Handling the reply will continue executing below in
+    // onChangeConfiguration3.
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatStreamsChanged);
+    notify->setInt32("changedMask", changedMask);
 
-        // signal a 'hard' discontinuity for explicit or bandwidthChanged.
-        uint8_t type = (explicitDiscontinuity || bandwidthChanged) ? 1 : 0;
+    msg->setWhat(kWhatChangeConfiguration3);
+    msg->setTarget(id());
 
-        if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
-            // If this was a live event this made no sense since
-            // we don't have access to all the segment before the current
-            // one.
-            int64_t segmentStartTimeUs = getSegmentStartTimeUs(mSeqNumber);
-            memcpy(tmp->data() + 2, &segmentStartTimeUs, sizeof(segmentStartTimeUs));
+    notify->setMessage("reply", msg);
+    notify->post();
+}
 
-            type |= 2;
-        }
+void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
+    // All remaining fetchers are still suspended, the player has shutdown
+    // any decoders that needed it.
 
-        tmp->data()[1] = type;
+    uint32_t streamMask;
+    CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
 
-        mDataSource->queueBuffer(tmp);
+    AString audioURI, videoURI, subtitleURI;
+    if (streamMask & STREAMTYPE_AUDIO) {
+        CHECK(msg->findString("audioURI", &audioURI));
+    }
+    if (streamMask & STREAMTYPE_VIDEO) {
+        CHECK(msg->findString("videoURI", &videoURI));
+    }
+    if (streamMask & STREAMTYPE_SUBTITLES) {
+        CHECK(msg->findString("subtitleURI", &subtitleURI));
     }
 
-    mDataSource->queueBuffer(buffer);
+    int64_t timeUs;
+    CHECK(msg->findInt64("timeUs", &timeUs));
 
-    mPrevBandwidthIndex = bandwidthIndex;
-    ++mSeqNumber;
+    if (timeUs < 0ll) {
+        timeUs = mLastDequeuedTimeUs;
+    }
 
-    postMonitorQueue();
-}
+    mStreamMask = streamMask;
+    mAudioURI = audioURI;
+    mVideoURI = videoURI;
+    mSubtitleURI = subtitleURI;
 
-void LiveSession::signalEOS(status_t err) {
-    if (mInPreparationPhase && mNotify != NULL) {
-        sp<AMessage> notify = mNotify->dup();
+    // Resume all existing fetchers and assign them packet sources.
+    for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+        const AString &uri = mFetcherInfos.keyAt(i);
 
-        notify->setInt32(
-                "what",
-                err == ERROR_END_OF_STREAM
-                    ? kWhatPrepared : kWhatPreparationFailed);
+        uint32_t resumeMask = 0;
 
-        if (err != ERROR_END_OF_STREAM) {
-            notify->setInt32("err", err);
+        sp<AnotherPacketSource> audioSource;
+        if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
+            audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
+            resumeMask |= STREAMTYPE_AUDIO;
         }
 
-        notify->post();
-
-        mInPreparationPhase = false;
-    }
-
-    mDataSource->queueEOS(err);
-}
-
-void LiveSession::onMonitorQueue() {
-    if (mSeekTimeUs >= 0
-            || mDataSource->countQueuedBuffers() < kMaxNumQueuedFragments) {
-        onDownloadNext();
-    } else {
-        if (mInPreparationPhase) {
-            if (mNotify != NULL) {
-                sp<AMessage> notify = mNotify->dup();
-                notify->setInt32("what", kWhatPrepared);
-                notify->post();
-            }
-
-            mInPreparationPhase = false;
+        sp<AnotherPacketSource> videoSource;
+        if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
+            videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
+            resumeMask |= STREAMTYPE_VIDEO;
         }
 
-        postMonitorQueue(1000000ll);
-    }
-}
+        sp<AnotherPacketSource> subtitleSource;
+        if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
+            subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
+            resumeMask |= STREAMTYPE_SUBTITLES;
+        }
 
-status_t LiveSession::decryptBuffer(
-        size_t playlistIndex, const sp<ABuffer> &buffer) {
-    sp<AMessage> itemMeta;
-    bool found = false;
-    AString method;
+        CHECK_NE(resumeMask, 0u);
 
-    for (ssize_t i = playlistIndex; i >= 0; --i) {
-        AString uri;
-        CHECK(mPlaylist->itemAt(i, &uri, &itemMeta));
+        ALOGV("resuming fetchers for mask 0x%08x", resumeMask);
 
-        if (itemMeta->findString("cipher-method", &method)) {
-            found = true;
-            break;
-        }
-    }
+        streamMask &= ~resumeMask;
 
-    if (!found) {
-        method = "NONE";
+        mFetcherInfos.valueAt(i).mFetcher->startAsync(
+                audioSource, videoSource, subtitleSource);
     }
 
-    if (method == "NONE") {
-        return OK;
-    } else if (!(method == "AES-128")) {
-        ALOGE("Unsupported cipher method '%s'", method.c_str());
-        return ERROR_UNSUPPORTED;
-    }
+    // streamMask now only contains the types that need a new fetcher created.
 
-    AString keyURI;
-    if (!itemMeta->findString("cipher-uri", &keyURI)) {
-        ALOGE("Missing key uri");
-        return ERROR_MALFORMED;
+    if (streamMask != 0) {
+        ALOGV("creating new fetchers for mask 0x%08x", streamMask);
     }
 
-    ssize_t index = mAESKeyForURI.indexOfKey(keyURI);
-
-    sp<ABuffer> key;
-    if (index >= 0) {
-        key = mAESKeyForURI.valueAt(index);
-    } else {
-        key = new ABuffer(16);
-
-        sp<HTTPBase> keySource =
-              HTTPBase::Create(
-                  (mFlags & kFlagIncognito)
-                    ? HTTPBase::kFlagIncognito
-                    : 0);
+    while (streamMask != 0) {
+        StreamType streamType = (StreamType)(streamMask & ~(streamMask - 1));
 
-        if (mUIDValid) {
-            keySource->setUID(mUID);
+        AString uri;
+        switch (streamType) {
+            case STREAMTYPE_AUDIO:
+                uri = audioURI;
+                break;
+            case STREAMTYPE_VIDEO:
+                uri = videoURI;
+                break;
+            case STREAMTYPE_SUBTITLES:
+                uri = subtitleURI;
+                break;
+            default:
+                TRESPASS();
         }
 
-        status_t err =
-            keySource->connect(
-                    keyURI.c_str(),
-                    mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
-
-        if (err == OK) {
-            size_t offset = 0;
-            while (offset < 16) {
-                ssize_t n = keySource->readAt(
-                        offset, key->data() + offset, 16 - offset);
-                if (n <= 0) {
-                    err = ERROR_IO;
-                    break;
-                }
+        sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
+        CHECK(fetcher != NULL);
 
-                offset += n;
-            }
-        }
+        sp<AnotherPacketSource> audioSource;
+        if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
+            audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
+            audioSource->clear();
 
-        if (err != OK) {
-            ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str());
-            return ERROR_IO;
+            streamMask &= ~STREAMTYPE_AUDIO;
         }
 
-        mAESKeyForURI.add(keyURI, key);
-    }
+        sp<AnotherPacketSource> videoSource;
+        if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
+            videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
+            videoSource->clear();
 
-    AES_KEY aes_key;
-    if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
-        ALOGE("failed to set AES decryption key.");
-        return UNKNOWN_ERROR;
-    }
-
-    unsigned char aes_ivec[16];
-
-    AString iv;
-    if (itemMeta->findString("cipher-iv", &iv)) {
-        if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
-                || iv.size() != 16 * 2 + 2) {
-            ALOGE("malformed cipher IV '%s'.", iv.c_str());
-            return ERROR_MALFORMED;
+            streamMask &= ~STREAMTYPE_VIDEO;
         }
 
-        memset(aes_ivec, 0, sizeof(aes_ivec));
-        for (size_t i = 0; i < 16; ++i) {
-            char c1 = tolower(iv.c_str()[2 + 2 * i]);
-            char c2 = tolower(iv.c_str()[3 + 2 * i]);
-            if (!isxdigit(c1) || !isxdigit(c2)) {
-                ALOGE("malformed cipher IV '%s'.", iv.c_str());
-                return ERROR_MALFORMED;
-            }
-            uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
-            uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
+        sp<AnotherPacketSource> subtitleSource;
+        if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
+            subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
+            subtitleSource->clear();
 
-            aes_ivec[i] = nibble1 << 4 | nibble2;
+            streamMask &= ~STREAMTYPE_SUBTITLES;
         }
-    } else {
-        memset(aes_ivec, 0, sizeof(aes_ivec));
-        aes_ivec[15] = mSeqNumber & 0xff;
-        aes_ivec[14] = (mSeqNumber >> 8) & 0xff;
-        aes_ivec[13] = (mSeqNumber >> 16) & 0xff;
-        aes_ivec[12] = (mSeqNumber >> 24) & 0xff;
+
+        fetcher->startAsync(audioSource, videoSource, subtitleSource, timeUs);
     }
 
-    AES_cbc_encrypt(
-            buffer->data(), buffer->data(), buffer->size(),
-            &aes_key, aes_ivec, AES_DECRYPT);
+    // All fetchers have now been started, the configuration change
+    // has completed.
 
-    // hexdump(buffer->data(), buffer->size());
+    scheduleCheckBandwidthEvent();
 
-    size_t n = buffer->size();
-    CHECK_GT(n, 0u);
+    ALOGV("XXX configuration change completed.");
 
-    size_t pad = buffer->data()[n - 1];
+    mReconfigurationInProgress = false;
 
-    CHECK_GT(pad, 0u);
-    CHECK_LE(pad, 16u);
-    CHECK_GE((size_t)n, pad);
-    for (size_t i = 0; i < pad; ++i) {
-        CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad);
+    if (mDisconnectReplyID != 0) {
+        finishDisconnect();
     }
+}
 
-    n -= pad;
-
-    buffer->setRange(buffer->offset(), n);
-
-    return OK;
+void LiveSession::scheduleCheckBandwidthEvent() {
+    sp<AMessage> msg = new AMessage(kWhatCheckBandwidth, id());
+    msg->setInt32("generation", mCheckBandwidthGeneration);
+    msg->post(10000000ll);
 }
 
-void LiveSession::postMonitorQueue(int64_t delayUs) {
-    sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id());
-    msg->setInt32("generation", ++mMonitorQueueGeneration);
-    msg->post(delayUs);
+void LiveSession::cancelCheckBandwidthEvent() {
+    ++mCheckBandwidthGeneration;
 }
 
-void LiveSession::onSeek(const sp<AMessage> &msg) {
-    int64_t timeUs;
-    CHECK(msg->findInt64("timeUs", &timeUs));
+void LiveSession::onCheckBandwidth() {
+    if (mReconfigurationInProgress) {
+        scheduleCheckBandwidthEvent();
+        return;
+    }
+
+    size_t bandwidthIndex = getBandwidthIndex();
+    if (mPrevBandwidthIndex < 0
+            || bandwidthIndex != (size_t)mPrevBandwidthIndex) {
+        changeConfiguration(-1ll /* timeUs */, bandwidthIndex);
+    }
 
-    mSeekTimeUs = timeUs;
-    postMonitorQueue();
+    // Handling the kWhatCheckBandwidth even here does _not_ automatically
+    // schedule another one on return, only an explicit call to
+    // scheduleCheckBandwidthEvent will do that.
+    // This ensures that only one configuration change is ongoing at any
+    // one time, once that completes it'll schedule another check bandwidth
+    // event.
 }
 
-status_t LiveSession::getDuration(int64_t *durationUs) const {
-    Mutex::Autolock autoLock(mLock);
-    *durationUs = mDurationUs;
+void LiveSession::postPrepared(status_t err) {
+    CHECK(mInPreparationPhase);
 
-    return OK;
-}
+    sp<AMessage> notify = mNotify->dup();
+    if (err == OK || err == ERROR_END_OF_STREAM) {
+        notify->setInt32("what", kWhatPrepared);
+    } else {
+        notify->setInt32("what", kWhatPreparationFailed);
+        notify->setInt32("err", err);
+    }
 
-bool LiveSession::isSeekable() const {
-    int64_t durationUs;
-    return getDuration(&durationUs) == OK && durationUs >= 0;
-}
+    notify->post();
 
-bool LiveSession::hasDynamicDuration() const {
-    return !mDurationFixed;
+    mInPreparationPhase = false;
 }
 
 }  // namespace android
similarity index 53%
rename from media/libstagefright/include/LiveSession.h
rename to media/libstagefright/httplive/LiveSession.h
index db44a33..b134725 100644 (file)
 namespace android {
 
 struct ABuffer;
+struct AnotherPacketSource;
 struct DataSource;
+struct HTTPBase;
 struct LiveDataSource;
 struct M3UParser;
-struct HTTPBase;
+struct PlaylistFetcher;
 
 struct LiveSession : public AHandler {
     enum Flags {
@@ -39,24 +41,32 @@ struct LiveSession : public AHandler {
             const sp<AMessage> &notify,
             uint32_t flags = 0, bool uidValid = false, uid_t uid = 0);
 
-    sp<DataSource> getDataSource();
+    enum StreamType {
+        STREAMTYPE_AUDIO        = 1,
+        STREAMTYPE_VIDEO        = 2,
+        STREAMTYPE_SUBTITLES    = 4,
+    };
+    status_t dequeueAccessUnit(StreamType stream, sp<ABuffer> *accessUnit);
+
+    status_t getStreamFormat(StreamType stream, sp<AMessage> *format);
 
-    void connect(
+    void connectAsync(
             const char *url,
             const KeyedVector<String8, String8> *headers = NULL);
 
-    void disconnect();
+    status_t disconnect();
 
     // Blocks until seek is complete.
-    void seekTo(int64_t timeUs);
+    status_t seekTo(int64_t timeUs);
 
     status_t getDuration(int64_t *durationUs) const;
 
     bool isSeekable() const;
     bool hasDynamicDuration() const;
 
-    // Posted notification's "what" field will carry one of the following:
     enum {
+        kWhatStreamsChanged,
+        kWhatError,
         kWhatPrepared,
         kWhatPreparationFailed,
     };
@@ -67,23 +77,30 @@ protected:
     virtual void onMessageReceived(const sp<AMessage> &msg);
 
 private:
-    enum {
-        kMaxNumQueuedFragments = 3,
-        kMaxNumRetries         = 5,
-    };
+    friend struct PlaylistFetcher;
 
     enum {
-        kWhatConnect        = 'conn',
-        kWhatDisconnect     = 'disc',
-        kWhatMonitorQueue   = 'moni',
-        kWhatSeek           = 'seek',
+        kWhatConnect                    = 'conn',
+        kWhatDisconnect                 = 'disc',
+        kWhatSeek                       = 'seek',
+        kWhatFetcherNotify              = 'notf',
+        kWhatCheckBandwidth             = 'bndw',
+        kWhatChangeConfiguration2       = 'chC2',
+        kWhatChangeConfiguration3       = 'chC3',
+        kWhatFinishDisconnect2          = 'fin2',
     };
 
     struct BandwidthItem {
-        AString mURI;
+        size_t mPlaylistIndex;
         unsigned long mBandwidth;
     };
 
+    struct FetcherInfo {
+        sp<PlaylistFetcher> mFetcher;
+        int64_t mDurationUs;
+        bool mIsPrepared;
+    };
+
     sp<AMessage> mNotify;
     uint32_t mFlags;
     bool mUIDValid;
@@ -91,71 +108,61 @@ private:
 
     bool mInPreparationPhase;
 
-    sp<LiveDataSource> mDataSource;
-
     sp<HTTPBase> mHTTPDataSource;
+    KeyedVector<String8, String8> mExtraHeaders;
 
     AString mMasterURL;
-    KeyedVector<String8, String8> mExtraHeaders;
 
     Vector<BandwidthItem> mBandwidthItems;
-
-    KeyedVector<AString, sp<ABuffer> > mAESKeyForURI;
-
     ssize_t mPrevBandwidthIndex;
-    int64_t mLastPlaylistFetchTimeUs;
+
     sp<M3UParser> mPlaylist;
-    int32_t mSeqNumber;
-    int64_t mSeekTimeUs;
-    int32_t mNumRetries;
-    bool mStartOfPlayback;
-
-    mutable Mutex mLock;
-    Condition mCondition;
-    int64_t mDurationUs;
-    bool mDurationFixed;  // Duration has been determined once and for all.
-    bool mSeekDone;
-    bool mDisconnectPending;
-
-    int32_t mMonitorQueueGeneration;
-
-    enum RefreshState {
-        INITIAL_MINIMUM_RELOAD_DELAY,
-        FIRST_UNCHANGED_RELOAD_ATTEMPT,
-        SECOND_UNCHANGED_RELOAD_ATTEMPT,
-        THIRD_UNCHANGED_RELOAD_ATTEMPT
-    };
-    RefreshState mRefreshState;
 
-    uint8_t mPlaylistHash[16];
+    KeyedVector<AString, FetcherInfo> mFetcherInfos;
+    AString mAudioURI, mVideoURI, mSubtitleURI;
+    uint32_t mStreamMask;
+
+    KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources;
+
+    int32_t mCheckBandwidthGeneration;
+
+    size_t mContinuationCounter;
+    sp<AMessage> mContinuation;
+
+    int64_t mLastDequeuedTimeUs;
+
+    bool mReconfigurationInProgress;
+    uint32_t mDisconnectReplyID;
+
+    sp<PlaylistFetcher> addFetcher(const char *uri);
 
     void onConnect(const sp<AMessage> &msg);
-    void onDisconnect();
-    void onDownloadNext();
-    void onMonitorQueue();
-    void onSeek(const sp<AMessage> &msg);
+    status_t onSeek(const sp<AMessage> &msg);
+    void onFinishDisconnect2();
 
     status_t fetchFile(
             const char *url, sp<ABuffer> *out,
             int64_t range_offset = 0, int64_t range_length = -1);
 
-    sp<M3UParser> fetchPlaylist(const char *url, bool *unchanged);
+    sp<M3UParser> fetchPlaylist(
+            const char *url, uint8_t *curPlaylistHash, bool *unchanged);
+
     size_t getBandwidthIndex();
 
-    status_t decryptBuffer(
-            size_t playlistIndex, const sp<ABuffer> &buffer);
+    static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
 
-    void postMonitorQueue(int64_t delayUs = 0);
+    void changeConfiguration(int64_t timeUs, size_t bandwidthIndex);
+    void onChangeConfiguration2(const sp<AMessage> &msg);
+    void onChangeConfiguration3(const sp<AMessage> &msg);
 
-    bool timeToRefreshPlaylist(int64_t nowUs) const;
+    void scheduleCheckBandwidthEvent();
+    void cancelCheckBandwidthEvent();
 
-    static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
+    void onCheckBandwidth();
 
-    // Returns the media time in us of the segment specified by seqNumber.
-    // This is computed by summing the durations of all segments before it.
-    int64_t getSegmentStartTimeUs(int32_t seqNumber) const;
+    void finishDisconnect();
 
-    void signalEOS(status_t err);
+    void postPrepared(status_t err);
 
     DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
 };
index 68bbca2..be66252 100644 (file)
 #define LOG_TAG "M3UParser"
 #include <utils/Log.h>
 
-#include "include/M3UParser.h"
+#include "M3UParser.h"
 
+#include <cutils/properties.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaErrors.h>
 
 namespace android {
 
+struct M3UParser::MediaGroup : public RefBase {
+    enum Type {
+        TYPE_AUDIO,
+        TYPE_VIDEO,
+        TYPE_SUBS,
+    };
+
+    enum FlagBits {
+        FLAG_AUTOSELECT         = 1,
+        FLAG_DEFAULT            = 2,
+        FLAG_FORCED             = 4,
+        FLAG_HAS_LANGUAGE       = 8,
+        FLAG_HAS_URI            = 16,
+    };
+
+    MediaGroup(Type type);
+
+    Type type() const;
+
+    status_t addMedia(
+            const char *name,
+            const char *uri,
+            const char *language,
+            uint32_t flags);
+
+    bool getActiveURI(AString *uri) const;
+
+    void pickRandomMediaItems();
+
+protected:
+    virtual ~MediaGroup();
+
+private:
+    struct Media {
+        AString mName;
+        AString mURI;
+        AString mLanguage;
+        uint32_t mFlags;
+    };
+
+    Type mType;
+    Vector<Media> mMediaItems;
+
+    ssize_t mSelectedIndex;
+
+    DISALLOW_EVIL_CONSTRUCTORS(MediaGroup);
+};
+
+M3UParser::MediaGroup::MediaGroup(Type type)
+    : mType(type),
+      mSelectedIndex(-1) {
+}
+
+M3UParser::MediaGroup::~MediaGroup() {
+}
+
+M3UParser::MediaGroup::Type M3UParser::MediaGroup::type() const {
+    return mType;
+}
+
+status_t M3UParser::MediaGroup::addMedia(
+        const char *name,
+        const char *uri,
+        const char *language,
+        uint32_t flags) {
+    mMediaItems.push();
+    Media &item = mMediaItems.editItemAt(mMediaItems.size() - 1);
+
+    item.mName = name;
+
+    if (uri) {
+        item.mURI = uri;
+    }
+
+    if (language) {
+        item.mLanguage = language;
+    }
+
+    item.mFlags = flags;
+
+    return OK;
+}
+
+void M3UParser::MediaGroup::pickRandomMediaItems() {
+#if 1
+    switch (mType) {
+        case TYPE_AUDIO:
+        {
+            char value[PROPERTY_VALUE_MAX];
+            if (property_get("media.httplive.audio-index", value, NULL)) {
+                char *end;
+                mSelectedIndex = strtoul(value, &end, 10);
+                CHECK(end > value && *end == '\0');
+
+                if (mSelectedIndex >= mMediaItems.size()) {
+                    mSelectedIndex = mMediaItems.size() - 1;
+                }
+            } else {
+                mSelectedIndex = 0;
+            }
+            break;
+        }
+
+        case TYPE_VIDEO:
+        {
+            mSelectedIndex = 0;
+            break;
+        }
+
+        case TYPE_SUBS:
+        {
+            mSelectedIndex = -1;
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+#else
+    mSelectedIndex = (rand() * mMediaItems.size()) / RAND_MAX;
+#endif
+}
+
+bool M3UParser::MediaGroup::getActiveURI(AString *uri) const {
+    for (size_t i = 0; i < mMediaItems.size(); ++i) {
+        if (mSelectedIndex >= 0 && i == (size_t)mSelectedIndex) {
+            const Media &item = mMediaItems.itemAt(i);
+
+            *uri = item.mURI;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 M3UParser::M3UParser(
         const char *baseURI, const void *data, size_t size)
     : mInitCheck(NO_INIT),
@@ -92,6 +231,58 @@ bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) {
     return true;
 }
 
+void M3UParser::pickRandomMediaItems() {
+    for (size_t i = 0; i < mMediaGroups.size(); ++i) {
+        mMediaGroups.valueAt(i)->pickRandomMediaItems();
+    }
+}
+
+bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
+    if (!mIsVariantPlaylist) {
+        *uri = mBaseURI;
+
+        // Assume media without any more specific attribute contains
+        // audio and video, but no subtitles.
+        return !strcmp("audio", key) || !strcmp("video", key);
+    }
+
+    CHECK_LT(index, mItems.size());
+
+    sp<AMessage> meta = mItems.itemAt(index).mMeta;
+
+    AString groupID;
+    if (!meta->findString(key, &groupID)) {
+        *uri = mItems.itemAt(index).mURI;
+
+        // Assume media without any more specific attribute contains
+        // audio and video, but no subtitles.
+        return !strcmp("audio", key) || !strcmp("video", key);
+    }
+
+    sp<MediaGroup> group = mMediaGroups.valueFor(groupID);
+    if (!group->getActiveURI(uri)) {
+        return false;
+    }
+
+    if ((*uri).empty()) {
+        *uri = mItems.itemAt(index).mURI;
+    }
+
+    return true;
+}
+
+bool M3UParser::getAudioURI(size_t index, AString *uri) const {
+    return getTypeURI(index, "audio", uri);
+}
+
+bool M3UParser::getVideoURI(size_t index, AString *uri) const {
+    return getTypeURI(index, "video", uri);
+}
+
+bool M3UParser::getSubtitleURI(size_t index, AString *uri) const {
+    return getTypeURI(index, "subtitles", uri);
+}
+
 static bool MakeURL(const char *baseURL, const char *url, AString *out) {
     out->clear();
 
@@ -241,6 +432,8 @@ status_t M3UParser::parse(const void *_data, size_t size) {
 
                     segmentRangeOffset = offset + length;
                 }
+            } else if (line.startsWith("#EXT-X-MEDIA")) {
+                err = parseMedia(line);
             }
 
             if (err != OK) {
@@ -322,9 +515,31 @@ status_t M3UParser::parseMetaDataDuration(
     return OK;
 }
 
-// static
+// Find the next occurence of the character "what" at or after "offset",
+// but ignore occurences between quotation marks.
+// Return the index of the occurrence or -1 if not found.
+static ssize_t FindNextUnquoted(
+        const AString &line, char what, size_t offset) {
+    CHECK_NE((int)what, (int)'"');
+
+    bool quoted = false;
+    while (offset < line.size()) {
+        char c = line.c_str()[offset];
+
+        if (c == '"') {
+            quoted = !quoted;
+        } else if (c == what && !quoted) {
+            return offset;
+        }
+
+        ++offset;
+    }
+
+    return -1;
+}
+
 status_t M3UParser::parseStreamInf(
-        const AString &line, sp<AMessage> *meta) {
+        const AString &line, sp<AMessage> *meta) const {
     ssize_t colonPos = line.find(":");
 
     if (colonPos < 0) {
@@ -334,7 +549,7 @@ status_t M3UParser::parseStreamInf(
     size_t offset = colonPos + 1;
 
     while (offset < line.size()) {
-        ssize_t end = line.find(",", offset);
+        ssize_t end = FindNextUnquoted(line, ',', offset);
         if (end < 0) {
             end = line.size();
         }
@@ -371,33 +586,35 @@ status_t M3UParser::parseStreamInf(
                 *meta = new AMessage;
             }
             (*meta)->setInt32("bandwidth", x);
-        }
-    }
+        } else if (!strcasecmp("audio", key.c_str())
+                || !strcasecmp("video", key.c_str())
+                || !strcasecmp("subtitles", key.c_str())) {
+            if (val.size() < 2
+                    || val.c_str()[0] != '"'
+                    || val.c_str()[val.size() - 1] != '"') {
+                ALOGE("Expected quoted string for %s attribute, "
+                      "got '%s' instead.",
+                      key.c_str(), val.c_str());
+
+                return ERROR_MALFORMED;
+            }
 
-    return OK;
-}
+            AString groupID(val, 1, val.size() - 2);
+            ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
 
-// Find the next occurence of the character "what" at or after "offset",
-// but ignore occurences between quotation marks.
-// Return the index of the occurrence or -1 if not found.
-static ssize_t FindNextUnquoted(
-        const AString &line, char what, size_t offset) {
-    CHECK_NE((int)what, (int)'"');
+            if (groupIndex < 0) {
+                ALOGE("Undefined media group '%s' referenced in stream info.",
+                      groupID.c_str());
 
-    bool quoted = false;
-    while (offset < line.size()) {
-        char c = line.c_str()[offset];
+                return ERROR_MALFORMED;
+            }
 
-        if (c == '"') {
-            quoted = !quoted;
-        } else if (c == what && !quoted) {
-            return offset;
+            key.tolower();
+            (*meta)->setString(key.c_str(), groupID.c_str());
         }
-
-        ++offset;
     }
 
-    return -1;
+    return OK;
 }
 
 // static
@@ -515,6 +732,234 @@ status_t M3UParser::parseByteRange(
     return OK;
 }
 
+status_t M3UParser::parseMedia(const AString &line) {
+    ssize_t colonPos = line.find(":");
+
+    if (colonPos < 0) {
+        return ERROR_MALFORMED;
+    }
+
+    bool haveGroupType = false;
+    MediaGroup::Type groupType = MediaGroup::TYPE_AUDIO;
+
+    bool haveGroupID = false;
+    AString groupID;
+
+    bool haveGroupLanguage = false;
+    AString groupLanguage;
+
+    bool haveGroupName = false;
+    AString groupName;
+
+    bool haveGroupAutoselect = false;
+    bool groupAutoselect = false;
+
+    bool haveGroupDefault = false;
+    bool groupDefault = false;
+
+    bool haveGroupForced = false;
+    bool groupForced = false;
+
+    bool haveGroupURI = false;
+    AString groupURI;
+
+    size_t offset = colonPos + 1;
+
+    while (offset < line.size()) {
+        ssize_t end = FindNextUnquoted(line, ',', offset);
+        if (end < 0) {
+            end = line.size();
+        }
+
+        AString attr(line, offset, end - offset);
+        attr.trim();
+
+        offset = end + 1;
+
+        ssize_t equalPos = attr.find("=");
+        if (equalPos < 0) {
+            continue;
+        }
+
+        AString key(attr, 0, equalPos);
+        key.trim();
+
+        AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
+        val.trim();
+
+        ALOGV("key=%s value=%s", key.c_str(), val.c_str());
+
+        if (!strcasecmp("type", key.c_str())) {
+            if (!strcasecmp("subtitles", val.c_str())) {
+                groupType = MediaGroup::TYPE_SUBS;
+            } else if (!strcasecmp("audio", val.c_str())) {
+                groupType = MediaGroup::TYPE_AUDIO;
+            } else if (!strcasecmp("video", val.c_str())) {
+                groupType = MediaGroup::TYPE_VIDEO;
+            } else {
+                ALOGE("Invalid media group type '%s'", val.c_str());
+                return ERROR_MALFORMED;
+            }
+
+            haveGroupType = true;
+        } else if (!strcasecmp("group-id", key.c_str())) {
+            if (val.size() < 2
+                    || val.c_str()[0] != '"'
+                    || val.c_str()[val.size() - 1] != '"') {
+                ALOGE("Expected quoted string for GROUP-ID, got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            groupID.setTo(val, 1, val.size() - 2);
+            haveGroupID = true;
+        } else if (!strcasecmp("language", key.c_str())) {
+            if (val.size() < 2
+                    || val.c_str()[0] != '"'
+                    || val.c_str()[val.size() - 1] != '"') {
+                ALOGE("Expected quoted string for LANGUAGE, got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            groupLanguage.setTo(val, 1, val.size() - 2);
+            haveGroupLanguage = true;
+        } else if (!strcasecmp("name", key.c_str())) {
+            if (val.size() < 2
+                    || val.c_str()[0] != '"'
+                    || val.c_str()[val.size() - 1] != '"') {
+                ALOGE("Expected quoted string for NAME, got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            groupName.setTo(val, 1, val.size() - 2);
+            haveGroupName = true;
+        } else if (!strcasecmp("autoselect", key.c_str())) {
+            groupAutoselect = false;
+            if (!strcasecmp("YES", val.c_str())) {
+                groupAutoselect = true;
+            } else if (!strcasecmp("NO", val.c_str())) {
+                groupAutoselect = false;
+            } else {
+                ALOGE("Expected YES or NO for AUTOSELECT attribute, "
+                      "got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            haveGroupAutoselect = true;
+        } else if (!strcasecmp("default", key.c_str())) {
+            groupDefault = false;
+            if (!strcasecmp("YES", val.c_str())) {
+                groupDefault = true;
+            } else if (!strcasecmp("NO", val.c_str())) {
+                groupDefault = false;
+            } else {
+                ALOGE("Expected YES or NO for DEFAULT attribute, "
+                      "got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            haveGroupDefault = true;
+        } else if (!strcasecmp("forced", key.c_str())) {
+            groupForced = false;
+            if (!strcasecmp("YES", val.c_str())) {
+                groupForced = true;
+            } else if (!strcasecmp("NO", val.c_str())) {
+                groupForced = false;
+            } else {
+                ALOGE("Expected YES or NO for FORCED attribute, "
+                      "got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            haveGroupForced = true;
+        } else if (!strcasecmp("uri", key.c_str())) {
+            if (val.size() < 2
+                    || val.c_str()[0] != '"'
+                    || val.c_str()[val.size() - 1] != '"') {
+                ALOGE("Expected quoted string for URI, got '%s' instead.",
+                      val.c_str());
+
+                return ERROR_MALFORMED;
+            }
+
+            AString tmp(val, 1, val.size() - 2);
+
+            if (!MakeURL(mBaseURI.c_str(), tmp.c_str(), &groupURI)) {
+                ALOGI("Failed to make absolute URI from '%s'.", tmp.c_str());
+            }
+
+            haveGroupURI = true;
+        }
+    }
+
+    if (!haveGroupType || !haveGroupID || !haveGroupName) {
+        ALOGE("Incomplete EXT-X-MEDIA element.");
+        return ERROR_MALFORMED;
+    }
+
+    uint32_t flags = 0;
+    if (haveGroupAutoselect && groupAutoselect) {
+        flags |= MediaGroup::FLAG_AUTOSELECT;
+    }
+    if (haveGroupDefault && groupDefault) {
+        flags |= MediaGroup::FLAG_DEFAULT;
+    }
+    if (haveGroupForced) {
+        if (groupType != MediaGroup::TYPE_SUBS) {
+            ALOGE("The FORCED attribute MUST not be present on anything "
+                  "but SUBS media.");
+
+            return ERROR_MALFORMED;
+        }
+
+        if (groupForced) {
+            flags |= MediaGroup::FLAG_FORCED;
+        }
+    }
+    if (haveGroupLanguage) {
+        flags |= MediaGroup::FLAG_HAS_LANGUAGE;
+    }
+    if (haveGroupURI) {
+        flags |= MediaGroup::FLAG_HAS_URI;
+    }
+
+    ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
+    sp<MediaGroup> group;
+
+    if (groupIndex < 0) {
+        group = new MediaGroup(groupType);
+        mMediaGroups.add(groupID, group);
+    } else {
+        group = mMediaGroups.valueAt(groupIndex);
+
+        if (group->type() != groupType) {
+            ALOGE("Attempt to put media item under group of different type "
+                  "(groupType = %d, item type = %d",
+                  group->type(),
+                  groupType);
+
+            return ERROR_MALFORMED;
+        }
+    }
+
+    return group->addMedia(
+            groupName.c_str(),
+            haveGroupURI ? groupURI.c_str() : NULL,
+            haveGroupLanguage ? groupLanguage.c_str() : NULL,
+            flags);
+}
+
 // static
 status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
     char *end;
similarity index 81%
rename from media/libstagefright/include/M3UParser.h
rename to media/libstagefright/httplive/M3UParser.h
index 2d2f50f..abea286 100644 (file)
@@ -40,10 +40,18 @@ struct M3UParser : public RefBase {
     size_t size();
     bool itemAt(size_t index, AString *uri, sp<AMessage> *meta = NULL);
 
+    void pickRandomMediaItems();
+
+    bool getAudioURI(size_t index, AString *uri) const;
+    bool getVideoURI(size_t index, AString *uri) const;
+    bool getSubtitleURI(size_t index, AString *uri) const;
+
 protected:
     virtual ~M3UParser();
 
 private:
+    struct MediaGroup;
+
     struct Item {
         AString mURI;
         sp<AMessage> mMeta;
@@ -60,6 +68,9 @@ private:
     sp<AMessage> mMeta;
     Vector<Item> mItems;
 
+    // Media groups keyed by group ID.
+    KeyedVector<AString, sp<MediaGroup> > mMediaGroups;
+
     status_t parse(const void *data, size_t size);
 
     static status_t parseMetaData(
@@ -68,8 +79,8 @@ private:
     static status_t parseMetaDataDuration(
             const AString &line, sp<AMessage> *meta, const char *key);
 
-    static status_t parseStreamInf(
-            const AString &line, sp<AMessage> *meta);
+    status_t parseStreamInf(
+            const AString &line, sp<AMessage> *meta) const;
 
     static status_t parseCipherInfo(
             const AString &line, sp<AMessage> *meta, const AString &baseURI);
@@ -78,6 +89,10 @@ private:
             const AString &line, uint64_t curOffset,
             uint64_t *length, uint64_t *offset);
 
+    status_t parseMedia(const AString &line);
+
+    bool getTypeURI(size_t index, const char *key, AString *uri) const;
+
     static status_t ParseInt32(const char *s, int32_t *x);
     static status_t ParseDouble(const char *s, double *x);
 
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
new file mode 100644 (file)
index 0000000..8ae70b7
--- /dev/null
@@ -0,0 +1,969 @@
+/*
+ * Copyright (C) 2012 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 "PlaylistFetcher"
+#include <utils/Log.h>
+
+#include "PlaylistFetcher.h"
+
+#include "LiveDataSource.h"
+#include "LiveSession.h"
+#include "M3UParser.h"
+
+#include "include/avc_utils.h"
+#include "include/HTTPBase.h"
+#include "include/ID3.h"
+#include "mpeg2ts/AnotherPacketSource.h"
+
+#include <media/IStreamSource.h>
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+#include <ctype.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+
+namespace android {
+
+// static
+const int64_t PlaylistFetcher::kMinBufferedDurationUs = 10000000ll;
+
+PlaylistFetcher::PlaylistFetcher(
+        const sp<AMessage> &notify,
+        const sp<LiveSession> &session,
+        const char *uri)
+    : mNotify(notify),
+      mSession(session),
+      mURI(uri),
+      mStreamTypeMask(0),
+      mStartTimeUs(-1ll),
+      mLastPlaylistFetchTimeUs(-1ll),
+      mSeqNumber(-1),
+      mNumRetries(0),
+      mStartup(true),
+      mNextPTSTimeUs(-1ll),
+      mMonitorQueueGeneration(0),
+      mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY),
+      mFirstPTSValid(false),
+      mAbsoluteTimeAnchorUs(0ll) {
+    memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
+}
+
+PlaylistFetcher::~PlaylistFetcher() {
+}
+
+int64_t PlaylistFetcher::getSegmentStartTimeUs(int32_t seqNumber) const {
+    CHECK(mPlaylist != NULL);
+
+    int32_t firstSeqNumberInPlaylist;
+    if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+                "media-sequence", &firstSeqNumberInPlaylist)) {
+        firstSeqNumberInPlaylist = 0;
+    }
+
+    int32_t lastSeqNumberInPlaylist =
+        firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+
+    CHECK_GE(seqNumber, firstSeqNumberInPlaylist);
+    CHECK_LE(seqNumber, lastSeqNumberInPlaylist);
+
+    int64_t segmentStartUs = 0ll;
+    for (int32_t index = 0;
+            index < seqNumber - firstSeqNumberInPlaylist; ++index) {
+        sp<AMessage> itemMeta;
+        CHECK(mPlaylist->itemAt(
+                    index, NULL /* uri */, &itemMeta));
+
+        int64_t itemDurationUs;
+        CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+        segmentStartUs += itemDurationUs;
+    }
+
+    return segmentStartUs;
+}
+
+bool PlaylistFetcher::timeToRefreshPlaylist(int64_t nowUs) const {
+    if (mPlaylist == NULL) {
+        CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY);
+        return true;
+    }
+
+    int32_t targetDurationSecs;
+    CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
+
+    int64_t targetDurationUs = targetDurationSecs * 1000000ll;
+
+    int64_t minPlaylistAgeUs;
+
+    switch (mRefreshState) {
+        case INITIAL_MINIMUM_RELOAD_DELAY:
+        {
+            size_t n = mPlaylist->size();
+            if (n > 0) {
+                sp<AMessage> itemMeta;
+                CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta));
+
+                int64_t itemDurationUs;
+                CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+                minPlaylistAgeUs = itemDurationUs;
+                break;
+            }
+
+            // fall through
+        }
+
+        case FIRST_UNCHANGED_RELOAD_ATTEMPT:
+        {
+            minPlaylistAgeUs = targetDurationUs / 2;
+            break;
+        }
+
+        case SECOND_UNCHANGED_RELOAD_ATTEMPT:
+        {
+            minPlaylistAgeUs = (targetDurationUs * 3) / 2;
+            break;
+        }
+
+        case THIRD_UNCHANGED_RELOAD_ATTEMPT:
+        {
+            minPlaylistAgeUs = targetDurationUs * 3;
+            break;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+
+    return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs;
+}
+
+status_t PlaylistFetcher::decryptBuffer(
+        size_t playlistIndex, const sp<ABuffer> &buffer) {
+    sp<AMessage> itemMeta;
+    bool found = false;
+    AString method;
+
+    for (ssize_t i = playlistIndex; i >= 0; --i) {
+        AString uri;
+        CHECK(mPlaylist->itemAt(i, &uri, &itemMeta));
+
+        if (itemMeta->findString("cipher-method", &method)) {
+            found = true;
+            break;
+        }
+    }
+
+    if (!found) {
+        method = "NONE";
+    }
+
+    if (method == "NONE") {
+        return OK;
+    } else if (!(method == "AES-128")) {
+        ALOGE("Unsupported cipher method '%s'", method.c_str());
+        return ERROR_UNSUPPORTED;
+    }
+
+    AString keyURI;
+    if (!itemMeta->findString("cipher-uri", &keyURI)) {
+        ALOGE("Missing key uri");
+        return ERROR_MALFORMED;
+    }
+
+    ssize_t index = mAESKeyForURI.indexOfKey(keyURI);
+
+    sp<ABuffer> key;
+    if (index >= 0) {
+        key = mAESKeyForURI.valueAt(index);
+    } else {
+        status_t err = mSession->fetchFile(keyURI.c_str(), &key);
+
+        if (err != OK) {
+            ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str());
+            return ERROR_IO;
+        } else if (key->size() != 16) {
+            ALOGE("key file '%s' wasn't 16 bytes in size.", keyURI.c_str());
+            return ERROR_MALFORMED;
+        }
+
+        mAESKeyForURI.add(keyURI, key);
+    }
+
+    AES_KEY aes_key;
+    if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
+        ALOGE("failed to set AES decryption key.");
+        return UNKNOWN_ERROR;
+    }
+
+    unsigned char aes_ivec[16];
+
+    AString iv;
+    if (itemMeta->findString("cipher-iv", &iv)) {
+        if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
+                || iv.size() != 16 * 2 + 2) {
+            ALOGE("malformed cipher IV '%s'.", iv.c_str());
+            return ERROR_MALFORMED;
+        }
+
+        memset(aes_ivec, 0, sizeof(aes_ivec));
+        for (size_t i = 0; i < 16; ++i) {
+            char c1 = tolower(iv.c_str()[2 + 2 * i]);
+            char c2 = tolower(iv.c_str()[3 + 2 * i]);
+            if (!isxdigit(c1) || !isxdigit(c2)) {
+                ALOGE("malformed cipher IV '%s'.", iv.c_str());
+                return ERROR_MALFORMED;
+            }
+            uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
+            uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
+
+            aes_ivec[i] = nibble1 << 4 | nibble2;
+        }
+    } else {
+        memset(aes_ivec, 0, sizeof(aes_ivec));
+        aes_ivec[15] = mSeqNumber & 0xff;
+        aes_ivec[14] = (mSeqNumber >> 8) & 0xff;
+        aes_ivec[13] = (mSeqNumber >> 16) & 0xff;
+        aes_ivec[12] = (mSeqNumber >> 24) & 0xff;
+    }
+
+    AES_cbc_encrypt(
+            buffer->data(), buffer->data(), buffer->size(),
+            &aes_key, aes_ivec, AES_DECRYPT);
+
+    // hexdump(buffer->data(), buffer->size());
+
+    size_t n = buffer->size();
+    CHECK_GT(n, 0u);
+
+    size_t pad = buffer->data()[n - 1];
+
+    CHECK_GT(pad, 0u);
+    CHECK_LE(pad, 16u);
+    CHECK_GE((size_t)n, pad);
+    for (size_t i = 0; i < pad; ++i) {
+        CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad);
+    }
+
+    n -= pad;
+
+    buffer->setRange(buffer->offset(), n);
+
+    return OK;
+}
+
+void PlaylistFetcher::postMonitorQueue(int64_t delayUs) {
+    sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id());
+    msg->setInt32("generation", mMonitorQueueGeneration);
+    msg->post(delayUs);
+}
+
+void PlaylistFetcher::cancelMonitorQueue() {
+    ++mMonitorQueueGeneration;
+}
+
+void PlaylistFetcher::startAsync(
+        const sp<AnotherPacketSource> &audioSource,
+        const sp<AnotherPacketSource> &videoSource,
+        const sp<AnotherPacketSource> &subtitleSource,
+        int64_t startTimeUs) {
+    sp<AMessage> msg = new AMessage(kWhatStart, id());
+
+    uint32_t streamTypeMask = 0ul;
+
+    if (audioSource != NULL) {
+        msg->setPointer("audioSource", audioSource.get());
+        streamTypeMask |= LiveSession::STREAMTYPE_AUDIO;
+    }
+
+    if (videoSource != NULL) {
+        msg->setPointer("videoSource", videoSource.get());
+        streamTypeMask |= LiveSession::STREAMTYPE_VIDEO;
+    }
+
+    if (subtitleSource != NULL) {
+        msg->setPointer("subtitleSource", subtitleSource.get());
+        streamTypeMask |= LiveSession::STREAMTYPE_SUBTITLES;
+    }
+
+    msg->setInt32("streamTypeMask", streamTypeMask);
+    msg->setInt64("startTimeUs", startTimeUs);
+    msg->post();
+}
+
+void PlaylistFetcher::pauseAsync() {
+    (new AMessage(kWhatPause, id()))->post();
+}
+
+void PlaylistFetcher::stopAsync() {
+    (new AMessage(kWhatStop, id()))->post();
+}
+
+void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatStart:
+        {
+            status_t err = onStart(msg);
+
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatStarted);
+            notify->setInt32("err", err);
+            notify->post();
+            break;
+        }
+
+        case kWhatPause:
+        {
+            onPause();
+
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatPaused);
+            notify->post();
+            break;
+        }
+
+        case kWhatStop:
+        {
+            onStop();
+
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatStopped);
+            notify->post();
+            break;
+        }
+
+        case kWhatMonitorQueue:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mMonitorQueueGeneration) {
+                // Stale event
+                break;
+            }
+
+            onMonitorQueue();
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
+    mPacketSources.clear();
+
+    uint32_t streamTypeMask;
+    CHECK(msg->findInt32("streamTypeMask", (int32_t *)&streamTypeMask));
+
+    int64_t startTimeUs;
+    CHECK(msg->findInt64("startTimeUs", &startTimeUs));
+
+    if (streamTypeMask & LiveSession::STREAMTYPE_AUDIO) {
+        void *ptr;
+        CHECK(msg->findPointer("audioSource", &ptr));
+
+        mPacketSources.add(
+                LiveSession::STREAMTYPE_AUDIO,
+                static_cast<AnotherPacketSource *>(ptr));
+    }
+
+    if (streamTypeMask & LiveSession::STREAMTYPE_VIDEO) {
+        void *ptr;
+        CHECK(msg->findPointer("videoSource", &ptr));
+
+        mPacketSources.add(
+                LiveSession::STREAMTYPE_VIDEO,
+                static_cast<AnotherPacketSource *>(ptr));
+    }
+
+    if (streamTypeMask & LiveSession::STREAMTYPE_SUBTITLES) {
+        void *ptr;
+        CHECK(msg->findPointer("subtitleSource", &ptr));
+
+        mPacketSources.add(
+                LiveSession::STREAMTYPE_SUBTITLES,
+                static_cast<AnotherPacketSource *>(ptr));
+    }
+
+    mStreamTypeMask = streamTypeMask;
+    mStartTimeUs = startTimeUs;
+
+    if (mStartTimeUs >= 0ll) {
+        mSeqNumber = -1;
+        mStartup = true;
+    }
+
+    postMonitorQueue();
+
+    return OK;
+}
+
+void PlaylistFetcher::onPause() {
+    cancelMonitorQueue();
+
+    mPacketSources.clear();
+    mStreamTypeMask = 0;
+}
+
+void PlaylistFetcher::onStop() {
+    cancelMonitorQueue();
+
+    for (size_t i = 0; i < mPacketSources.size(); ++i) {
+        mPacketSources.valueAt(i)->clear();
+    }
+
+    mPacketSources.clear();
+    mStreamTypeMask = 0;
+}
+
+void PlaylistFetcher::notifyError(status_t err) {
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatError);
+    notify->setInt32("err", err);
+    notify->post();
+}
+
+void PlaylistFetcher::queueDiscontinuity(
+        ATSParser::DiscontinuityType type, const sp<AMessage> &extra) {
+    for (size_t i = 0; i < mPacketSources.size(); ++i) {
+        mPacketSources.valueAt(i)->queueDiscontinuity(type, extra);
+    }
+}
+
+void PlaylistFetcher::onMonitorQueue() {
+    bool downloadMore = false;
+
+    status_t finalResult;
+    if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
+        sp<AnotherPacketSource> packetSource =
+            mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
+
+        downloadMore = packetSource->hasBufferAvailable(&finalResult);
+    } else {
+        bool first = true;
+        int64_t minBufferedDurationUs = 0ll;
+
+        for (size_t i = 0; i < mPacketSources.size(); ++i) {
+            if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) {
+                continue;
+            }
+
+            int64_t bufferedDurationUs =
+                mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
+
+            if (first || bufferedDurationUs < minBufferedDurationUs) {
+                minBufferedDurationUs = bufferedDurationUs;
+                first = false;
+            }
+        }
+
+        downloadMore =
+            !first && (minBufferedDurationUs < kMinBufferedDurationUs);
+    }
+
+    if (finalResult == OK && downloadMore) {
+        onDownloadNext();
+    } else {
+        // Nothing to do yet, try again in a second.
+
+        sp<AMessage> msg = mNotify->dup();
+        msg->setInt32("what", kWhatTemporarilyDoneFetching);
+        msg->post();
+
+        postMonitorQueue(1000000ll);
+    }
+}
+
+void PlaylistFetcher::onDownloadNext() {
+    int64_t nowUs = ALooper::GetNowUs();
+
+    if (mLastPlaylistFetchTimeUs < 0ll
+            || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
+        bool unchanged;
+        sp<M3UParser> playlist = mSession->fetchPlaylist(
+                mURI.c_str(), mPlaylistHash, &unchanged);
+
+        if (playlist == NULL) {
+            if (unchanged) {
+                // We succeeded in fetching the playlist, but it was
+                // unchanged from the last time we tried.
+
+                if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) {
+                    mRefreshState = (RefreshState)(mRefreshState + 1);
+                }
+            } else {
+                ALOGE("failed to load playlist at url '%s'", mURI.c_str());
+                notifyError(ERROR_IO);
+                return;
+            }
+        } else {
+            mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
+            mPlaylist = playlist;
+
+            if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+                updateDuration();
+            }
+        }
+
+        mLastPlaylistFetchTimeUs = ALooper::GetNowUs();
+    }
+
+    int32_t firstSeqNumberInPlaylist;
+    if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+                "media-sequence", &firstSeqNumberInPlaylist)) {
+        firstSeqNumberInPlaylist = 0;
+    }
+
+    bool seekDiscontinuity = false;
+    bool explicitDiscontinuity = false;
+
+    const int32_t lastSeqNumberInPlaylist =
+        firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1;
+
+    if (mSeqNumber < 0) {
+        CHECK_GE(mStartTimeUs, 0ll);
+
+        if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+            mSeqNumber = getSeqNumberForTime(mStartTimeUs);
+        } else {
+            // If this is a live session, start 3 segments from the end.
+            mSeqNumber = lastSeqNumberInPlaylist - 3;
+            if (mSeqNumber < firstSeqNumberInPlaylist) {
+                mSeqNumber = firstSeqNumberInPlaylist;
+            }
+        }
+
+        mStartTimeUs = -1ll;
+    }
+
+    if (mSeqNumber < firstSeqNumberInPlaylist
+            || mSeqNumber > lastSeqNumberInPlaylist) {
+        if (!mPlaylist->isComplete() && mNumRetries < kMaxNumRetries) {
+            ++mNumRetries;
+
+            if (mSeqNumber > lastSeqNumberInPlaylist) {
+                mLastPlaylistFetchTimeUs = -1;
+                postMonitorQueue(3000000ll);
+                return;
+            }
+
+            // we've missed the boat, let's start from the lowest sequence
+            // number available and signal a discontinuity.
+
+            ALOGI("We've missed the boat, restarting playback.");
+            mSeqNumber = lastSeqNumberInPlaylist;
+            explicitDiscontinuity = true;
+
+            // fall through
+        } else {
+            ALOGE("Cannot find sequence number %d in playlist "
+                 "(contains %d - %d)",
+                 mSeqNumber, firstSeqNumberInPlaylist,
+                 firstSeqNumberInPlaylist + mPlaylist->size() - 1);
+
+            notifyError(ERROR_END_OF_STREAM);
+            return;
+        }
+    }
+
+    mNumRetries = 0;
+
+    AString uri;
+    sp<AMessage> itemMeta;
+    CHECK(mPlaylist->itemAt(
+                mSeqNumber - firstSeqNumberInPlaylist,
+                &uri,
+                &itemMeta));
+
+    int32_t val;
+    if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
+        explicitDiscontinuity = true;
+    }
+
+    int64_t range_offset, range_length;
+    if (!itemMeta->findInt64("range-offset", &range_offset)
+            || !itemMeta->findInt64("range-length", &range_length)) {
+        range_offset = 0;
+        range_length = -1;
+    }
+
+    ALOGV("fetching segment %d from (%d .. %d)",
+          mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
+
+    ALOGV("fetching '%s'", uri.c_str());
+
+    sp<ABuffer> buffer;
+    status_t err = mSession->fetchFile(
+            uri.c_str(), &buffer, range_offset, range_length);
+
+    if (err != OK) {
+        ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str());
+        notifyError(err);
+        return;
+    }
+
+    CHECK(buffer != NULL);
+
+    err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer);
+
+    if (err != OK) {
+        ALOGE("decryptBuffer failed w/ error %d", err);
+
+        notifyError(err);
+        return;
+    }
+
+    if (mStartup || seekDiscontinuity || explicitDiscontinuity) {
+        // Signal discontinuity.
+
+        if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+            // If this was a live event this made no sense since
+            // we don't have access to all the segment before the current
+            // one.
+            mNextPTSTimeUs = getSegmentStartTimeUs(mSeqNumber);
+        }
+
+        if (seekDiscontinuity || explicitDiscontinuity) {
+            ALOGI("queueing discontinuity (seek=%d, explicit=%d)",
+                 seekDiscontinuity, explicitDiscontinuity);
+
+            queueDiscontinuity(
+                    explicitDiscontinuity
+                        ? ATSParser::DISCONTINUITY_FORMATCHANGE
+                        : ATSParser::DISCONTINUITY_SEEK,
+                    NULL /* extra */);
+        }
+    }
+
+    err = extractAndQueueAccessUnits(buffer);
+
+    if (err != OK) {
+        notifyError(err);
+        return;
+    }
+
+    ++mSeqNumber;
+
+    postMonitorQueue();
+
+    mStartup = false;
+}
+
+int32_t PlaylistFetcher::getSeqNumberForTime(int64_t timeUs) const {
+    int32_t firstSeqNumberInPlaylist;
+    if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
+                "media-sequence", &firstSeqNumberInPlaylist)) {
+        firstSeqNumberInPlaylist = 0;
+    }
+
+    size_t index = 0;
+    int64_t segmentStartUs = 0;
+    while (index < mPlaylist->size()) {
+        sp<AMessage> itemMeta;
+        CHECK(mPlaylist->itemAt(
+                    index, NULL /* uri */, &itemMeta));
+
+        int64_t itemDurationUs;
+        CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+        if (timeUs < segmentStartUs + itemDurationUs) {
+            break;
+        }
+
+        segmentStartUs += itemDurationUs;
+        ++index;
+    }
+
+    if (index >= mPlaylist->size()) {
+        index = mPlaylist->size() - 1;
+    }
+
+    return firstSeqNumberInPlaylist + index;
+}
+
+status_t PlaylistFetcher::extractAndQueueAccessUnits(
+        const sp<ABuffer> &buffer) {
+    if (buffer->size() > 0 && buffer->data()[0] == 0x47) {
+        // Let's assume this is an MPEG2 transport stream.
+
+        if ((buffer->size() % 188) != 0) {
+            ALOGE("MPEG2 transport stream is not an even multiple of 188 "
+                  "bytes in length.");
+            return ERROR_MALFORMED;
+        }
+
+        if (mTSParser == NULL) {
+            mTSParser = new ATSParser;
+        }
+
+        if (mNextPTSTimeUs >= 0ll) {
+            sp<AMessage> extra = new AMessage;
+            extra->setInt64(IStreamListener::kKeyMediaTimeUs, mNextPTSTimeUs);
+
+            mTSParser->signalDiscontinuity(
+                    ATSParser::DISCONTINUITY_SEEK, extra);
+
+            mNextPTSTimeUs = -1ll;
+        }
+
+        size_t offset = 0;
+        while (offset < buffer->size()) {
+            status_t err = mTSParser->feedTSPacket(buffer->data() + offset, 188);
+
+            if (err != OK) {
+                return err;
+            }
+
+            offset += 188;
+        }
+
+        for (size_t i = mPacketSources.size(); i-- > 0;) {
+            sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i);
+
+            ATSParser::SourceType type;
+            switch (mPacketSources.keyAt(i)) {
+                case LiveSession::STREAMTYPE_VIDEO:
+                    type = ATSParser::VIDEO;
+                    break;
+
+                case LiveSession::STREAMTYPE_AUDIO:
+                    type = ATSParser::AUDIO;
+                    break;
+
+                case LiveSession::STREAMTYPE_SUBTITLES:
+                {
+                    ALOGE("MPEG2 Transport streams do not contain subtitles.");
+                    return ERROR_MALFORMED;
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+
+            sp<AnotherPacketSource> source =
+                static_cast<AnotherPacketSource *>(
+                        mTSParser->getSource(type).get());
+
+            if (source == NULL) {
+                ALOGW("MPEG2 Transport stream does not contain %s data.",
+                      type == ATSParser::VIDEO ? "video" : "audio");
+
+                mStreamTypeMask &= ~mPacketSources.keyAt(i);
+                mPacketSources.removeItemsAt(i);
+                continue;
+            }
+
+            sp<ABuffer> accessUnit;
+            status_t finalResult;
+            while (source->hasBufferAvailable(&finalResult)
+                    && source->dequeueAccessUnit(&accessUnit) == OK) {
+                // Note that we do NOT dequeue any discontinuities.
+
+                packetSource->queueAccessUnit(accessUnit);
+            }
+
+            if (packetSource->getFormat() == NULL) {
+                packetSource->setFormat(source->getFormat());
+            }
+        }
+
+        return OK;
+    } else if (buffer->size() >= 7 && !memcmp("WEBVTT\n", buffer->data(), 7)) {
+        if (mStreamTypeMask != LiveSession::STREAMTYPE_SUBTITLES) {
+            ALOGE("This stream only contains subtitles.");
+            return ERROR_MALFORMED;
+        }
+
+        const sp<AnotherPacketSource> packetSource =
+            mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
+
+        buffer->meta()->setInt64("timeUs", 0ll);
+
+        packetSource->queueAccessUnit(buffer);
+        return OK;
+    }
+
+    if (mNextPTSTimeUs >= 0ll) {
+        mFirstPTSValid = false;
+        mAbsoluteTimeAnchorUs = mNextPTSTimeUs;
+        mNextPTSTimeUs = -1ll;
+    }
+
+    // This better be an ISO 13818-7 (AAC) or ISO 13818-1 (MPEG) audio
+    // stream prefixed by an ID3 tag.
+
+    bool firstID3Tag = true;
+    uint64_t PTS = 0;
+
+    for (;;) {
+        // Make sure to skip all ID3 tags preceding the audio data.
+        // At least one must be present to provide the PTS timestamp.
+
+        ID3 id3(buffer->data(), buffer->size(), true /* ignoreV1 */);
+        if (!id3.isValid()) {
+            if (firstID3Tag) {
+                ALOGE("Unable to parse ID3 tag.");
+                return ERROR_MALFORMED;
+            } else {
+                break;
+            }
+        }
+
+        if (firstID3Tag) {
+            bool found = false;
+
+            ID3::Iterator it(id3, "PRIV");
+            while (!it.done()) {
+                size_t length;
+                const uint8_t *data = it.getData(&length);
+
+                static const char *kMatchName =
+                    "com.apple.streaming.transportStreamTimestamp";
+                static const size_t kMatchNameLen = strlen(kMatchName);
+
+                if (length == kMatchNameLen + 1 + 8
+                        && !strncmp((const char *)data, kMatchName, kMatchNameLen)) {
+                    found = true;
+                    PTS = U64_AT(&data[kMatchNameLen + 1]);
+                }
+
+                it.next();
+            }
+
+            if (!found) {
+                ALOGE("Unable to extract transportStreamTimestamp from ID3 tag.");
+                return ERROR_MALFORMED;
+            }
+        }
+
+        // skip the ID3 tag
+        buffer->setRange(
+                buffer->offset() + id3.rawSize(), buffer->size() - id3.rawSize());
+
+        firstID3Tag = false;
+    }
+
+    if (!mFirstPTSValid) {
+        mFirstPTSValid = true;
+        mFirstPTS = PTS;
+    }
+    PTS -= mFirstPTS;
+
+    int64_t timeUs = (PTS * 100ll) / 9ll + mAbsoluteTimeAnchorUs;
+
+    if (mStreamTypeMask != LiveSession::STREAMTYPE_AUDIO) {
+        ALOGW("This stream only contains audio data!");
+
+        mStreamTypeMask &= LiveSession::STREAMTYPE_AUDIO;
+
+        if (mStreamTypeMask == 0) {
+            return OK;
+        }
+    }
+
+    sp<AnotherPacketSource> packetSource =
+        mPacketSources.valueFor(LiveSession::STREAMTYPE_AUDIO);
+
+    if (packetSource->getFormat() == NULL && buffer->size() >= 7) {
+        ABitReader bits(buffer->data(), buffer->size());
+
+        // adts_fixed_header
+
+        CHECK_EQ(bits.getBits(12), 0xfffu);
+        bits.skipBits(3);  // ID, layer
+        bool protection_absent = bits.getBits(1) != 0;
+
+        unsigned profile = bits.getBits(2);
+        CHECK_NE(profile, 3u);
+        unsigned sampling_freq_index = bits.getBits(4);
+        bits.getBits(1);  // private_bit
+        unsigned channel_configuration = bits.getBits(3);
+        CHECK_NE(channel_configuration, 0u);
+        bits.skipBits(2);  // original_copy, home
+
+        sp<MetaData> meta = MakeAACCodecSpecificData(
+                profile, sampling_freq_index, channel_configuration);
+
+        meta->setInt32(kKeyIsADTS, true);
+
+        packetSource->setFormat(meta);
+    }
+
+    int64_t numSamples = 0ll;
+    int32_t sampleRate;
+    CHECK(packetSource->getFormat()->findInt32(kKeySampleRate, &sampleRate));
+
+    size_t offset = 0;
+    while (offset < buffer->size()) {
+        const uint8_t *adtsHeader = buffer->data() + offset;
+        CHECK_LT(offset + 5, buffer->size());
+
+        unsigned aac_frame_length =
+            ((adtsHeader[3] & 3) << 11)
+            | (adtsHeader[4] << 3)
+            | (adtsHeader[5] >> 5);
+
+        CHECK_LE(offset + aac_frame_length, buffer->size());
+
+        sp<ABuffer> unit = new ABuffer(aac_frame_length);
+        memcpy(unit->data(), adtsHeader, aac_frame_length);
+
+        int64_t unitTimeUs = timeUs + numSamples * 1000000ll / sampleRate;
+        unit->meta()->setInt64("timeUs", unitTimeUs);
+
+        // Each AAC frame encodes 1024 samples.
+        numSamples += 1024;
+
+        packetSource->queueAccessUnit(unit);
+
+        offset += aac_frame_length;
+    }
+
+    return OK;
+}
+
+void PlaylistFetcher::updateDuration() {
+    int64_t durationUs = 0ll;
+    for (size_t index = 0; index < mPlaylist->size(); ++index) {
+        sp<AMessage> itemMeta;
+        CHECK(mPlaylist->itemAt(
+                    index, NULL /* uri */, &itemMeta));
+
+        int64_t itemDurationUs;
+        CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+        durationUs += itemDurationUs;
+    }
+
+    sp<AMessage> msg = mNotify->dup();
+    msg->setInt32("what", kWhatDurationUpdate);
+    msg->setInt64("durationUs", durationUs);
+    msg->post();
+}
+
+}  // namespace android
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
new file mode 100644 (file)
index 0000000..5a2b901
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2012 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 PLAYLIST_FETCHER_H_
+
+#define PLAYLIST_FETCHER_H_
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include "mpeg2ts/ATSParser.h"
+#include "LiveSession.h"
+
+namespace android {
+
+struct ABuffer;
+struct AnotherPacketSource;
+struct DataSource;
+struct HTTPBase;
+struct LiveDataSource;
+struct M3UParser;
+struct String8;
+
+struct PlaylistFetcher : public AHandler {
+    enum {
+        kWhatStarted,
+        kWhatPaused,
+        kWhatStopped,
+        kWhatError,
+        kWhatDurationUpdate,
+        kWhatTemporarilyDoneFetching,
+        kWhatPrepared,
+        kWhatPreparationFailed,
+    };
+
+    PlaylistFetcher(
+            const sp<AMessage> &notify,
+            const sp<LiveSession> &session,
+            const char *uri);
+
+    sp<DataSource> getDataSource();
+
+    void startAsync(
+            const sp<AnotherPacketSource> &audioSource,
+            const sp<AnotherPacketSource> &videoSource,
+            const sp<AnotherPacketSource> &subtitleSource,
+            int64_t startTimeUs = -1ll);
+
+    void pauseAsync();
+
+    void stopAsync();
+
+protected:
+    virtual ~PlaylistFetcher();
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum {
+        kMaxNumRetries         = 5,
+    };
+
+    enum {
+        kWhatStart          = 'strt',
+        kWhatPause          = 'paus',
+        kWhatStop           = 'stop',
+        kWhatMonitorQueue   = 'moni',
+    };
+
+    static const int64_t kMinBufferedDurationUs;
+
+    sp<AMessage> mNotify;
+    sp<LiveSession> mSession;
+    AString mURI;
+
+    uint32_t mStreamTypeMask;
+    int64_t mStartTimeUs;
+
+    KeyedVector<LiveSession::StreamType, sp<AnotherPacketSource> >
+        mPacketSources;
+
+    KeyedVector<AString, sp<ABuffer> > mAESKeyForURI;
+
+    int64_t mLastPlaylistFetchTimeUs;
+    sp<M3UParser> mPlaylist;
+    int32_t mSeqNumber;
+    int32_t mNumRetries;
+    bool mStartup;
+    int64_t mNextPTSTimeUs;
+
+    int32_t mMonitorQueueGeneration;
+
+    enum RefreshState {
+        INITIAL_MINIMUM_RELOAD_DELAY,
+        FIRST_UNCHANGED_RELOAD_ATTEMPT,
+        SECOND_UNCHANGED_RELOAD_ATTEMPT,
+        THIRD_UNCHANGED_RELOAD_ATTEMPT
+    };
+    RefreshState mRefreshState;
+
+    uint8_t mPlaylistHash[16];
+
+    sp<ATSParser> mTSParser;
+
+    bool mFirstPTSValid;
+    uint64_t mFirstPTS;
+    int64_t mAbsoluteTimeAnchorUs;
+
+    status_t decryptBuffer(
+            size_t playlistIndex, const sp<ABuffer> &buffer);
+
+    void postMonitorQueue(int64_t delayUs = 0);
+    void cancelMonitorQueue();
+
+    bool timeToRefreshPlaylist(int64_t nowUs) const;
+
+    // Returns the media time in us of the segment specified by seqNumber.
+    // This is computed by summing the durations of all segments before it.
+    int64_t getSegmentStartTimeUs(int32_t seqNumber) const;
+
+    status_t onStart(const sp<AMessage> &msg);
+    void onPause();
+    void onStop();
+    void onMonitorQueue();
+    void onDownloadNext();
+
+    status_t extractAndQueueAccessUnits(const sp<ABuffer> &buffer);
+
+    void notifyError(status_t err);
+
+    void queueDiscontinuity(
+            ATSParser::DiscontinuityType type, const sp<AMessage> &extra);
+
+    int32_t getSeqNumberForTime(int64_t timeUs) const;
+
+    void updateDuration();
+
+    DISALLOW_EVIL_CONSTRUCTORS(PlaylistFetcher);
+};
+
+}  // namespace android
+
+#endif  // PLAYLIST_FETCHER_H_
+
index 22c2f5a..8d3013b 100644 (file)
@@ -30,12 +30,55 @@ namespace android {
 
 static const size_t kMaxMetadataSize = 3 * 1024 * 1024;
 
+struct MemorySource : public DataSource {
+    MemorySource(const uint8_t *data, size_t size)
+        : mData(data),
+          mSize(size) {
+    }
+
+    virtual status_t initCheck() const {
+        return OK;
+    }
+
+    virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
+        off64_t available = (offset >= mSize) ? 0ll : mSize - offset;
+
+        size_t copy = (available > size) ? size : available;
+        memcpy(data, mData + offset, copy);
+
+        return copy;
+    }
+
+private:
+    const uint8_t *mData;
+    size_t mSize;
+
+    DISALLOW_EVIL_CONSTRUCTORS(MemorySource);
+};
+
 ID3::ID3(const sp<DataSource> &source, bool ignoreV1)
     : mIsValid(false),
       mData(NULL),
       mSize(0),
       mFirstFrameOffset(0),
-      mVersion(ID3_UNKNOWN) {
+      mVersion(ID3_UNKNOWN),
+      mRawSize(0) {
+    mIsValid = parseV2(source);
+
+    if (!mIsValid && !ignoreV1) {
+        mIsValid = parseV1(source);
+    }
+}
+
+ID3::ID3(const uint8_t *data, size_t size, bool ignoreV1)
+    : mIsValid(false),
+      mData(NULL),
+      mSize(0),
+      mFirstFrameOffset(0),
+      mVersion(ID3_UNKNOWN),
+      mRawSize(0) {
+    sp<MemorySource> source = new MemorySource(data, size);
+
     mIsValid = parseV2(source);
 
     if (!mIsValid && !ignoreV1) {
@@ -140,6 +183,7 @@ struct id3_header {
     }
 
     mSize = size;
+    mRawSize = mSize + sizeof(header);
 
     if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) {
         free(mData);
@@ -505,7 +549,7 @@ void ID3::Iterator::getstring(String8 *id, bool otherdata) const {
         int32_t i = n - 4;
         while(--i >= 0 && *++frameData != 0) ;
         int skipped = (frameData - mFrameData);
-        if (skipped >= n) {
+        if (skipped >= (int)n) {
             return;
         }
         n -= skipped;
index 3028f56..cca83ab 100644 (file)
@@ -36,6 +36,7 @@ struct ID3 {
     };
 
     ID3(const sp<DataSource> &source, bool ignoreV1 = false);
+    ID3(const uint8_t *data, size_t size, bool ignoreV1 = false);
     ~ID3();
 
     bool isValid() const;
@@ -71,6 +72,8 @@ struct ID3 {
         Iterator &operator=(const Iterator &);
     };
 
+    size_t rawSize() const { return mRawSize; }
+
 private:
     bool mIsValid;
     uint8_t *mData;
@@ -78,6 +81,10 @@ private:
     size_t mFirstFrameOffset;
     Version mVersion;
 
+    // size of the ID3 tag including header before any unsynchronization.
+    // only valid for IDV2+
+    size_t mRawSize;
+
     bool parseV1(const sp<DataSource> &source);
     bool parseV2(const sp<DataSource> &source);
     void removeUnsynchronization();
index fe74a42..c5e86a6 100644 (file)
@@ -31,7 +31,6 @@ struct ATSParser;
 struct DataSource;
 struct MPEG2TSSource;
 struct String8;
-struct LiveSession;
 
 struct MPEG2TSExtractor : public MediaExtractor {
     MPEG2TSExtractor(const sp<DataSource> &source);
@@ -44,16 +43,12 @@ struct MPEG2TSExtractor : public MediaExtractor {
 
     virtual uint32_t flags() const;
 
-    void setLiveSession(const sp<LiveSession> &liveSession);
-    void seekTo(int64_t seekTimeUs);
-
 private:
     friend struct MPEG2TSSource;
 
     mutable Mutex mLock;
 
     sp<DataSource> mDataSource;
-    sp<LiveSession> mLiveSession;
 
     sp<ATSParser> mParser;
 
index 3de3a61..3153c8b 100644 (file)
@@ -32,9 +32,22 @@ const int64_t kNearEOSMarkUs = 2000000ll; // 2 secs
 
 AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
     : mIsAudio(false),
-      mFormat(meta),
+      mFormat(NULL),
       mLastQueuedTimeUs(0),
       mEOSResult(OK) {
+    setFormat(meta);
+}
+
+void AnotherPacketSource::setFormat(const sp<MetaData> &meta) {
+    CHECK(mFormat == NULL);
+
+    mIsAudio = false;
+
+    if (meta == NULL) {
+        return;
+    }
+
+    mFormat = meta;
     const char *mime;
     CHECK(meta->findCString(kKeyMIMEType, &mime));
 
@@ -45,11 +58,6 @@ AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
     }
 }
 
-void AnotherPacketSource::setFormat(const sp<MetaData> &meta) {
-    CHECK(mFormat == NULL);
-    mFormat = meta;
-}
-
 AnotherPacketSource::~AnotherPacketSource() {
 }
 
@@ -152,6 +160,15 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
     mCondition.signal();
 }
 
+void AnotherPacketSource::clear() {
+    Mutex::Autolock autoLock(mLock);
+
+    mBuffers.clear();
+    mEOSResult = OK;
+
+    mFormat = NULL;
+}
+
 void AnotherPacketSource::queueDiscontinuity(
         ATSParser::DiscontinuityType type,
         const sp<AMessage> &extra) {
index 1db4068..e16cf78 100644 (file)
@@ -41,6 +41,8 @@ struct AnotherPacketSource : public MediaSource {
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL);
 
+    void clear();
+
     bool hasBufferAvailable(status_t *finalResult);
 
     // Returns the difference between the last and the first queued
index e1589b4..d449c34 100644 (file)
@@ -19,7 +19,6 @@
 #include <utils/Log.h>
 
 #include "include/MPEG2TSExtractor.h"
-#include "include/LiveSession.h"
 #include "include/NuCachedSource2.h"
 
 #include <media/stagefright/foundation/ADebug.h>
@@ -79,15 +78,7 @@ status_t MPEG2TSSource::stop() {
 }
 
 sp<MetaData> MPEG2TSSource::getFormat() {
-    sp<MetaData> meta = mImpl->getFormat();
-
-    int64_t durationUs;
-    if (mExtractor->mLiveSession != NULL
-            && mExtractor->mLiveSession->getDuration(&durationUs) == OK) {
-        meta->setInt64(kKeyDuration, durationUs);
-    }
-
-    return meta;
+    return mImpl->getFormat();
 }
 
 status_t MPEG2TSSource::read(
@@ -97,7 +88,7 @@ status_t MPEG2TSSource::read(
     int64_t seekTimeUs;
     ReadOptions::SeekMode seekMode;
     if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) {
-        mExtractor->seekTo(seekTimeUs);
+        return ERROR_UNSUPPORTED;
     }
 
     status_t finalResult;
@@ -216,32 +207,8 @@ status_t MPEG2TSExtractor::feedMore() {
     return mParser->feedTSPacket(packet, kTSPacketSize);
 }
 
-void MPEG2TSExtractor::setLiveSession(const sp<LiveSession> &liveSession) {
-    Mutex::Autolock autoLock(mLock);
-
-    mLiveSession = liveSession;
-}
-
-void MPEG2TSExtractor::seekTo(int64_t seekTimeUs) {
-    Mutex::Autolock autoLock(mLock);
-
-    if (mLiveSession == NULL) {
-        return;
-    }
-
-    mLiveSession->seekTo(seekTimeUs);
-}
-
 uint32_t MPEG2TSExtractor::flags() const {
-    Mutex::Autolock autoLock(mLock);
-
-    uint32_t flags = CAN_PAUSE;
-
-    if (mLiveSession != NULL && mLiveSession->isSeekable()) {
-        flags |= CAN_SEEK_FORWARD | CAN_SEEK_BACKWARD | CAN_SEEK;
-    }
-
-    return flags;
+    return CAN_PAUSE;
 }
 
 ////////////////////////////////////////////////////////////////////////////////