Bug 1171314: P3. Detect change of stream content and automatically recreate decoder. r=cpearce
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 15 Jun 2015 10:58:12 +1000
changeset 249710 5a79d2d62dc4d0ee76023085ff57f2c6137b93a5
parent 249709 40c9845aff7d882bf72a4c258a2a02ff2d8e3eef
child 249711 6a5a99f92a009304d086cb26596890c985ae8c10
push id28936
push userryanvm@gmail.com
push dateFri, 19 Jun 2015 20:34:42 +0000
treeherdermozilla-central@c319f262ce3e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1171314
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1171314: P3. Detect change of stream content and automatically recreate decoder. r=cpearce
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -103,16 +103,19 @@ MediaFormatReader::Shutdown()
   MOZ_ASSERT(OnTaskQueue());
 
   mDemuxerInitRequest.DisconnectIfExists();
   mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
   mSkipRequest.DisconnectIfExists();
 
   if (mAudio.mDecoder) {
     Flush(TrackInfo::kAudioTrack);
+    if (mAudio.HasPromise()) {
+      mAudio.RejectPromise(CANCELED, __func__);
+    }
     mAudio.mDecoder->Shutdown();
     mAudio.mDecoder = nullptr;
   }
   if (mAudio.mTrackDemuxer) {
     mAudio.ResetDemuxer();
     mAudio.mTrackDemuxer->BreakCycles();
     mAudio.mTrackDemuxer = nullptr;
   }
@@ -120,16 +123,19 @@ MediaFormatReader::Shutdown()
     mAudio.mTaskQueue->BeginShutdown();
     mAudio.mTaskQueue->AwaitShutdownAndIdle();
     mAudio.mTaskQueue = nullptr;
   }
   MOZ_ASSERT(mAudio.mPromise.IsEmpty());
 
   if (mVideo.mDecoder) {
     Flush(TrackInfo::kVideoTrack);
+    if (mVideo.HasPromise()) {
+      mVideo.RejectPromise(CANCELED, __func__);
+    }
     mVideo.mDecoder->Shutdown();
     mVideo.mDecoder = nullptr;
   }
   if (mVideo.mTrackDemuxer) {
     mVideo.ResetDemuxer();
     mVideo.mTrackDemuxer->BreakCycles();
     mVideo.mTrackDemuxer = nullptr;
   }
@@ -426,40 +432,46 @@ MediaFormatReader::EnsureDecodersSetup()
 
   MOZ_ASSERT(mPlatform);
 
   if (HasAudio() && !mAudio.mDecoder) {
     NS_ENSURE_TRUE(IsSupportedAudioMimeType(mInfo.mAudio.mMimeType),
                    false);
 
     mAudio.mDecoder =
-      mPlatform->CreateDecoder(mInfo.mAudio,
+      mPlatform->CreateDecoder(mAudio.mInfo ?
+                                 *mAudio.mInfo->GetAsAudioInfo() :
+                                 mInfo.mAudio,
                                mAudio.mTaskQueue,
                                mAudio.mCallback);
     NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, false);
     nsresult rv = mAudio.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, false);
   }
 
   if (HasVideo() && !mVideo.mDecoder) {
     NS_ENSURE_TRUE(IsSupportedVideoMimeType(mInfo.mVideo.mMimeType),
                    false);
 
     if (mSharedDecoderManager &&
         mPlatform->SupportsSharedDecoders(mInfo.mVideo)) {
       mVideo.mDecoder =
         mSharedDecoderManager->CreateVideoDecoder(mPlatform,
-                                                  mInfo.mVideo,
+                                                  mVideo.mInfo ?
+                                                    *mVideo.mInfo->GetAsVideoInfo() :
+                                                    mInfo.mVideo,
                                                   mLayersBackendType,
                                                   mDecoder->GetImageContainer(),
                                                   mVideo.mTaskQueue,
                                                   mVideo.mCallback);
     } else {
       mVideo.mDecoder =
-        mPlatform->CreateDecoder(mInfo.mVideo,
+        mPlatform->CreateDecoder(mVideo.mInfo ?
+                                   *mVideo.mInfo->GetAsVideoInfo() :
+                                   mInfo.mVideo,
                                  mVideo.mTaskQueue,
                                  mVideo.mCallback,
                                  mLayersBackendType,
                                  mDecoder->GetImageContainer());
     }
     NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, false);
     nsresult rv = mVideo.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, false);
