Bug 951278 - [RTSP][V1.3] The RTSP streaming always stops at the last 1st or 2nd second. r=sworkman
☠☠ backed out by 4c9fa6efddee ☠ ☠
authorVincent Chang <vchang@mozilla.com>
Tue, 18 Mar 2014 14:30:51 +0800
changeset 196603 90f267beb7f5f644cedc0dbda23cac1fa0fadd95
parent 196602 9cdf74ea594885d4b9a0c693ac548e359a429dea
child 196604 5fc57d2ec82aa023e772abbf929987fca1c70c29
push idunknown
push userunknown
push dateunknown
reviewerssworkman
bugs951278
milestone32.0a1
Bug 951278 - [RTSP][V1.3] The RTSP streaming always stops at the last 1st or 2nd second. r=sworkman
content/media/MediaDecoderStateMachine.cpp
content/media/RtspMediaResource.cpp
netwerk/base/public/nsIStreamingProtocolController.idl
netwerk/protocol/rtsp/rtsp/RTSPConnectionHandler.h
netwerk/protocol/rtsp/rtsp/RTSPSource.cpp
netwerk/protocol/rtsp/rtsp/RTSPSource.h
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -2493,17 +2493,20 @@ void MediaDecoderStateMachine::AdvanceFr
     StartPlayback();
   }
 
   if (currentFrame) {
     // Decode one frame and display it.
     TimeStamp presTime = mPlayStartTime - UsecsToDuration(mPlayDuration) +
                           UsecsToDuration(currentFrame->mTime - mStartTime);
     NS_ASSERTION(currentFrame->mTime >= mStartTime, "Should have positive frame time");
-    {
+    // Filter out invalid frames by checking the frame time. FrameTime could be
+    // zero if it's a initial frame.
+    int64_t frameTime = currentFrame->mTime - mStartTime;
+    if (frameTime > 0  || (frameTime == 0 && mPlayDuration == 0)) {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       // If we have video, we want to increment the clock in steps of the frame
       // duration.
       RenderVideoFrame(currentFrame, presTime);
     }
     // If we're no longer playing after dropping and reacquiring the lock,
     // playback must've been stopped on the decode thread (by a seek, for
     // example).  In that case, the current frame is probably out of date.
--- a/content/media/RtspMediaResource.cpp
+++ b/content/media/RtspMediaResource.cpp
@@ -45,16 +45,17 @@ namespace mozilla {
 #define BUFFER_SLOT_DEFAULT_SIZE 256
 #define BUFFER_SLOT_MAX_SIZE 512
 #define BUFFER_SLOT_INVALID -1
 #define BUFFER_SLOT_EMPTY 0
 
 struct BufferSlotData {
   int32_t mLength;
   uint64_t mTime;
+  int32_t  mFrameType;
 };
 
 class RtspTrackBuffer
 {
 public:
   RtspTrackBuffer(const char *aMonitor, int32_t aTrackIdx, uint32_t aSlotSize)
   : mMonitor(aMonitor)
   , mSlotSize(aSlotSize)
@@ -171,16 +172,19 @@ nsresult RtspTrackBuffer::ReadBuffer(uin
            ,mBufferSlotData[mConsumerIdx].mLength);
   // Reader should skip the slots with mLength==BUFFER_SLOT_INVALID.
   // The loop ends when
   // 1. Read data successfully
   // 2. Fail to read data due to aToBuffer's space
   // 3. No data in this buffer
   // 4. mIsStarted is not set
   while (1) {
+    if (mBufferSlotData[mConsumerIdx].mFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
+      return NS_BASE_STREAM_CLOSED;
+    }
     if (mBufferSlotData[mConsumerIdx].mLength > 0) {
       // Check the aToBuffer space is enough for data copy.
       if ((int32_t)aToBufferSize < mBufferSlotData[mConsumerIdx].mLength) {
         aFrameSize = mBufferSlotData[mConsumerIdx].mLength;
         break;
       }
       uint32_t slots = (mBufferSlotData[mConsumerIdx].mLength / mSlotSize) + 1;
       // we have data, copy to aToBuffer
@@ -301,39 +305,53 @@ void RtspTrackBuffer::WriteBuffer(const 
           mConsumerIdx = i;
           break;
         }
       }
     }
     mProducerIdx = 0;
   }
 
-  memcpy(&(mRingBuffer[mSlotSize * mProducerIdx]), aFromBuffer, aWriteCount);
+  if (!(aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM)) {
+    memcpy(&(mRingBuffer[mSlotSize * mProducerIdx]), aFromBuffer, aWriteCount);
+  }
 
   if (mProducerIdx <= mConsumerIdx && mConsumerIdx < mProducerIdx + slots
       && mBufferSlotData[mConsumerIdx].mLength > 0) {
     // Wrote one or more slots that the decode thread has not yet read.
     RTSPMLOG("overwrite!! %d time %lld"
              ,mTrackIdx,mBufferSlotData[mConsumerIdx].mTime);
-    mBufferSlotData[mProducerIdx].mLength = aWriteCount;
-    mBufferSlotData[mProducerIdx].mTime = aFrameTime;
+    if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
+      mBufferSlotData[mProducerIdx].mLength = 0;
+      mBufferSlotData[mProducerIdx].mTime = 0;
+    } else {
+      mBufferSlotData[mProducerIdx].mLength = aWriteCount;
+      mBufferSlotData[mProducerIdx].mTime = aFrameTime;
+    }
+    mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
     // Clear the mBufferSlotDataLength except the start slot.
     if (isMultipleSlots) {
       for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
         mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
       }
     }
     mProducerIdx = (mProducerIdx + slots) % BUFFER_SLOT_NUM;
     // Move the mConsumerIdx forward to ensure that the decoder reads the
     // oldest data available.
     mConsumerIdx = mProducerIdx;
   } else {
     // Normal case, the writer doesn't take over the reader.
-    mBufferSlotData[mProducerIdx].mLength = aWriteCount;
-    mBufferSlotData[mProducerIdx].mTime = aFrameTime;
+    if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
+      mBufferSlotData[mProducerIdx].mLength = 0;
+      mBufferSlotData[mProducerIdx].mTime = 0;
+    } else {
+      mBufferSlotData[mProducerIdx].mLength = aWriteCount;
+      mBufferSlotData[mProducerIdx].mTime = aFrameTime;
+    }
+    mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
     // Clear the mBufferSlotData[].mLength except the start slot.
     if (isMultipleSlots) {
       for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
         mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
       }
     }
     mProducerIdx = (mProducerIdx + slots) % BUFFER_SLOT_NUM;
   }
