diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index f9bee3fb7ab8..a5ed23daccfa 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -350,6 +350,71 @@ MediaDecoder::DecodedStreamGraphListener::NotifyEvent(MediaStreamGraph* aGraph, } } +class MediaDecoder::OutputStreamListener : public MediaStreamListener { +public: + OutputStreamListener(MediaDecoder* aDecoder, MediaStream* aStream) + : mDecoder(aDecoder), mStream(aStream) {} + + virtual void NotifyEvent( + MediaStreamGraph* aGraph, + MediaStreamListener::MediaStreamGraphEvent event) MOZ_OVERRIDE { + if (event == EVENT_FINISHED) { + nsRefPtr r = NS_NewRunnableMethod( + this, &OutputStreamListener::DoNotifyFinished); + aGraph->DispatchToMainThreadAfterStreamStateUpdate(r.forget()); + } + } + + void Forget() { + MOZ_ASSERT(NS_IsMainThread()); + mDecoder = nullptr; + } + +private: + void DoNotifyFinished() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mDecoder) { + return; + } + + // Remove the finished stream so it won't block the decoded stream. + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + auto& streams = mDecoder->OutputStreams(); + // Don't read |mDecoder| in the loop since removing the element will lead + // to ~OutputStreamData() which will call Forget() to reset |mDecoder|. + for (int32_t i = streams.Length() - 1; i >= 0; --i) { + auto& os = streams[i]; + MediaStream* p = os.mStream.get(); + if (p == mStream.get()) { + if (os.mPort) { + os.mPort->Destroy(); + os.mPort = nullptr; + } + streams.RemoveElementAt(i); + break; + } + } + } + + // Main thread only + MediaDecoder* mDecoder; + nsRefPtr mStream; +}; + +void +MediaDecoder::OutputStreamData::Init(MediaDecoder* aDecoder, + ProcessedMediaStream* aStream) +{ + mStream = aStream; + mListener = new OutputStreamListener(aDecoder, aStream); + aStream->AddListener(mListener); +} + +MediaDecoder::OutputStreamData::~OutputStreamData() +{ + mListener->Forget(); +} + void MediaDecoder::DestroyDecodedStream() { MOZ_ASSERT(NS_IsMainThread()); @@ -366,22 +431,20 @@ void MediaDecoder::DestroyDecodedStream() // need to be explicitly blocked again. for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) { OutputStreamData& os = mOutputStreams[i]; + // Explicitly remove all existing ports. + // This is not strictly necessary but it's good form. + MOZ_ASSERT(os.mPort, "Double-delete of the ports!"); + os.mPort->Destroy(); + os.mPort = nullptr; // During cycle collection, nsDOMMediaStream can be destroyed and send // its Destroy message before this decoder is destroyed. So we have to // be careful not to send any messages after the Destroy(). if (os.mStream->IsDestroyed()) { // Probably the DOM MediaStream was GCed. Clean up. - MOZ_ASSERT(os.mPort, "Double-delete of the ports!"); - os.mPort->Destroy(); mOutputStreams.RemoveElementAt(i); - continue; + } else { + os.mStream->ChangeExplicitBlockerCount(1); } - os.mStream->ChangeExplicitBlockerCount(1); - // Explicitly remove all existing ports. This is not strictly necessary but it's - // good form. - MOZ_ASSERT(os.mPort, "Double-delete of the ports!"); - os.mPort->Destroy(); - os.mPort = nullptr; } mDecodedStream = nullptr; @@ -429,12 +492,8 @@ void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs) // between main-thread stable states take effect atomically. for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) { OutputStreamData& os = mOutputStreams[i]; - if (os.mStream->IsDestroyed()) { - // Probably the DOM MediaStream was GCed. Clean up. - // No need to destroy the port; all ports have been destroyed here. - mOutputStreams.RemoveElementAt(i); - continue; - } + MOZ_ASSERT(!os.mStream->IsDestroyed(), + "Should've been removed in DestroyDecodedStream()"); ConnectDecodedStreamToOutputStream(&os); } UpdateStreamBlockingForStateMachinePlaying(); @@ -462,7 +521,7 @@ void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream, RecreateDecodedStream(t); } OutputStreamData* os = mOutputStreams.AppendElement(); - os->Init(aStream, aFinishWhenEnded); + os->Init(this, aStream); ConnectDecodedStreamToOutputStream(os); if (aFinishWhenEnded) { // Ensure that aStream finishes the moment mDecodedStream does. @@ -945,32 +1004,6 @@ void MediaDecoder::PlaybackEnded() return; } - { - ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - - for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) { - OutputStreamData& os = mOutputStreams[i]; - if (os.mStream->IsDestroyed()) { - // Probably the DOM MediaStream was GCed. Clean up. - MOZ_ASSERT(os.mPort, "Double-delete of the ports!"); - os.mPort->Destroy(); - mOutputStreams.RemoveElementAt(i); - continue; - } - if (os.mFinishWhenEnded) { - // Shouldn't really be needed since mDecodedStream should already have - // finished, but doesn't hurt. - os.mStream->Finish(); - MOZ_ASSERT(os.mPort, "Double-delete of the ports!"); - os.mPort->Destroy(); - // Not really needed but it keeps the invariant that a stream not - // connected to mDecodedStream is explicity blocked. - os.mStream->ChangeExplicitBlockerCount(1); - mOutputStreams.RemoveElementAt(i); - } - } - } - PlaybackPositionChanged(); ChangeState(PLAY_STATE_ENDED); InvalidateWithFlags(VideoFrameContainer::INVALIDATE_FORCE); diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 9c80585afe01..dccef419404a 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -479,17 +479,17 @@ public: bool mStreamFinishedOnMainThread; }; + class OutputStreamListener; + struct OutputStreamData { - void Init(ProcessedMediaStream* aStream, bool aFinishWhenEnded) - { - mStream = aStream; - mFinishWhenEnded = aFinishWhenEnded; - } + void Init(MediaDecoder* aDecoder, ProcessedMediaStream* aStream); + ~OutputStreamData(); nsRefPtr mStream; // mPort connects mDecodedStream->mStream to our mStream. nsRefPtr mPort; - bool mFinishWhenEnded; + nsRefPtr mListener; }; + /** * Connects mDecodedStream->mStream to aStream->mStream. */