@@ -513,18 +525,17 @@ MediaFormatReader::ShouldSkip(bool aSkip
 
 nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
                                      int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
-  MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
-  MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
+  MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists());
   MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
 
   if (!HasVideo()) {
     LOG("called with no video track");
     return VideoDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
@@ -558,24 +569,23 @@ MediaFormatReader::RequestVideoData(bool
   ScheduleUpdate(TrackInfo::kVideoTrack);
   return p;
 }
 
 void
 MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
-  LOG("Failed to demux %s, failure = %d",
+  LOG("Failed to demux %s, failure:%d",
       aTrack == TrackType::kVideoTrack ? "video" : "audio", aFailure);
   auto& decoder = GetDecoderData(aTrack);
   decoder.mDemuxRequest.Complete();
   switch (aFailure) {
     case DemuxerFailureReason::END_OF_STREAM:
-      decoder.mDemuxEOS = true;
-      ScheduleUpdate(aTrack);
+      NotifyEndOfStream(aTrack);
       break;
     case DemuxerFailureReason::DEMUXER_ERROR:
       NotifyError(aTrack);
       break;
     case DemuxerFailureReason::WAITING_FOR_DATA:
       NotifyWaitingForData(aTrack);
       break;
     case DemuxerFailureReason::CANCELED:
@@ -598,29 +608,30 @@ MediaFormatReader::DoDemuxVideo()
                       ->Then(TaskQueue(), __func__, this,
                              &MediaFormatReader::OnVideoDemuxCompleted,
                              &MediaFormatReader::OnVideoDemuxFailed));
 }
 
 void
 MediaFormatReader::OnVideoDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
-  LOGV("%d video samples demuxed", aSamples->mSamples.Length());
+  LOGV("%d video samples demuxed (sid:%d)",
+       aSamples->mSamples.Length(),
+       aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
   mVideo.mDemuxRequest.Complete();
   mVideo.mQueuedSamples.AppendElements(aSamples->mSamples);
   ScheduleUpdate(TrackInfo::kVideoTrack);
 }
 
 nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaFormatReader::RequestAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
-  MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
-  MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
+  MOZ_DIAGNOSTIC_ASSERT(!mAudio.mSeekRequest.Exists());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("");
 
   if (!HasAudio()) {
     LOG("called with no audio track");
     return AudioDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
@@ -653,17 +664,19 @@ MediaFormatReader::DoDemuxAudio()
                       ->Then(TaskQueue(), __func__, this,
                              &MediaFormatReader::OnAudioDemuxCompleted,
                              &MediaFormatReader::OnAudioDemuxFailed));
 }
 
 void
 MediaFormatReader::OnAudioDemuxCompleted(nsRefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
 {
-  LOGV("%d audio samples demuxed", aSamples->mSamples.Length());
+  LOGV("%d audio samples demuxed (sid:%d)",
+       aSamples->mSamples.Length(),
+       aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
   mAudio.mDemuxRequest.Complete();
   mAudio.mQueuedSamples.AppendElements(aSamples->mSamples);
   ScheduleUpdate(TrackInfo::kAudioTrack);
 }
 
 void
 MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample)
 {
@@ -717,31 +730,41 @@ void
 MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mWaitingForData = true;
   ScheduleUpdate(aTrack);
 }
 
+void
+MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  decoder.mDemuxEOS = true;
+  ScheduleUpdate(aTrack);
+}
+
 bool
 MediaFormatReader::NeedInput(DecoderData& aDecoder)
 {
   MOZ_ASSERT(OnTaskQueue());
   // We try to keep a few more compressed samples input than decoded samples
   // have been output, provided the state machine has requested we send it a
   // decoded sample. To account for H.264 streams which may require a longer
   // run of input than we input, decoders fire an "input exhausted" callback,
   // which overrides our "few more samples" threshold.
   return
     !aDecoder.mError &&
     aDecoder.HasPromise() &&
     !aDecoder.mDemuxRequest.Exists() &&
     aDecoder.mOutput.IsEmpty() &&
     (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
+     aDecoder.mTimeThreshold.isSome() ||
      aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead);
 }
 
 void
 MediaFormatReader::ScheduleUpdate(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mShutdown) {
@@ -789,17 +812,17 @@ MediaFormatReader::UpdateReceivedNewData
   if (decoder.HasWaitingPromise()) {
     MOZ_ASSERT(!decoder.HasPromise());
     LOG("We have new data. Resolving WaitingPromise");
     decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
     return true;
   }
   if (!mSeekPromise.IsEmpty()) {
     MOZ_ASSERT(!decoder.HasPromise());
-    if (mVideoSeekRequest.Exists() || mAudioSeekRequest.Exists()) {
+    if (mVideo.mSeekRequest.Exists() || mAudio.mSeekRequest.Exists()) {
       // Already waiting for a seek to complete. Nothing more to do.
       return true;
     }
     LOG("Attempting Seek");
     AttemptSeek();
     return true;
   }
   return false;
@@ -835,29 +858,98 @@ MediaFormatReader::DecodeDemuxedSamples(
                                         AbstractMediaDecoder::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
 
   if (decoder.mQueuedSamples.IsEmpty()) {
     return;
   }
+
   LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
-  decoder.mOutputRequested = true;
 
   // Decode all our demuxed frames.
-  for (auto& samples : decoder.mQueuedSamples) {
-    decoder.mDecoder->Input(samples);
+  bool samplesPending = false;
+  while (decoder.mQueuedSamples.Length()) {
+    nsRefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
+    nsRefPtr<SharedTrackInfo> info = sample->mTrackInfo;
+
+    if (info && decoder.mLastStreamSourceID != info->GetID()) {
+      if (samplesPending) {
+        // Let existing samples complete their decoding. We'll resume later.
+        return;
+      }
+
+      LOG("%s stream id has changed from:%d to:%d, recreating decoder.",
+          TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
+          info->GetID());
+      decoder.mInfo = info;
+      decoder.mLastStreamSourceID = info->GetID();
+      // Flush will clear our array of queued samples. So make a copy now.
+      nsTArray<nsRefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
+      Flush(aTrack);
+      decoder.mDecoder->Shutdown();
+      decoder.mDecoder = nullptr;
+      if (!EnsureDecodersSetup()) {
+        LOG("Unable to re-create decoder, aborting");
+        NotifyError(aTrack);
+        return;
+      }
+      LOGV("%s decoder:%p created for sid:%u",
+           TrackTypeToStr(aTrack), decoder.mDecoder.get(), info->GetID());
+      if (sample->mKeyframe) {
+        decoder.mQueuedSamples.MoveElementsFrom(samples);
+      } else {
+        MOZ_ASSERT(decoder.mTimeThreshold.isNothing());
+        LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
+            sample->mTime);
+        decoder.mTimeThreshold = Some(TimeUnit::FromMicroseconds(sample->mTime));
+        nsRefPtr<MediaFormatReader> self = this;
+        decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref())
+                   ->Then(TaskQueue(), __func__,
+                          [self, aTrack] (media::TimeUnit aTime) {
+                            auto& decoder = self->GetDecoderData(aTrack);
+                            decoder.mSeekRequest.Complete();
+                            self->ScheduleUpdate(aTrack);
+                          },
+                          [self, aTrack] (DemuxerFailureReason aResult) {
+                            auto& decoder = self->GetDecoderData(aTrack);
+                            decoder.mSeekRequest.Complete();
+                            switch (aResult) {
+                              case DemuxerFailureReason::WAITING_FOR_DATA:
+                                self->NotifyWaitingForData(aTrack);
+                                break;
+                              case DemuxerFailureReason::END_OF_STREAM:
+                                self->NotifyEndOfStream(aTrack);
+                                break;
+                              case DemuxerFailureReason::CANCELED:
+                              case DemuxerFailureReason::SHUTDOWN:
+                                break;
+                              default:
+                                self->NotifyError(aTrack);
+                                break;
+                            }
+                            decoder.mTimeThreshold.reset();
+                          }));
+        return;
+      }
+    }
+
+    LOGV("Input:%lld (dts:%lld kf:%d)",
+         sample->mTime, sample->mTimecode, sample->mKeyframe);
+    decoder.mOutputRequested = true;
+    decoder.mNumSamplesInput++;
+    decoder.mSizeOfQueue++;
+    if (aTrack == TrackInfo::kVideoTrack) {
+      aA.mParsed++;
+    }
+    decoder.mDecoder->Input(sample);
+    decoder.mQueuedSamples.RemoveElementAt(0);
+    samplesPending = true;
   }
-  decoder.mNumSamplesInput += decoder.mQueuedSamples.Length();
-  decoder.mSizeOfQueue += decoder.mQueuedSamples.Length();
-  if (aTrack == TrackInfo::kVideoTrack) {
-    aA.mParsed += decoder.mQueuedSamples.Length();
-  }
-  decoder.mQueuedSamples.Clear();
 
   // We have serviced the decoder's request for more data.
   decoder.mInputExhausted = false;
 }
 
 void
 MediaFormatReader::Update(TrackType aTrack)
 {
@@ -908,17 +1000,26 @@ MediaFormatReader::Update(TrackType aTra
   }
   if (decoder.HasPromise()) {
     needOutput = true;
     if (!decoder.mOutput.IsEmpty()) {
       // We have a decoded sample ready to be returned.
       nsRefPtr<MediaData> output = decoder.mOutput[0];
       decoder.mOutput.RemoveElementAt(0);
       decoder.mSizeOfQueue -= 1;
-      ReturnOutput(output, aTrack);
+      if (decoder.mTimeThreshold.isNothing() ||
+          media::TimeUnit::FromMicroseconds(output->mTime) >= decoder.mTimeThreshold.ref()) {
+        ReturnOutput(output, aTrack);
+        decoder.mTimeThreshold.reset();
+      } else {
+        LOGV("Internal Seeking: Dropping frame time:%f wanted:%f (kf:%d)",
+             media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
+             decoder.mTimeThreshold.ref().ToSeconds(),
+             output->mKeyframe);
+      }
     } else if (decoder.mDrainComplete) {
       decoder.RejectPromise(END_OF_STREAM, __func__);
       decoder.mDrainComplete = false;
     }
   }
 
   if (decoder.mDemuxEOS && !decoder.mDemuxEOSServiced) {
     decoder.mOutputRequested = true;
@@ -930,20 +1031,20 @@ MediaFormatReader::Update(TrackType aTra
 
   if (!NeedInput(decoder)) {
     LOGV("No need for additional input");
     return;
   }
 
   needInput = true;
 
-  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d",
+  LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d sid:%d",
        TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
-       size_t(decoder.mSizeOfQueue));
+       size_t(decoder.mSizeOfQueue), decoder.mLastStreamSourceID);
 
   // Demux samples if we don't have some.
   RequestDemuxSamples(aTrack);
   // Decode all pending demuxed samples.
   DecodeDemuxedSamples(aTrack, a);
 }
 
 void
