Bug 943461. Part 8: When a MediaDecoder is decoding to a stream, run PlaybackEnded when the stream finishes. r=padenot
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -195,55 +195,69 @@ MediaDecoder::DecodedStreamData::Decoded
mStreamInitialized(false),
mHaveSentFinish(false),
mHaveSentFinishAudio(false),
mHaveSentFinishVideo(false),
mStream(aStream),
mHaveBlockedForPlayState(false),
mHaveBlockedForStateMachineNotPlaying(false)
{
- mStream->AddMainThreadListener(this);
- mListener = new DecodedStreamGraphListener(mStream);
+ mListener = new DecodedStreamGraphListener(mStream, this);
mStream->AddListener(mListener);
}
MediaDecoder::DecodedStreamData::~DecodedStreamData()
{
- mStream->RemoveMainThreadListener(this);
mListener->Forget();
mStream->Destroy();
}
-void
-MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
-{
- mDecoder->NotifyDecodedStreamMainThreadStateChanged();
- if (mStream->IsFinished()) {
- mListener->SetFinishedOnMainThread(true);
- }
-}
-
-MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream)
- : mMutex("MediaDecoder::DecodedStreamData::mMutex"),
+MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream,
+ DecodedStreamData* aData)
+ : mData(aData),
+ mMutex("MediaDecoder::DecodedStreamData::mMutex"),
mStream(aStream),
mLastOutputTime(aStream->GetCurrentTime()),
mStreamFinishedOnMainThread(false)
{
}
void
MediaDecoder::DecodedStreamGraphListener::NotifyOutput(MediaStreamGraph* aGraph,
GraphTime aCurrentTime)
{
MutexAutoLock lock(mMutex);
if (mStream) {
mLastOutputTime = mStream->GraphTimeToStreamTime(aCurrentTime);
}
}
+void
+MediaDecoder::DecodedStreamGraphListener::DoNotifyFinished()
+{
+ if (mData && mData->mDecoder) {
+ if (mData->mDecoder->GetState() == PLAY_STATE_PLAYING) {
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(mData->mDecoder, &MediaDecoder::PlaybackEnded);
+ NS_DispatchToCurrentThread(event);
+ }
+ }
+
+ MutexAutoLock lock(mMutex);
+ mStreamFinishedOnMainThread = true;
+}
+
+void
+MediaDecoder::DecodedStreamGraphListener::NotifyFinished(MediaStreamGraph* aGraph)
+{
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
+ aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+}
+
void MediaDecoder::DestroyDecodedStream()
{
MOZ_ASSERT(NS_IsMainThread());
GetReentrantMonitor().AssertCurrentThreadIn();
// All streams are having their SourceMediaStream disconnected, so they
// need to be explicitly blocked again.
for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
@@ -321,29 +335,16 @@ void MediaDecoder::RecreateDecodedStream
UpdateStreamBlockingForStateMachinePlaying();
mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING;
if (mDecodedStream->mHaveBlockedForPlayState) {
mDecodedStream->mStream->ChangeExplicitBlockerCount(1);
}
}
-void MediaDecoder::NotifyDecodedStreamMainThreadStateChanged()
-{
- if (mTriggerPlaybackEndedWhenSourceStreamFinishes && mDecodedStream &&
- mDecodedStream->mStream->IsFinished()) {
- mTriggerPlaybackEndedWhenSourceStreamFinishes = false;
- if (GetState() == PLAY_STATE_PLAYING) {
- nsCOMPtr<nsIRunnable> event =
- NS_NewRunnableMethod(this, &MediaDecoder::PlaybackEnded);
- NS_DispatchToCurrentThread(event);
- }
- }
-}
-
void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
bool aFinishWhenEnded)
{
MOZ_ASSERT(NS_IsMainThread());
DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoder::AddOutputStream this=%p aStream=%p!",
this, aStream));
{
@@ -416,17 +417,16 @@ MediaDecoder::MediaDecoder() :
mIsDormant(false),
mIsExitingDormant(false),
mPlayState(PLAY_STATE_PAUSED),
mNextState(PLAY_STATE_PAUSED),
mRequestedSeekTime(-1.0),
mCalledResourceLoaded(false),
mIgnoreProgressData(false),
mInfiniteStream(false),
- mTriggerPlaybackEndedWhenSourceStreamFinishes(false),
mOwner(nullptr),
mFrameBufferLength(0),
mPinnedForSeek(false),
mShuttingDown(false),
mPausedForPlaybackRateNull(false),
mAudioChannelType(AUDIO_CHANNEL_NORMAL)
{
MOZ_COUNT_CTOR(MediaDecoder);
@@ -931,22 +931,16 @@ void MediaDecoder::PlaybackEnded()
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown || mPlayState == MediaDecoder::PLAY_STATE_SEEKING)
return;
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
- if (mDecodedStream && !mDecodedStream->mStream->IsFinished()) {
- // Wait for it to finish before firing PlaybackEnded()
- mTriggerPlaybackEndedWhenSourceStreamFinishes = true;
- return;
- }
-
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.
os.mPort->Destroy();
mOutputStreams.RemoveElementAt(i);
continue;
}
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -345,23 +345,21 @@ public:
// 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.
- struct DecodedStreamData MOZ_FINAL : public MainThreadMediaStreamListener {
+ struct DecodedStreamData {
DecodedStreamData(MediaDecoder* aDecoder,
int64_t aInitialTime, SourceMediaStream* aStream);
~DecodedStreamData();
- virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
-
StreamTime GetLastOutputTime() { return mListener->GetLastOutputTime(); }
bool IsFinished() { return mListener->IsFinishedOnMainThread(); }
// The following group of fields are protected by the decoder's monitor
// and can be read or written on any thread.
int64_t mLastAudioPacketTime; // microseconds
int64_t mLastAudioPacketEndTime; // microseconds
// Count of audio frames written to the stream
@@ -395,40 +393,51 @@ public:
bool mHaveBlockedForPlayState;
// We also have an explicit blocker on the stream when
// mDecoderStateMachine is non-null and MediaDecoderStateMachine is false.
bool mHaveBlockedForStateMachineNotPlaying;
};
class DecodedStreamGraphListener : public MediaStreamListener {
public:
- DecodedStreamGraphListener(MediaStream* aStream);
+ DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData);
virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) MOZ_OVERRIDE;
+ virtual void NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+
+ void DoNotifyFinished();
StreamTime GetLastOutputTime()
{
MutexAutoLock lock(mMutex);
return mLastOutputTime;
}
void Forget()
{
+ NS_ASSERTION(NS_IsMainThread(), "Main thread only");
+ mData = nullptr;
+
MutexAutoLock lock(mMutex);
mStream = nullptr;
}
- void SetFinishedOnMainThread(bool aFinished)
+ bool SetFinishedOnMainThread(bool aFinished)
{
MutexAutoLock lock(mMutex);
+ bool result = !mStreamFinishedOnMainThread;
mStreamFinishedOnMainThread = aFinished;
+ return result;
}
bool IsFinishedOnMainThread()
{
MutexAutoLock lock(mMutex);
return mStreamFinishedOnMainThread;
}
private:
+ // Main thread only
+ DecodedStreamData* mData;
+
Mutex mMutex;
// Protected by mMutex
nsRefPtr<MediaStream> mStream;
// Protected by mMutex
StreamTime mLastOutputTime;
// Protected by mMutex
bool mStreamFinishedOnMainThread;
};
@@ -460,22 +469,16 @@ public:
* Decoder monitor must be held.
*/
void RecreateDecodedStream(int64_t aStartTimeUSecs);
/**
* Call this when mDecoderStateMachine or mDecoderStateMachine->IsPlaying() changes.
* Decoder monitor must be held.
*/
void UpdateStreamBlockingForStateMachinePlaying();
- /**
- * Called when the state of mDecodedStream as visible on the main thread
- * has changed. In particular we want to know when the stream has finished
- * so we can call PlaybackEnded.
- */
- void NotifyDecodedStreamMainThreadStateChanged();
nsTArray<OutputStreamData>& OutputStreams()
{
GetReentrantMonitor().AssertCurrentThreadIn();
return mOutputStreams;
}
DecodedStreamData* GetDecodedStream()
{
GetReentrantMonitor().AssertCurrentThreadIn();
@@ -1129,20 +1132,16 @@ protected:
// during seek and duration operations to prevent the progress indicator
// from jumping around. Read/Write from any thread. Must have decode monitor
// locked before accessing.
bool mIgnoreProgressData;
// True if the stream is infinite (e.g. a webradio).
bool mInfiniteStream;
- // True if NotifyDecodedStreamMainThreadStateChanged should retrigger
- // PlaybackEnded when mDecodedStream->mStream finishes.
- bool mTriggerPlaybackEndedWhenSourceStreamFinishes;
-
// Start timer to update download progress information.
nsresult StartProgress();
// Stop progress information timer.
nsresult StopProgress();
// Ensures our media stream has been pinned.
void PinForSeek();
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -2364,20 +2364,24 @@ nsresult MediaDecoderStateMachine::RunSt
if (mState != DECODER_STATE_COMPLETED) {
// While we're presenting a frame we can change state. Whatever changed
// our state should have scheduled another state machine run.
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
return NS_OK;
}
StopAudioThread();
- if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING) {
+ // When we're decoding to a stream, the stream's main-thread finish signal
+ // will take care of calling MediaDecoder::PlaybackEnded.
+ if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
+ !mDecoder->GetDecodedStream()) {
int64_t videoTime = HasVideo() ? mVideoFrameEndTime : 0;
int64_t clockTime = std::max(mEndTime, std::max(videoTime, GetAudioClock()));
UpdatePlaybackPosition(clockTime);
+
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
}
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -47,16 +47,19 @@ public:
mappedTracksFinished.AppendElement(true);
mappedTracksWithMatchingInputTracks.AppendElement(false);
}
bool allFinished = true;
bool allHaveCurrentData = true;
for (uint32_t i = 0; i < mInputs.Length(); ++i) {
MediaStream* stream = mInputs[i]->GetSource();
if (!stream->IsFinishedOnGraphThread()) {
+ // XXX we really should check whether 'stream' has finished within time aTo,
+ // not just that it's finishing when all its queued data eventually runs
+ // out.
allFinished = false;
}
if (!stream->HasCurrentData()) {
allHaveCurrentData = false;
}
for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer());
!tracks.IsEnded(); tracks.Next()) {
bool found = false;