Bug 934425 - Implement asynchronous method to switch sink in MediaDecoder. r?bryce draft
authorAlex Chronopoulos <achronop@gmail.com>
Thu, 09 Aug 2018 17:14:14 +0300
changeset 827854 01c1dcdc7877bbdb5baddba3bd2822c1d3eddb39
parent 827853 44bfb340b80b9e45b9232ae68d731b8a189ce1a4
child 827855 f79db264e82c5b7a931818296312515122a1873e
push id118601
push userachronop@gmail.com
push dateThu, 09 Aug 2018 14:17:09 +0000
reviewersbryce
bugs934425
milestone63.0a1
Bug 934425 - Implement asynchronous method to switch sink in MediaDecoder. r?bryce MozReview-Commit-ID: K1w8LKOOOxe
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -163,16 +163,24 @@ MediaDecoder::Pause()
 void
 MediaDecoder::SetVolume(double aVolume)
 {
   MOZ_ASSERT(NS_IsMainThread());
   AbstractThread::AutoEnter context(AbstractMainThread());
   mVolume = aVolume;
 }
 
+RefPtr<GenericPromise>
+MediaDecoder::SetSinkDevice(AudioDeviceInfo* aInfo)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  AbstractThread::AutoEnter context(AbstractMainThread());
+  return GetStateMachine()->SetSinkDevice(aInfo);
+}
+
 void
 MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
                               TrackID aNextAvailableTrackID,
                               bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   AbstractThread::AutoEnter context(AbstractMainThread());
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -152,16 +152,19 @@ public:
   virtual void Pause();
   // Adjust the speed of the playback, optionally with pitch correction,
   void SetVolume(double aVolume);
 
   void SetPlaybackRate(double aPlaybackRate);
   void SetPreservesPitch(bool aPreservesPitch);
   void SetLooping(bool aLooping);
 
+  // Set the given device as the output device.
+  RefPtr<GenericPromise> SetSinkDevice(AudioDeviceInfo* aInfo);
+
   bool GetMinimizePreroll() const { return mMinimizePreroll; }
 
   // All MediaStream-related data is protected by mReentrantMonitor.
   // We have at most one DecodedStreamData per MediaDecoder. Its stream
   // is used as the input for each ProcessedMediaStream created by calls to
   // captureStream(UntilEnded). Seeking creates a new source stream, as does
   // replaying after the input as ended. In the latter case, the new source is
   // not connected to streams created by captureStreamUntilEnded.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -3298,23 +3298,24 @@ MediaDecoderStateMachine::WaitForData(Me
       },
       [self] (const WaitForDataRejectValue& aRejection) {
         self->mVideoWaitRequest.Complete();
         self->DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
       })->Track(mVideoWaitRequest);
   }
 }
 