@@ -1009,35 +1110,41 @@ MediaFormatReader::WaitForData(MediaData
 nsresult
 MediaFormatReader::ResetDecode()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   LOGV("");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-  mAudioSeekRequest.DisconnectIfExists();
-  mVideoSeekRequest.DisconnectIfExists();
+  mAudio.mSeekRequest.DisconnectIfExists();
+  mVideo.mSeekRequest.DisconnectIfExists();
   mSeekPromise.RejectIfExists(NS_OK, __func__);
   mSkipRequest.DisconnectIfExists();
 
   // Do the same for any data wait promises.
   mAudio.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::CANCELED), __func__);
   mVideo.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::CANCELED), __func__);
 
   // Reset miscellaneous seeking state.
   mPendingSeekTime.reset();
 
   if (HasVideo()) {
     mVideo.ResetDemuxer();
     Flush(TrackInfo::kVideoTrack);
+    if (mVideo.HasPromise()) {
+      mVideo.RejectPromise(CANCELED, __func__);
+    }
   }
   if (HasAudio()) {
     mAudio.ResetDemuxer();
     Flush(TrackInfo::kAudioTrack);
+    if (mAudio.HasPromise()) {
+      mAudio.RejectPromise(CANCELED, __func__);
+    }
   }
   return MediaDecoderReader::ResetDecode();
 }
 
 void
 MediaFormatReader::Output(TrackType aTrack, MediaData* aSample)
 {
   LOGV("Decoded %s sample time=%lld dur=%lld",
@@ -1093,19 +1200,16 @@ MediaFormatReader::Flush(TrackType aTrac
     return;
   }
 
   decoder.mDecoder->Flush();
   // Purge the current decoder's state.
   // ResetState clears mOutputRequested flag so that we ignore all output until
   // the next request for more data.
   decoder.ResetState();
-  if (decoder.HasPromise()) {
-    decoder.RejectPromise(CANCELED, __func__);
-  }
   LOG("Flush(%s) END", TrackTypeToStr(aTrack));
 }
 
 void
 MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -1140,39 +1244,48 @@ MediaFormatReader::OnVideoSkipCompleted(
 void
 MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailure)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Skipping failed, skipped %u frames", aFailure.mSkipped);
   mSkipRequest.Complete();
   mDecoder->NotifyDecodedFrames(aFailure.mSkipped, 0, aFailure.mSkipped);
   MOZ_ASSERT(mVideo.HasPromise());
-  if (aFailure.mFailure == DemuxerFailureReason::END_OF_STREAM) {
-    mVideo.mDemuxEOS = true;
-    ScheduleUpdate(TrackType::kVideoTrack);
-    mVideo.RejectPromise(END_OF_STREAM, __func__);
-  } else if (aFailure.mFailure == DemuxerFailureReason::WAITING_FOR_DATA) {
-    NotifyWaitingForData(TrackType::kVideoTrack);
-    mVideo.RejectPromise(WAITING_FOR_DATA, __func__);
-  } else {
-    mVideo.RejectPromise(DECODE_ERROR, __func__);
+  switch (aFailure.mFailure) {
+    case DemuxerFailureReason::END_OF_STREAM:
+      NotifyEndOfStream(TrackType::kVideoTrack);
+      mVideo.RejectPromise(END_OF_STREAM, __func__);
+      break;
+    case DemuxerFailureReason::WAITING_FOR_DATA:
+      NotifyWaitingForData(TrackType::kVideoTrack);
+      mVideo.RejectPromise(WAITING_FOR_DATA, __func__);
+      break;
+    case DemuxerFailureReason::CANCELED:
+    case DemuxerFailureReason::SHUTDOWN:
+      break;
+    default:
+      NotifyError(TrackType::kVideoTrack);
+      mVideo.RejectPromise(DECODE_ERROR, __func__);
+      break;
   }
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 MediaFormatReader::Seek(int64_t aTime, int64_t aUnused)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   LOG("aTime=(%lld)", aTime);
 
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty());
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(mPendingSeekTime.isNothing());
+  MOZ_DIAGNOSTIC_ASSERT(mVideo.mTimeThreshold.isNothing());
+  MOZ_DIAGNOSTIC_ASSERT(mAudio.mTimeThreshold.isNothing());
 
   if (!mSeekable) {
     LOG("Seek() END (Unseekable)");
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   if (mShutdown) {
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
@@ -1199,50 +1312,50 @@ MediaFormatReader::AttemptSeek()
     MOZ_CRASH();
   }
 }
 
 void
 MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult)
 {
   MOZ_ASSERT(OnTaskQueue());
-  LOGV("%s failure = %d", TrackTypeToStr(aTrack), aResult);
+  LOGV("%s failure:%d", TrackTypeToStr(aTrack), aResult);
   if (aTrack == TrackType::kVideoTrack) {
-    mVideoSeekRequest.Complete();
+    mVideo.mSeekRequest.Complete();
   } else {
-    mAudioSeekRequest.Complete();
+    mAudio.mSeekRequest.Complete();
   }
 
   if (aResult == DemuxerFailureReason::WAITING_FOR_DATA) {
     NotifyWaitingForData(aTrack);
     return;
   }
-  MOZ_ASSERT(!mVideoSeekRequest.Exists() && !mAudioSeekRequest.Exists());
+  MOZ_ASSERT(!mVideo.mSeekRequest.Exists() && !mAudio.mSeekRequest.Exists());
   mPendingSeekTime.reset();
   mSeekPromise.Reject(NS_ERROR_FAILURE, __func__);
 }
 
 void
 MediaFormatReader::DoVideoSeek()
 {
   MOZ_ASSERT(mPendingSeekTime.isSome());
   LOGV("Seeking video to %lld", mPendingSeekTime.ref().ToMicroseconds());
   media::TimeUnit seekTime = mPendingSeekTime.ref();
-  mVideoSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime)
+  mVideo.mSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime)
                           ->Then(TaskQueue(), __func__, this,
                                  &MediaFormatReader::OnVideoSeekCompleted,
                                  &MediaFormatReader::OnVideoSeekFailed));
 }
 
 void
 MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Video seeked to %lld", aTime.ToMicroseconds());