@@ -343,16 +361,17 @@ void RtspTrackBuffer::WriteBuffer(const 
 
 void RtspTrackBuffer::Reset() {
   MonitorAutoLock monitor(mMonitor);
   mProducerIdx = 0;
   mConsumerIdx = 0;
   for (uint32_t i = 0; i < BUFFER_SLOT_NUM; ++i) {
     mBufferSlotData[i].mLength = BUFFER_SLOT_EMPTY;
     mBufferSlotData[i].mTime = BUFFER_SLOT_EMPTY;
+    mBufferSlotData[i].mFrameType = MEDIASTREAM_FRAMETYPE_NORMAL;
   }
   mMonitor.NotifyAll();
 }
 
 RtspMediaResource::RtspMediaResource(MediaDecoder* aDecoder,
     nsIChannel* aChannel, nsIURI* aURI, const nsACString& aContentType)
   : BaseMediaResource(aDecoder, aChannel, aURI, aContentType)
   , mIsConnected(false)
--- a/netwerk/base/public/nsIStreamingProtocolController.idl
+++ b/netwerk/base/public/nsIStreamingProtocolController.idl
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 interface nsIURI;
 
 #include "nsISupports.idl"
 
 %{C++
 #define MEDIASTREAM_FRAMETYPE_NORMAL          0x00000001
 #define MEDIASTREAM_FRAMETYPE_DISCONTINUITY   0x00000002
+#define MEDIASTREAM_FRAMETYPE_END_OF_STREAM   0x00000004
 %}
 
 /**
  * Metadata of the media stream.
  */
 [uuid(294adb30-856c-11e2-9e96-0800200c9a66)]
 interface nsIStreamingProtocolMetaData : nsISupports
 {
--- a/netwerk/protocol/rtsp/rtsp/RTSPConnectionHandler.h
+++ b/netwerk/protocol/rtsp/rtsp/RTSPConnectionHandler.h
@@ -41,19 +41,19 @@
 #include "prnetdb.h"
 
 extern PRLogModuleInfo* gRtspLog;
 #define LOGI(msg, ...) PR_LOG(gRtspLog, PR_LOG_ALWAYS, ("RTSP" msg, ##__VA_ARGS__))
 #define LOGV(msg, ...) PR_LOG(gRtspLog, PR_LOG_DEBUG, (msg, ##__VA_ARGS__))
 #define LOGE(msg, ...) PR_LOG(gRtspLog, PR_LOG_ERROR, (msg, ##__VA_ARGS__))
 #define LOGW(msg, ...) PR_LOG(gRtspLog, PR_LOG_WARNING, (msg, ##__VA_ARGS__))
 
-// If no access units are received within 5 secs, assume that the rtp
+// If no access units are received within 2 secs, assume that the rtp
 // stream has ended and signal end of stream.
-static int64_t kAccessUnitTimeoutUs = 10000000ll;
+static int64_t kAccessUnitTimeoutUs = 2000000ll;
 
 // If no access units arrive for the first 10 secs after starting the
 // stream, assume none ever will and signal EOS or switch transports.
 static int64_t kPlayTimeoutUs = 10000000ll;
 
 static int64_t kDefaultKeepAliveTimeoutUs = 60000000ll;
 
 namespace android {
@@ -127,18 +127,16 @@ struct RtspConnectionHandler : public AH
           mSetupTracksSuccessful(false),
           mSeekPending(false),
           mPausePending(false),
           mAborted(false),
           mFirstAccessUnit(true),
           mNTPAnchorUs(-1),
           mMediaAnchorUs(-1),
           mLastMediaTimeUs(0),
-          mNumAccessUnitsReceived(0),
-          mCheckPending(false),
           mCheckGeneration(0),
           mTryTCPInterleaving(false),
           mTryFakeRTCP(false),
           mReceivedFirstRTCPPacket(false),
           mReceivedFirstRTPPacket(false),
           mSeekable(false),
           mKeepAliveTimeoutUs(kDefaultKeepAliveTimeoutUs),
           mKeepAliveGeneration(0),
@@ -185,46 +183,65 @@ struct RtspConnectionHandler : public AH
     }
 
     void seek(int64_t timeUs) {
         sp<AMessage> msg = new AMessage('seek', id());
         msg->setInt64("time", timeUs);
         msg->post();
     }
 
+    void setCheckPending(bool flag) {
+        for (size_t i = 0; i < mTracks.size(); ++i) {
+            setCheckPending(i, flag);
+        }
+    }
+
+    void setCheckPending(size_t trackIndex, bool flag) {
+        TrackInfo *info = &mTracks.editItemAt(trackIndex);
+        if (info) {
+            info->mCheckPendings = flag;
+        }
+    }
+
+    bool getCheckPending(size_t trackIndex) {
+        TrackInfo *info = &mTracks.editItemAt(trackIndex);
+        return info->mCheckPendings;
+    }
+
     void play(uint64_t timeUs) {
         AString request = "PLAY ";
         request.append(mSessionURL);
         request.append(" RTSP/1.0\r\n");
 
         request.append("Session: ");
         request.append(mSessionID);
         request.append("\r\n");
 
         request.append(nsPrintfCString("Range: npt=%lld-\r\n", timeUs / 1000000ll).get());
         request.append("\r\n");
 
-        mCheckPending = false;
+        setCheckPending(false);
+
         sp<AMessage> reply = new AMessage('play', id());
         mConn->sendRequest(request.c_str(), reply);
     }
 
     void pause() {
         AString request = "PAUSE ";
         request.append(mSessionURL);
         request.append(" RTSP/1.0\r\n");
 
         request.append("Session: ");
         request.append(mSessionID);
         request.append("\r\n");
 
         request.append("\r\n");
         // Disable the access unit timeout until we resume
         // playback again.
-        mCheckPending = true;
+        setCheckPending(true);
         ++mCheckGeneration;
 
         sp<AMessage> reply = new AMessage('pause', id());
         mConn->sendRequest(request.c_str(), reply);
     }
 
     void resume(uint64_t timeUs) {
         AString request = "PLAY ";
@@ -233,17 +250,18 @@ struct RtspConnectionHandler : public AH
 
         request.append("Session: ");
         request.append(mSessionID);
         request.append("\r\n");
 
         request.append(nsPrintfCString("Range: npt=%lld-\r\n", timeUs / 1000000ll).get());
         request.append("\r\n");
 
-        mCheckPending = false;
+        setCheckPending(false);
+
         sp<AMessage> reply = new AMessage('resume', id());
         mConn->sendRequest(request.c_str(), reply);
 
     }
 
     static void addRR(const sp<ABuffer> &buf) {
         uint8_t *ptr = buf->data() + buf->size();
         ptr[0] = 0x80 | 0;
@@ -746,17 +764,16 @@ struct RtspConnectionHandler : public AH
                   break;
                 }
 
                 if (result == OK) {
                     sp<RefBase> obj;
                     CHECK(msg->findObject("response", &obj));
                     sp<ARTSPResponse> response =
                         static_cast<ARTSPResponse *>(obj.get());
-
                     if (response->mStatusCode != 200) {
                         result = UNKNOWN_ERROR;
                     } else {
                         parsePlayResponse(response);
 
                         sp<AMessage> timeout = new AMessage('tiou', id());
                         timeout->post(kPlayTimeoutUs);
                         mPausePending = false;
@@ -813,20 +830,37 @@ struct RtspConnectionHandler : public AH
                     // obsolete event.
                     break;
                 }
 
                 postKeepAlive();
                 break;
             }
 
+            case 'endofstream':
+            {
+                size_t trackIndex = 0;
+                msg->findSize("trackIndex", &trackIndex);
+                postQueueEOS(trackIndex, ERROR_END_OF_STREAM);
+                TrackInfo *info = &mTracks.editItemAt(trackIndex);
+                if (info) {
+                  mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);
+                  close(info->mRTPSocket);
+                  close(info->mRTCPSocket);
+                }
+                break;
+            }
+
             case 'abor':
             {
                 for (size_t i = 0; i < mTracks.size(); ++i) {
                     TrackInfo *info = &mTracks.editItemAt(i);
+                    if (!info) {
+                        continue;
+                    }
 
                     if (!mFirstAccessUnit) {
                         postQueueEOS(i, ERROR_END_OF_STREAM);
                     }
 
                     if (!info->mUsingInterleavedTCP) {
                         mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);
 
@@ -841,17 +875,16 @@ struct RtspConnectionHandler : public AH
                 }
                 mTracks.clear();
                 mSetupTracksSuccessful = false;
                 mSeekPending = false;
                 mPausePending = false;
                 mFirstAccessUnit = true;
                 mNTPAnchorUs = -1;
                 mMediaAnchorUs = -1;
-                mNumAccessUnitsReceived = 0;
                 mReceivedFirstRTCPPacket = false;
                 mReceivedFirstRTPPacket = false;
                 mSeekable = false;
                 mAborted = true;
 
                 sp<AMessage> reply = new AMessage('tear', id());
 
                 int32_t reconnect;
@@ -910,24 +943,31 @@ struct RtspConnectionHandler : public AH
             case 'chek':
             {
                 int32_t generation;
                 CHECK(msg->findInt32("generation", &generation));
                 if (generation != mCheckGeneration) {
                     // This is an outdated message. Ignore.
                     break;
                 }
+                size_t trackIndex;
+                msg->findSize("trackIndex", &trackIndex);
+                TrackInfo *track = &mTracks.editItemAt(trackIndex);
+                if (!track) {
+                  break;
+                }
 
-                if (mNumAccessUnitsReceived == 0) {
+                if (track->mNumAccessUnitsReceiveds == 0) {
                     LOGI("stream ended? aborting.");
-                    (new AMessage('abor', id()))->post();
+                    sp<AMessage> endStreamMsg = new AMessage('endofstream', id());
+                    endStreamMsg->setSize("trackIndex", trackIndex);
+                    endStreamMsg->post();
                     break;
                 }
-
-                mNumAccessUnitsReceived = 0;
+                track->mNumAccessUnitsReceiveds = 0;
                 msg->post(kAccessUnitTimeoutUs);
                 break;
             }
 
             case 'accu':
             {
                 int32_t timeUpdate;
                 if (msg->findInt32("time-update", &timeUpdate) && timeUpdate) {
@@ -949,29 +989,29 @@ struct RtspConnectionHandler : public AH
                     break;
                 }
 
                 if (msg->findInt32("first-rtp", &first)) {
                     mReceivedFirstRTPPacket = true;
                     break;
                 }
 
-                ++mNumAccessUnitsReceived;
-                postAccessUnitTimeoutCheck();
-
                 size_t trackIndex;
                 CHECK(msg->findSize("track-index", &trackIndex));
 
                 if (trackIndex >= mTracks.size()) {
                     LOGV("late packets ignored.");
                     break;
                 }
 
                 TrackInfo *track = &mTracks.editItemAt(trackIndex);
 
+                track->mNumAccessUnitsReceiveds++;
+                postAccessUnitTimeoutCheck(trackIndex);
+
                 int32_t eos;
                 if (msg->findInt32("eos", &eos)) {
                     LOGI("received BYE on track index %d", trackIndex);
                     return;
                 }
 
                 sp<RefBase> obj;
                 CHECK(msg->findObject("access-unit", &obj));
@@ -1022,17 +1062,18 @@ struct RtspConnectionHandler : public AH
 
                 int64_t timeUs;
                 CHECK(msg->findInt64("time", &timeUs));
 
                 mSeekPending = true;
 
                 // Disable the access unit timeout until we resumed
                 // playback again.
-                mCheckPending = true;
+                setCheckPending(true);
+
                 ++mCheckGeneration;
 
                 AString request = "PAUSE ";
                 request.append(mSessionURL);
                 request.append(" RTSP/1.0\r\n");
 
                 request.append("Session: ");
                 request.append(mSessionID);
@@ -1087,18 +1128,20 @@ struct RtspConnectionHandler : public AH
 
                 LOGI("PLAY completed with result %d (%s)",
                      result, strerror(-result));
                 if (mAborted) {
                   LOGV("we're aborted, dropping stale packet.");
                   break;
                 }
 
-                mCheckPending = false;
-                postAccessUnitTimeoutCheck();
+                for (size_t i = 0; i < mTracks.size(); i++) {
+                    setCheckPending(i, false);
+                    postAccessUnitTimeoutCheck(i);
+                }
 
                 if (result == OK) {
                     sp<RefBase> obj;
                     CHECK(msg->findObject("response", &obj));
                     sp<ARTSPResponse> response =
                         static_cast<ARTSPResponse *>(obj.get());
 
                     if (response->mStatusCode != 200) {
@@ -1191,24 +1234,24 @@ struct RtspConnectionHandler : public AH
     }
 
     void postKeepAlive() {
         sp<AMessage> msg = new AMessage('aliv', id());
         msg->setInt32("generation", mKeepAliveGeneration);
         msg->post((mKeepAliveTimeoutUs * 9) / 10);
     }
 
-    void postAccessUnitTimeoutCheck() {
-        if (mCheckPending) {
+    void postAccessUnitTimeoutCheck(size_t trackIndex) {
+        if (getCheckPending(trackIndex)) {
             return;
         }
-
-        mCheckPending = true;
+        setCheckPending(trackIndex, true);
         sp<AMessage> check = new AMessage('chek', id());
         check->setInt32("generation", mCheckGeneration);
+        check->setSize("trackIndex", trackIndex);
         check->post(kAccessUnitTimeoutUs);
     }
 
     static void SplitString(
             const AString &s, const char *separator, List<AString> *items) {
         items->clear();
         size_t start = 0;
         while (start < s.size()) {
@@ -1339,16 +1382,18 @@ private:
         int64_t mNormalPlayTimeUs;
 
         sp<APacketSource> mPacketSource;
 
         // Stores packets temporarily while no notion of time
         // has been established yet.
         List<sp<ABuffer> > mPackets;
         bool mIsPlayAcked;
+        int64_t mNumAccessUnitsReceiveds;
+        bool mCheckPendings;
     };
 
     sp<AMessage> mNotify;
     bool mUIDValid;
     uid_t mUID;
     sp<ALooper> mNetLooper;
     sp<ARTSPConnection> mConn;
     sp<ARTPConnection> mRTPConn;
@@ -1363,18 +1408,16 @@ private:
     bool mPausePending;
     bool mAborted;
     bool mFirstAccessUnit;
 
     int64_t mNTPAnchorUs;
     int64_t mMediaAnchorUs;
     int64_t mLastMediaTimeUs;
 
-    int64_t mNumAccessUnitsReceived;
-    bool mCheckPending;
     int32_t mCheckGeneration;
     bool mTryTCPInterleaving;
     bool mTryFakeRTCP;
     bool mReceivedFirstRTCPPacket;
     bool mReceivedFirstRTPPacket;
     bool mSeekable;
     int64_t mKeepAliveTimeoutUs;
     int32_t mKeepAliveGeneration;
@@ -1409,16 +1452,18 @@ private:
         info->mUsingInterleavedTCP = false;
         info->mFirstSeqNumInSegment = 0;
         info->mNewSegment = true;
         info->mRTPAnchor = 0;
         info->mNTPAnchorUs = -1;
         info->mNormalPlayTimeRTP = 0;
         info->mNormalPlayTimeUs = 0ll;
         info->mIsPlayAcked = false;
+        info->mNumAccessUnitsReceiveds = 0;
+        info->mCheckPendings = false;
 
         unsigned long PT;
         AString formatDesc;
         AString formatParams;
         mSessionDesc->getFormatType(index, &PT, &formatDesc, &formatParams);
 
         int32_t timescale;
         int32_t numChannels;
--- a/netwerk/protocol/rtsp/rtsp/RTSPSource.cpp
+++ b/netwerk/protocol/rtsp/rtsp/RTSPSource.cpp
@@ -429,16 +429,17 @@ void RTSPSource::onMessageReceived(const
             CHECK_NE(finalResult, (status_t)OK);
 
             TrackInfo *info = &mTracks.editItemAt(trackIndex);
             sp<AnotherPacketSource> source = info->mSource;
             if (source != NULL) {
                 source->signalEOS(finalResult);
             }
 
+            onTrackEndOfStream(trackIndex);
             break;
         }
 
         case RtspConnectionHandler::kWhatSeekDiscontinuity:
         {
             size_t trackIndex;
             CHECK(msg->findSize("trackIndex", &trackIndex));
             CHECK_LT(trackIndex, mTracks.size());
@@ -614,17 +615,19 @@ void RTSPSource::onDisconnected(const sp
     }
     mAudioTrack = NULL;
     mVideoTrack = NULL;
     mTracks.clear();
 }
 
 void RTSPSource::finishDisconnectIfPossible() {
     if (mState != DISCONNECTED) {
-        mHandler->disconnect();
+        if (!mHandler.get()) {
+            mHandler->disconnect();
+        }
     }
 
     (new AMessage)->postReply(mDisconnectReplyID);
     mDisconnectReplyID = 0;
 }
 
 void RTSPSource::onTrackDataAvailable(size_t trackIndex)
 {
@@ -665,9 +668,26 @@ void RTSPSource::onTrackDataAvailable(si
 
     meta->SetFrameType(MEDIASTREAM_FRAMETYPE_NORMAL);
     data.Assign((const char *) accessUnit->data(), accessUnit->size());
 
     if (mListener) {
         mListener->OnMediaDataAvailable(trackIndex, data, data.Length(), 0, meta.get());
     }
 }
+
+void RTSPSource::onTrackEndOfStream(size_t trackIndex)
+{
+    mState = CONNECTED;
+    if (!mListener) {
+        return;
+    }
+
+    nsRefPtr<nsIStreamingProtocolMetaData> meta;
+    meta = new mozilla::net::RtspMetaData();
+    meta->SetFrameType(MEDIASTREAM_FRAMETYPE_END_OF_STREAM);
+
+    nsCString data;
+    data.AssignLiteral("END_OF_STREAM");
+
+    mListener->OnMediaDataAvailable(trackIndex, data, data.Length(), 0, meta.get());
+}
 }  // namespace android
--- a/netwerk/protocol/rtsp/rtsp/RTSPSource.h
+++ b/netwerk/protocol/rtsp/rtsp/RTSPSource.h
@@ -137,16 +137,18 @@ private:
     void performPause();
 
     void performResume();
 
     void performSuspend();
 
     void onTrackDataAvailable(size_t trackIndex);
 
+    void onTrackEndOfStream(size_t trackIndex);
+
     nsMainThreadPtrHandle<nsIStreamingProtocolListener> mListener;
     int mPrintCount;
 
     DISALLOW_EVIL_CONSTRUCTORS(RTSPSource);
 };
 
 }  // namespace android