Bug 934425 - Implement asynchronous method to switch sink in MediaDecoder. r?bryce
MozReview-Commit-ID: K1w8LKOOOxe
--- 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.