-  mVideoSeekRequest.Complete();
+  mVideo.mSeekRequest.Complete();
 
   if (HasAudio()) {
     MOZ_ASSERT(mPendingSeekTime.isSome());
     mPendingSeekTime.ref() = aTime;
     DoAudioSeek();
   } else {
     mPendingSeekTime.reset();
     mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__);
@@ -1250,28 +1363,28 @@ MediaFormatReader::OnVideoSeekCompleted(
 }
 
 void
 MediaFormatReader::DoAudioSeek()
 {
   MOZ_ASSERT(mPendingSeekTime.isSome());
   LOGV("Seeking audio to %lld", mPendingSeekTime.ref().ToMicroseconds());
   media::TimeUnit seekTime = mPendingSeekTime.ref();
-  mAudioSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
+  mAudio.mSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
                          ->Then(TaskQueue(), __func__, this,
                                 &MediaFormatReader::OnAudioSeekCompleted,
                                 &MediaFormatReader::OnAudioSeekFailed));
 }
 
 void
 MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("Audio seeked to %lld", aTime.ToMicroseconds());
-  mAudioSeekRequest.Complete();
+  mAudio.mSeekRequest.Complete();
   mPendingSeekTime.reset();
   mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__);
 }
 
 int64_t
 MediaFormatReader::GetEvictionOffset(double aTime)
 {
   int64_t audioOffset;
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -126,16 +126,17 @@ private:
   // Decode any pending already demuxed samples.
   void DecodeDemuxedSamples(TrackType aTrack,
                             AbstractMediaDecoder::AutoNotifyDecoded& aA);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack);
   void NotifyWaitingForData(TrackType aTrack);
