Seek/Duration support for completed http live streams in NuPlayer.

Change-Id: I55bbe75d87140c07b1927d14ad24130fce803463
related-to-bug: 3321475
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 178f0db..88b0c3e 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -36,7 +36,6 @@
     MEDIA_BUFFERING_UPDATE  = 3,
     MEDIA_SEEK_COMPLETE     = 4,
     MEDIA_SET_VIDEO_SIZE    = 5,
-    MEDIA_RESET_COMPLETE    = 6,  // not visible on java side
     MEDIA_ERROR             = 100,
     MEDIA_INFO              = 200,
 };
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index 29e4971..b81e0e9 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -128,5 +128,25 @@
     return source->dequeueAccessUnit(accessUnit);
 }
 
+status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) {
+    return mLiveSession->getDuration(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()) {
+        usleep(100000);
+    }
+
+    mLiveSession->seekTo(seekTimeUs);
+
+    return OK;
+}
+
+bool NuPlayer::HTTPLiveSource::isSeekable() {
+    return mLiveSession->isSeekable();
+}
+
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
index 1b97699..f3f539a 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
@@ -29,13 +29,17 @@
 struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
     HTTPLiveSource(const char *url);
 
-    void start();
+    virtual void start();
 
     // Returns true iff more data was available, false on EOS.
-    bool feedMoreTSData();
+    virtual bool feedMoreTSData();
 
-    sp<MetaData> getFormat(bool audio);
-    status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+    virtual sp<MetaData> getFormat(bool audio);
+    virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+
+    virtual status_t getDuration(int64_t *durationUs);
+    virtual status_t seekTo(int64_t seekTimeUs);
+    virtual bool isSeekable();
 
 protected:
     virtual ~HTTPLiveSource();
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 9a8cff3..a607b31 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -22,6 +22,7 @@
 
 #include "HTTPLiveSource.h"
 #include "NuPlayerDecoder.h"
+#include "NuPlayerDriver.h"
 #include "NuPlayerRenderer.h"
 #include "NuPlayerSource.h"
 #include "StreamingSource.h"
@@ -55,8 +56,8 @@
 NuPlayer::~NuPlayer() {
 }
 
