From 2cab1f445f52b792d2d636d3e5059e937627c494 Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Wed, 24 Apr 2019 10:56:16 +0000 Subject: [PATCH] Bug 1536766 - End a track only after the graph has reported reaching its end time in DecodedStream. r=jya,padenot This gives us a guarantee that the first frame of a media file can be rendered with a second media element and mozCaptureStream(), even if the file is very very short. With longer video-only files there were also cases where nothing ended up being rendered. Probably because the MediaStreamGraph ended up switching from an AudioCallbackDriver to a SystemClockDriver and this took enough time to put the SourceMediaStream::EndTrack and the SourceMediaStream::AddTrackListener calls for this video track to be processed in the same iteration. The listener would then always lose to the ending track and update main thread state too late, leading to its media element not rendering any frames and nasty intermittent failures. Differential Revision: https://phabricator.services.mozilla.com/D27270 --HG-- extra : moz-landing-system : lando --- dom/media/mediasink/DecodedStream.cpp | 47 +++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/dom/media/mediasink/DecodedStream.cpp b/dom/media/mediasink/DecodedStream.cpp index 2220c1400447..0448ffbfe1dc 100644 --- a/dom/media/mediasink/DecodedStream.cpp +++ b/dom/media/mediasink/DecodedStream.cpp @@ -92,6 +92,17 @@ class DecodedStreamGraphListener { } void NotifyOutput(TrackID aTrackID, StreamTime aCurrentTrackTime) { + if (aTrackID == mAudioTrackID) { + if (aCurrentTrackTime >= mAudioEnd) { + mStream->EndTrack(mAudioTrackID); + } + } else if (aTrackID == mVideoTrackID) { + if (aCurrentTrackTime >= mVideoEnd) { + mStream->EndTrack(mVideoTrackID); + } + } else { + MOZ_CRASH("Unexpected TrackID"); + } if (aTrackID != mAudioTrackID && mAudioTrackID != TRACK_NONE && !mAudioEnded) { // Only audio playout drives the clock forward, if present and live. @@ -120,6 +131,36 @@ class DecodedStreamGraphListener { TrackID VideoTrackID() const { return mVideoTrackID; } + /** + * Tell the graph listener to end the given track after it has seen at least + * aEnd worth of output reported as processed by the graph. + * + * A StreamTime of STREAM_TIME_MAX indicates that the track has no end and is + * the default. + * + * This method of ending tracks is needed because the MediaStreamGraph + * processes ended tracks (through SourceMediaStream::EndTrack) at the + * beginning of an iteration, but waits until the end of the iteration to + * process any ControlMessages. When such a ControlMessage is a listener that + * is to be added to a track that has ended in its very first iteration, the + * track ends before the listener tracking this ending is added. This can lead + * to a MediaStreamTrack ending on main thread (it uses another listener) + * before the listeners to render the track get added, potentially meaning a + * media element doesn't progress before reaching the end although data was + * available. + * + * Callable from any thread. + */ + void EndTrackAt(TrackID aTrackID, StreamTime aEnd) { + if (aTrackID == mAudioTrackID) { + mAudioEnd = aEnd; + } else if (aTrackID == mVideoTrackID) { + mVideoEnd = aEnd; + } else { + MOZ_CRASH("Unexpected TrackID"); + } + } + void DoNotifyTrackEnded(TrackID aTrackID) { MOZ_ASSERT(NS_IsMainThread()); if (aTrackID == mAudioTrackID) { @@ -172,7 +213,9 @@ class DecodedStreamGraphListener { // Any thread. const RefPtr mStream; const TrackID mAudioTrackID; + Atomic mAudioEnd{STREAM_TIME_MAX}; const TrackID mVideoTrackID; + Atomic mVideoEnd{STREAM_TIME_MAX}; const RefPtr mAbstractMainThread; }; @@ -617,7 +660,7 @@ void DecodedStream::SendAudio(double aVolume, bool aIsSameOrigin, } if (mAudioQueue.IsFinished() && !mData->mHaveSentFinishAudio) { - sourceStream->EndTrack(audioTrackId); + mData->mListener->EndTrackAt(audioTrackId, mData->mStreamAudioWritten); mData->mHaveSentFinishAudio = true; } } @@ -810,7 +853,7 @@ void DecodedStream::SendVideo(bool aIsSameOrigin, mData->mStreamVideoWritten += sourceStream->AppendToTrack(videoTrackId, &endSegment); } - sourceStream->EndTrack(videoTrackId); + mData->mListener->EndTrackAt(videoTrackId, mData->mStreamVideoWritten); mData->mHaveSentFinishVideo = true; } }