+  void NotifyEndOfStream(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
@@ -200,16 +201,17 @@ private:
       , mDiscontinuity(true)
       , mOutputRequested(false)
       , mInputExhausted(false)
       , mError(false)
       , mDrainComplete(false)
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mSizeOfQueue(0)
+      , mLastStreamSourceID(UINT32_MAX)
       , mMonitor(aType == MediaData::AUDIO_DATA ? "audio decoder data"
                                                 : "video decoder data")
     {}
 
     MediaFormatReader* mOwner;
     // Disambiguate Audio vs Video.
     MediaData::Type mType;
     nsRefPtr<MediaTrackDemuxer> mTrackDemuxer;
@@ -225,31 +227,38 @@ private:
     uint32_t mDecodeAhead;
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mDemuxEOSServiced;
     bool mWaitingForData;
     bool mReceivedNewData;
     bool mDiscontinuity;
 
+    // Pending seek.
+    MediaPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
+
     // Queued demux samples waiting to be decoded.
     nsTArray<nsRefPtr<MediaRawData>> mQueuedSamples;
     MediaPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
     MediaPromiseHolder<WaitForDataPromise> mWaitingPromise;
     bool HasWaitingPromise()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       return !mWaitingPromise.IsEmpty();
     }
 
     // MediaDataDecoder handler's variables.
     bool mOutputRequested;
     bool mInputExhausted;
     bool mError;
     bool mDrainComplete;