-void NuPlayer::setListener(const wp<MediaPlayerBase> &listener) {
-    mListener = listener;
+void NuPlayer::setDriver(const wp<NuPlayerDriver> &driver) {
+    mDriver = driver;
 }
 
 void NuPlayer::setDataSource(const sp<IStreamSource> &source) {
@@ -90,10 +91,24 @@
     (new AMessage(kWhatStart, id()))->post();
 }
 
+void NuPlayer::pause() {
+    // XXX to be implemented
+}
+
+void NuPlayer::resume() {
+    // XXX to be implemented
+}
+
 void NuPlayer::resetAsync() {
     (new AMessage(kWhatReset, id()))->post();
 }
 
+void NuPlayer::seekToAsync(int64_t seekTimeUs) {
+    sp<AMessage> msg = new AMessage(kWhatSeek, id());
+    msg->setInt64("seekTimeUs", seekTimeUs);
+    msg->post();
+}
+
 // static
 bool NuPlayer::IsFlushingState(FlushStatus state, bool *needShutdown) {
     switch (state) {
@@ -153,6 +168,8 @@
 
         case kWhatStart:
         {
+            LOGV("kWhatStart");
+
             mAudioEOS = false;
             mVideoEOS = false;
 
@@ -179,6 +196,9 @@
 
             mScanSourcesPending = false;
 
+            LOGV("scanning sources haveAudio=%d, haveVideo=%d",
+                 mAudioDecoder != NULL, mVideoDecoder != NULL);
+
             instantiateDecoder(false, &mVideoDecoder);
 
             if (mAudioSink != NULL) {
@@ -312,6 +332,16 @@
                         && (mVideoEOS || mVideoDecoder == NULL)) {
                     notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
                 }
+            } else if (what == Renderer::kWhatPosition) {
+                int64_t positionUs;
+                CHECK(msg->findInt64("positionUs", &positionUs));
+
+                if (mDriver != NULL) {
+                    sp<NuPlayerDriver> driver = mDriver.promote();
+                    if (driver != NULL) {
+                        driver->notifyPosition(positionUs);
+                    }
+                }
             } else {
                 CHECK_EQ(what, (int32_t)Renderer::kWhatFlushComplete);
 
@@ -359,6 +389,26 @@
             break;
         }
 
+        case kWhatSeek:
+        {
+            int64_t seekTimeUs;
+            CHECK(msg->findInt64("seekTimeUs", &seekTimeUs));
+
+            LOGI("kWhatSeek seekTimeUs=%lld us (%.2f secs)",
+                 seekTimeUs, seekTimeUs / 1E6);
+
+            mSource->seekTo(seekTimeUs);
+
+            if (mDriver != NULL) {
+                sp<NuPlayerDriver> driver = mDriver.promote();
+                if (driver != NULL) {
+                    driver->notifySeekComplete();
+                }
+            }
+
+            break;
+        }
+
         default:
             TRESPASS();
             break;
@@ -415,7 +465,12 @@
     mRenderer.clear();
     mSource.clear();
 
-    notifyListener(MEDIA_RESET_COMPLETE, 0, 0);
+    if (mDriver != NULL) {
+        sp<NuPlayerDriver> driver = mDriver.promote();
+        if (driver != NULL) {
+            driver->notifyResetComplete();
+        }
+    }
 }
 
 void NuPlayer::postScanSources() {
@@ -450,6 +505,14 @@
 
     (*decoder)->configure(meta);
 
+    int64_t durationUs;
+    if (mDriver != NULL && mSource->getDuration(&durationUs) == OK) {
+        sp<NuPlayerDriver> driver = mDriver.promote();
+        if (driver != NULL) {
+            driver->notifyDuration(durationUs);
+        }
+    }
+
     return OK;
 }
 
@@ -488,7 +551,7 @@
         return OK;
     }
 
-    LOGV("returned a valid buffer of %s data", audio ? "audio" : "video");
+    // LOGV("returned a valid buffer of %s data", audio ? "audio" : "video");
 
 #if 0
     int64_t mediaTimeUs;
@@ -505,7 +568,7 @@
 }
 
 void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) {
-    LOGV("renderBuffer %s", audio ? "audio" : "video");
+    // LOGV("renderBuffer %s", audio ? "audio" : "video");
 
     sp<AMessage> reply;
     CHECK(msg->findMessage("reply", &reply));
@@ -519,22 +582,23 @@
 }
 
 void NuPlayer::notifyListener(int msg, int ext1, int ext2) {
-    if (mListener == NULL) {
+    if (mDriver == NULL) {
         return;
     }
 
-    sp<MediaPlayerBase> listener = mListener.promote();
+    sp<NuPlayerDriver> driver = mDriver.promote();
 
-    if (listener == NULL) {
+    if (driver == NULL) {
         return;
     }
 
-    listener->sendEvent(msg, ext1, ext2);
+    driver->sendEvent(msg, ext1, ext2);
 }
 
 void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
     // Make sure we don't continue to scan sources until we finish flushing.
     ++mScanSourcesGeneration;
+    mScanSourcesPending = false;
 
     (audio ? mAudioDecoder : mVideoDecoder)->signalFlush();
     mRenderer->flush(audio);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 1cf2f60..339b628 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -25,11 +25,12 @@
 
 struct ACodec;
 struct MetaData;
+struct NuPlayerDriver;
 
 struct NuPlayer : public AHandler {
     NuPlayer();
 
-    void setListener(const wp<MediaPlayerBase> &listener);
+    void setDriver(const wp<NuPlayerDriver> &driver);
 
     void setDataSource(const sp<IStreamSource> &source);
 
@@ -40,10 +41,15 @@
     void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink);
     void start();
 
-    // Will notify the listener that reset() has completed
-    // with code MEDIA_RESET_COMPLETE.
+    void pause();
+    void resume();
+
+    // Will notify the driver through "notifyResetComplete" once finished.
     void resetAsync();
 
+    // Will notify the driver through "notifySeekComplete" once finished.
+    void seekToAsync(int64_t seekTimeUs);
+
 protected:
     virtual ~NuPlayer();
 
@@ -68,9 +74,10 @@
         kWhatAudioNotify,
         kWhatRendererNotify,
         kWhatReset,
+        kWhatSeek,
     };
 
-    wp<MediaPlayerBase> mListener;
+    wp<NuPlayerDriver> mDriver;
     sp<Source> mSource;
     sp<Surface> mSurface;
     sp<MediaPlayerBase::AudioSink> mAudioSink;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 4988d24..ac19a2f 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -29,8 +29,11 @@
 
 NuPlayerDriver::NuPlayerDriver()
     : mResetInProgress(false),
+      mDurationUs(-1),
+      mPositionUs(-1),
       mLooper(new ALooper),
-      mPlayer(false) {
+      mState(UNINITIALIZED),
+      mStartupSeekTimeUs(-1) {
     mLooper->setName("NuPlayerDriver Looper");
 
     mLooper->start(
@@ -41,7 +44,7 @@
     mPlayer = new NuPlayer;
     mLooper->registerHandler(mPlayer);
 
-    mPlayer->setListener(this);
+    mPlayer->setDriver(this);
 }
 
 NuPlayerDriver::~NuPlayerDriver() {
@@ -54,8 +57,12 @@
 
 status_t NuPlayerDriver::setDataSource(
         const char *url, const KeyedVector<String8, String8> *headers) {
+    CHECK_EQ((int)mState, (int)UNINITIALIZED);
+
     mPlayer->setDataSource(url, headers);
 
+    mState = STOPPED;
+
     return OK;
 }
 
@@ -64,8 +71,12 @@
 }
 
 status_t NuPlayerDriver::setDataSource(const sp<IStreamSource> &source) {
+    CHECK_EQ((int)mState, (int)UNINITIALIZED);
+
     mPlayer->setDataSource(source);
 
+    mState = STOPPED;
+
     return OK;
 }
 
@@ -86,38 +97,110 @@
 }
 
 status_t NuPlayerDriver::start() {
-    mPlayer->start();
-    mPlaying = true;
+    switch (mState) {
+        case UNINITIALIZED:
+            return INVALID_OPERATION;
+        case STOPPED:
+        {
+            mPlayer->start();
+
+            if (mStartupSeekTimeUs >= 0) {
+                mPlayer->seekToAsync(mStartupSeekTimeUs);
+                mStartupSeekTimeUs = -1;
+            }
+            break;
+        }
+        case PLAYING:
+            return OK;
+        default:
+        {
+            CHECK_EQ((int)mState, (int)PAUSED);
+
+            mPlayer->resume();
+            break;
+        }
+    }
+
+    mState = PLAYING;
 
     return OK;
 }
 
 status_t NuPlayerDriver::stop() {
-    mPlaying = false;
-    return OK;
+    return pause();
 }
 
 status_t NuPlayerDriver::pause() {
-    mPlaying = false;
+    switch (mState) {
+        case UNINITIALIZED:
+            return INVALID_OPERATION;
+        case STOPPED:
+            return OK;
+        case PLAYING:
+            mPlayer->pause();
+            break;
+        default:
+        {
+            CHECK_EQ((int)mState, (int)PAUSED);
+            return OK;
+        }
+    }
+
+    mState = PAUSED;
+
     return OK;
 }
 
 bool NuPlayerDriver::isPlaying() {
-    return mPlaying;
+    return mState == PLAYING;
 }
 
 status_t NuPlayerDriver::seekTo(int msec) {
-    return INVALID_OPERATION;
+    int64_t seekTimeUs = msec * 1000ll;
+
+    switch (mState) {
+        case UNINITIALIZED:
+            return INVALID_OPERATION;
+        case STOPPED:
+        {
+            mStartupSeekTimeUs = seekTimeUs;
+            break;
+        }
+        case PLAYING:
+        case PAUSED:
+        {
+            mPlayer->seekToAsync(seekTimeUs);
+            break;
+        }
+
+        default:
+            TRESPASS();
+            break;
+    }
+
+    return OK;
 }
 
 status_t NuPlayerDriver::getCurrentPosition(int *msec) {
-    *msec = 0;
+    Mutex::Autolock autoLock(mLock);
+
+    if (mPositionUs < 0) {
+        *msec = 0;
+    } else {
+        *msec = (mPositionUs + 500ll) / 1000;
+    }
 
     return OK;
 }
 
 status_t NuPlayerDriver::getDuration(int *msec) {
-    *msec = 0;
+    Mutex::Autolock autoLock(mLock);
+
+    if (mDurationUs < 0) {
+        *msec = 0;
+    } else {
+        *msec = (mDurationUs + 500ll) / 1000;
+    }
 
     return OK;
 }
@@ -132,6 +215,11 @@
         mCondition.wait(mLock);
     }
 
+    mDurationUs = -1;
+    mPositionUs = -1;
+    mState = UNINITIALIZED;
+    mStartupSeekTimeUs = -1;
+
     return OK;
 }
 