-void
+nsresult
 MediaDecoderStateMachine::StartMediaSink()
 {
   MOZ_ASSERT(OnTaskQueue());
+  nsresult rv = NS_OK;
   if (!mMediaSink->IsStarted()) {
     mAudioCompleted = false;
-    mMediaSink->Start(GetMediaTime(), Info());
+    rv = mMediaSink->Start(GetMediaTime(), Info());
 
     auto videoPromise = mMediaSink->OnEnded(TrackInfo::kVideoTrack);
     auto audioPromise = mMediaSink->OnEnded(TrackInfo::kAudioTrack);
 
     if (audioPromise) {
       audioPromise->Then(
         OwnerThread(), __func__, this,
         &MediaDecoderStateMachine::OnMediaSinkAudioComplete,
@@ -3332,16 +3333,17 @@ MediaDecoderStateMachine::StartMediaSink
     // to calculate the rate at which bytes are consumed as playback moves on.
     RefPtr<MediaData> sample = mAudioQueue.PeekFront();
     mPlaybackOffset = sample ? sample->mOffset : 0;
     sample = mVideoQueue.PeekFront();
     if (sample && sample->mOffset > mPlaybackOffset) {
       mPlaybackOffset = sample->mOffset;
     }
   }
+  return rv;
 }
 
 bool
 MediaDecoderStateMachine::HasLowDecodedAudio()
 {
   MOZ_ASSERT(OnTaskQueue());
   return IsAudioDecoding() && GetDecodedAudioDuration()
                               < EXHAUSTED_DATA_MARGIN.MultDouble(mPlaybackRate);
@@ -3659,16 +3661,74 @@ void
 MediaDecoderStateMachine::LoopingChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mSeamlessLoopingAllowed) {
     mReader->SetSeamlessLoopingEnabled(mLooping);
   }
 }
 
+RefPtr<GenericPromise>
+MediaDecoderStateMachine::SetSinkDevice(AudioDeviceInfo* aInfo)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aInfo);
+  RefPtr<GenericPromise::Private> p = new GenericPromise::Private(__func__);
+
+  if (mAudioCaptured) {
+    // Not supported yet
+    p->Reject(NS_ERROR_ABORT, __func__);
+    return p.forget();
+  }
+
+  RefPtr<MediaDecoderStateMachine> self = this;
+  RefPtr<AudioDeviceInfo> sinkInfo = aInfo;
+
+  nsresult rv = OwnerThread()->Dispatch(
+    NS_NewRunnableFunction(
+      "MediaDecoderStateMachine::SwitchSinkDevice",
+      [self, p, sinkInfo] () {
+        if (self->mMediaSink->IsStarted()) {
+          // Backup current playback parameters.
+          MediaSink::PlaybackParams params = self->mMediaSink->GetPlaybackParams();
+          params.mSink = sinkInfo;
+          bool wasPlaying = self->mMediaSink->IsPlaying();
+          // Stop and shut down the existing sink.
+          self->StopMediaSink();
+          self->mMediaSink->Shutdown();
+          // Create a new sink according to whether audio is captured.
+          self->mMediaSink = self->CreateMediaSink(false);
+          // Restore playback parameters.
+          self->mMediaSink->SetPlaybackParams(params);
+          // Start the new sink
+          nsresult rv = NS_OK;
+          if (wasPlaying) {
+            rv = self->StartMediaSink();
+          }
+
+          if (NS_FAILED(rv)) {
+            p->Reject(NS_ERROR_ABORT, __func__);
+          } else {
+            p->Resolve(true, __func__);
+          }
+        } else {
+          MediaSink::PlaybackParams params = self->mMediaSink->GetPlaybackParams();
+          params.mSink = sinkInfo;
+          self->mMediaSink->SetPlaybackParams(params);
+          p->Resolve(false, __func__);
+        }
+      }),
+  AbstractThread::TailDispatch);
+
+  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+  Unused << rv;
+
+  return p.forget();
+}
+
 TimeUnit
 MediaDecoderStateMachine::AudioEndTime() const
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mMediaSink->IsStarted()) {
     return mMediaSink->GetEndTime(TrackInfo::kAudioTrack);
   }
   return GetMediaTime();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -291,16 +291,19 @@ public:
 
   size_t SizeOfVideoQueue() const;
 
   size_t SizeOfAudioQueue() const;
 
   // Sets the video decode mode. Used by the suspend-video-decoder feature.
   void SetVideoDecodeMode(VideoDecodeMode aMode);
 
+  // Set new sink device and restart MediaSink when playback is started.
+  RefPtr<GenericPromise> SetSinkDevice(AudioDeviceInfo* aInfo);
+
 private:
   class StateObject;
   class DecodeMetadataState;
   class DormantState;
   class DecodingFirstFrameState;
   class DecodingState;
   class SeekingState;
   class AccurateSeekingState;
@@ -442,17 +445,17 @@ protected:
   // Stops the media sink and shut it down.
   // The decoder monitor must be held with exactly one lock count.
   // Called on the state machine thread.
   void StopMediaSink();
 
   // Create and start the media sink.
   // The decoder monitor must be held with exactly one lock count.
   // Called on the state machine thread.
-  void StartMediaSink();
+  nsresult StartMediaSink();
 
   // Notification method invoked when mPlayState changes.
   void PlayStateChanged();
 
   // Notification method invoked when mIsVisible changes.
   void VisibilityChanged();
 
   // Sets internal state which causes playback of media to pause.