+    // If set, all decoded samples prior mTimeThreshold will be dropped.
+    // Used for internal seeking when a change of stream is detected.
+    Maybe<media::TimeUnit> mTimeThreshold;
+
     // Decoded samples returned my mDecoder awaiting being returned to
     // state machine upon request.
     nsTArray<nsRefPtr<MediaData>> mOutput;
     uint64_t mNumSamplesInput;
     uint64_t mNumSamplesOutput;
 
     // These get overriden in the templated concrete class.
     // Indicate if we have a pending promise for decoded frame.
@@ -271,27 +280,31 @@ private:
       mDemuxEOSServiced = false;
       mWaitingForData = false;
       mReceivedNewData = false;
       mDiscontinuity = true;
       mQueuedSamples.Clear();
       mOutputRequested = false;
       mInputExhausted = false;
       mDrainComplete = false;
+      mTimeThreshold.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
     }
 
     // Used by the MDSM for logging purposes.
     Atomic<size_t> mSizeOfQueue;
+    // Sample format monitoring.
+    uint32_t mLastStreamSourceID;
     // Monitor that protects all non-threadsafe state; the primitives
     // that follow.
     Monitor mMonitor;
     media::TimeIntervals mTimeRanges;
+    nsRefPtr<SharedTrackInfo> mInfo;
   };
 
   template<typename PromiseType>
   struct DecoderDataWithPromise : public DecoderData {
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
                            uint32_t aDecodeAhead) :
       DecoderData(aOwner, aType, aDecodeAhead)
@@ -388,18 +401,16 @@ private:
   void DoAudioSeek();
   void OnAudioSeekCompleted(media::TimeUnit aTime);
   void OnAudioSeekFailed(DemuxerFailureReason aFailure)
   {
     OnSeekFailed(TrackType::kAudioTrack, aFailure);
   }
   // Temporary seek information while we wait for the data
   Maybe<media::TimeUnit> mPendingSeekTime;
-  MediaPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mVideoSeekRequest;
-  MediaPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mAudioSeekRequest;
   MediaPromiseHolder<SeekPromise> mSeekPromise;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 
   nsRefPtr<SharedDecoderManager> mSharedDecoderManager;