@@ -156,16 +244,25 @@
     return INVALID_OPERATION;
 }
 
-void NuPlayerDriver::sendEvent(int msg, int ext1, int ext2) {
-    if (msg != MEDIA_RESET_COMPLETE) {
-        MediaPlayerInterface::sendEvent(msg, ext1, ext2);
-        return;
-    }
-
+void NuPlayerDriver::notifyResetComplete() {
     Mutex::Autolock autoLock(mLock);
     CHECK(mResetInProgress);
     mResetInProgress = false;
     mCondition.broadcast();
 }
 
+void NuPlayerDriver::notifyDuration(int64_t durationUs) {
+    Mutex::Autolock autoLock(mLock);
+    mDurationUs = durationUs;
+}
+
+void NuPlayerDriver::notifyPosition(int64_t positionUs) {
+    Mutex::Autolock autoLock(mLock);
+    mPositionUs = positionUs;
+}
+
+void NuPlayerDriver::notifySeekComplete() {
+    sendEvent(MEDIA_SEEK_COMPLETE);
+}
+
 }  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index f153af4..e3a5de4 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -54,7 +54,10 @@
     virtual status_t getMetadata(
             const media::Metadata::Filter& ids, Parcel *records);
 
-    virtual void sendEvent(int msg, int ext1 = 0, int ext2 = 0);
+    void notifyResetComplete();
+    void notifyDuration(int64_t durationUs);
+    void notifyPosition(int64_t positionUs);
+    void notifySeekComplete();
 
 protected:
     virtual ~NuPlayerDriver();
@@ -62,11 +65,27 @@
 private:
     Mutex mLock;
     Condition mCondition;
+
+    // The following are protected through "mLock"
+    // >>>
     bool mResetInProgress;
+    int64_t mDurationUs;
+    int64_t mPositionUs;
+    // <<<
 
     sp<ALooper> mLooper;
     sp<NuPlayer> mPlayer;
-    bool mPlaying;
+
+    enum State {
+        UNINITIALIZED,
+        STOPPED,
+        PLAYING,
+        PAUSED
+    };
+
+    State mState;
+
+    int64_t mStartupSeekTimeUs;
 
     DISALLOW_EVIL_CONSTRUCTORS(NuPlayerDriver);
 };
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 00cbec2..5833697 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -249,6 +249,8 @@
         numBytesAvailableToWrite -= copy;
         mNumFramesWritten += copy / mAudioSink->frameSize();
     }
+
+    notifyPosition();
 }
 
 void NuPlayer::Renderer::postDrainVideoQueue() {
@@ -322,6 +324,8 @@
     entry->mNotifyConsumed->post();
     mVideoQueue.erase(mVideoQueue.begin());
     entry = NULL;
+
+    notifyPosition();
 }
 
 void NuPlayer::Renderer::notifyEOS(bool audio) {
@@ -510,5 +514,19 @@
     mNumFramesWritten = 0;
 }
 
+void NuPlayer::Renderer::notifyPosition() {
+    if (mAnchorTimeRealUs < 0 || mAnchorTimeMediaUs < 0) {
+        return;
+    }
+
+    int64_t nowUs = ALooper::GetNowUs();
+    int64_t positionUs = (nowUs - mAnchorTimeRealUs) + mAnchorTimeMediaUs;
+
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", kWhatPosition);
+    notify->setInt64("positionUs", positionUs);
+    notify->post();
+}
+
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
index fb3903c..dbf3ecf 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h
@@ -44,6 +44,7 @@
     enum {
         kWhatEOS,
         kWhatFlushComplete,
+        kWhatPosition,
     };
 
 protected:
@@ -103,6 +104,7 @@
 
     void notifyEOS(bool audio);
     void notifyFlushComplete(bool audio);
+    void notifyPosition();
 
     void flushQueue(List<QueueEntry> *queue);
     bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
index 044e202..5e55487 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h
@@ -37,6 +37,18 @@
     virtual status_t dequeueAccessUnit(
             bool audio, sp<ABuffer> *accessUnit) = 0;
 
+    virtual status_t getDuration(int64_t *durationUs) {
+        return INVALID_OPERATION;
+    }
+
+    virtual status_t seekTo(int64_t seekTimeUs) {
+        return INVALID_OPERATION;
+    }
+
+    virtual bool isSeekable() {
+        return false;
+    }
+
 protected:
     virtual ~Source() {}
 
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.h b/media/libmediaplayerservice/nuplayer/StreamingSource.h
index 5f0a9dd..7abce84 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.h
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.h
@@ -29,13 +29,13 @@
 struct NuPlayer::StreamingSource : public NuPlayer::Source {
     StreamingSource(const sp<IStreamSource> &source);
 
-    void start();
+    virtual void start();
 
     // Returns true iff more data was available, false on EOS.
-    bool feedMoreTSData();
+    virtual bool feedMoreTSData();
 
-    sp<MetaData> getFormat(bool audio);
-    status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+    virtual sp<MetaData> getFormat(bool audio);
+    virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
 
 protected:
     virtual ~StreamingSource();
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 5b1f14d..51ab7a1 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -395,10 +395,17 @@
             int64_t index = seekTimeSecs / targetDuration;
 
             if (index >= 0 && index < mPlaylist->size()) {
-                mSeqNumber = firstSeqNumberInPlaylist + index;
-                mDataSource->reset();
+                int32_t newSeqNumber = firstSeqNumberInPlaylist + index;
 
-                explicitDiscontinuity = true;
+                if (newSeqNumber != mSeqNumber) {
+                    LOGI("seeking to seq no %d", newSeqNumber);
+
+                    mSeqNumber = newSeqNumber;
+
+                    mDataSource->reset();
+
+                    explicitDiscontinuity = true;
+                }
             }
         }
 
@@ -463,6 +470,8 @@
         return;
     }
 
+    CHECK(buffer != NULL);
+
     CHECK_EQ((status_t)OK,
              decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer));
 
@@ -482,6 +491,9 @@
     if (explicitDiscontinuity || bandwidthChanged) {
         // Signal discontinuity.
 
+        LOGI("queueing discontinuity (explicit=%d, bandwidthChanged=%d)",
+             explicitDiscontinuity, bandwidthChanged);
+
         sp<ABuffer> tmp = new ABuffer(188);
         memset(tmp->data(), 0, tmp->size());
         tmp->data()[1] = bandwidthChanged;
diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h
index 3873d5d..41f5ad0 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/include/LiveSession.h
@@ -49,7 +49,7 @@
 
 private:
     enum {
-        kMaxNumQueuedFragments = 2,
+        kMaxNumQueuedFragments = 3,
         kMaxNumRetries         = 5,
     };
 
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 7c81ffd..84a3860 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -56,6 +56,10 @@
 
     int64_t convertPTSToTimestamp(uint64_t PTS);
 
+    bool PTSTimeDeltaEstablished() const {
+        return mFirstPTSValid;
+    }
+
 private:
     ATSParser *mParser;
     unsigned mProgramMapPID;
@@ -734,4 +738,12 @@
     return NULL;
 }
 
+bool ATSParser::PTSTimeDeltaEstablished() {
+    if (mPrograms.isEmpty()) {
+        return false;
+    }
+
+    return mPrograms.editItemAt(0)->PTSTimeDeltaEstablished();
+}
+
 }  // namespace android
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index ef78c77..fe31981 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -50,6 +50,8 @@
     };
     sp<MediaSource> getSource(SourceType type);
 
+    bool PTSTimeDeltaEstablished();
+
 protected:
     virtual ~ATSParser();