Bug 1121692 - Make seeks cancelable. r=cpearce,r=mattwoodrow

This commit is contained in:
Bobby Holley 2015-01-16 10:58:00 -08:00
parent f5e1dd8c14
commit 986f783e03
6 changed files with 76 additions and 18 deletions

View File

@ -632,10 +632,8 @@ nsresult MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType); mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType);
mCurrentTime = aTime; mCurrentTime = aTime;
// If we are already in the seeking state, then setting mRequestedSeekTarget // If we are already in the seeking state, the new seek overrides the old one.
// above will result in the new seek occurring when the current seek if (mPlayState != PLAY_STATE_LOADING) {
// completes.
if (mPlayState != PLAY_STATE_LOADING && mPlayState != PLAY_STATE_SEEKING) {
bool paused = false; bool paused = false;
if (mOwner) { if (mOwner) {
paused = mOwner->GetPaused(); paused = mOwner->GetPaused();

View File

@ -156,6 +156,15 @@ public:
virtual nsRefPtr<SeekPromise> virtual nsRefPtr<SeekPromise>
Seek(int64_t aTime, int64_t aEndTime) = 0; Seek(int64_t aTime, int64_t aEndTime) = 0;
// Cancels an ongoing seek, if any. Any previously-requested seek is
// guaranteeed to be resolved or rejected in finite time, though no
// guarantees are made about precise nature of the resolve/reject, since the
// promise might have already dispatched a resolution or an error code before
// the cancel arrived.
//
// Must be called on the decode task queue.
virtual void CancelSeek() { };
// Called to move the reader into idle state. When the reader is // Called to move the reader into idle state. When the reader is
// created it is assumed to be active (i.e. not idle). When the media // created it is assumed to be active (i.e. not idle). When the media
// element is paused and we don't need to decode any more data, the state // element is paused and we don't need to decode any more data, the state

View File

@ -220,6 +220,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mDropVideoUntilNextDiscontinuity(false), mDropVideoUntilNextDiscontinuity(false),
mDecodeToSeekTarget(false), mDecodeToSeekTarget(false),
mWaitingForDecoderSeek(false), mWaitingForDecoderSeek(false),
mCancelingSeek(false),
mCurrentTimeBeforeSeek(0), mCurrentTimeBeforeSeek(0),
mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED), mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED),
mDecodingFrozenAtStateDecoding(false), mDecodingFrozenAtStateDecoding(false),
@ -1703,10 +1704,6 @@ void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
return; return;
} }
// MediaDecoder::mPlayState should be SEEKING while we seek, and
// in that case MediaDecoder shouldn't be calling us.
NS_ASSERTION(mState != DECODER_STATE_SEEKING,
"We shouldn't already be seeking");
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
"We should have got duration already"); "We should have got duration already");
@ -2381,12 +2378,28 @@ void MediaDecoderStateMachine::DecodeSeek()
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
if (mState != DECODER_STATE_SEEKING || if (mState != DECODER_STATE_SEEKING ||
!mSeekTarget.IsValid() || !mSeekTarget.IsValid()) {
mCurrentSeekTarget.IsValid()) {
DECODER_LOG("Early returning from DecodeSeek"); DECODER_LOG("Early returning from DecodeSeek");
return; return;
} }
// If there's already an existing seek in progress, we need to handle that.
if (mCurrentSeekTarget.IsValid()) {
// There are 3 states we might be in, listed in the order that they occur:
// (1) Waiting for the seek to be resolved.
// (2) Waiting for the seek to be resolved, having already issued a cancel.
// (3) After seek resolution, waiting for SeekComplete to run.
//
// If we're in the first state, we move to the second. Otherwise, we just wait
// for things to sort themselves out.
if (mWaitingForDecoderSeek && !mCancelingSeek) {
mReader->CancelSeek();
mCancelingSeek = true;
}
return;
}
mCurrentSeekTarget = mSeekTarget; mCurrentSeekTarget = mSeekTarget;
mSeekTarget.Reset(); mSeekTarget.Reset();
mDropAudioUntilNextDiscontinuity = HasAudio(); mDropAudioUntilNextDiscontinuity = HasAudio();
@ -2470,6 +2483,7 @@ MediaDecoderStateMachine::OnSeekCompleted(int64_t aTime)
{ {
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mWaitingForDecoderSeek = false; mWaitingForDecoderSeek = false;
mCancelingSeek = false;
// We must decode the first samples of active streams, so we can determine // We must decode the first samples of active streams, so we can determine
// the new stream time. So dispatch tasks to do that. // the new stream time. So dispatch tasks to do that.
@ -2481,12 +2495,21 @@ void
MediaDecoderStateMachine::OnSeekFailed(nsresult aResult) MediaDecoderStateMachine::OnSeekFailed(nsresult aResult)
{ {
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
bool wasCanceled = mCancelingSeek;
mWaitingForDecoderSeek = false; mWaitingForDecoderSeek = false;
// Sometimes we reject the promise for non-failure reasons, like mCancelingSeek = false;
// when we request a second seek before the previous one has
// completed.
if (NS_FAILED(aResult)) { if (NS_FAILED(aResult)) {
DecodeError(); DecodeError();
} else if (wasCanceled && mSeekTarget.IsValid() && mState == DECODER_STATE_SEEKING) {
// Try again.
mCurrentSeekTarget = mSeekTarget;
mSeekTarget.Reset();
mReader->Seek(mCurrentSeekTarget.mTime, mEndTime)
->Then(DecodeTaskQueue(), __func__, this,
&MediaDecoderStateMachine::OnSeekCompleted,
&MediaDecoderStateMachine::OnSeekFailed);
mWaitingForDecoderSeek = true;
} }
} }
@ -2549,7 +2572,12 @@ MediaDecoderStateMachine::SeekCompleted()
nsCOMPtr<nsIRunnable> stopEvent; nsCOMPtr<nsIRunnable> stopEvent;
bool isLiveStream = mDecoder->GetResource()->GetLength() == -1; bool isLiveStream = mDecoder->GetResource()->GetLength() == -1;
if (GetMediaTime() == mEndTime && !isLiveStream) { if (mSeekTarget.IsValid()) {
// A new seek target came in while we were processing the old one. No rest
// for the seeking.
DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING");
SetState(DECODER_STATE_SEEKING);
} else if (GetMediaTime() == mEndTime && !isLiveStream) {
// Seeked to end of media, move to COMPLETED state. Note we don't do // Seeked to end of media, move to COMPLETED state. Note we don't do
// this if we're playing a live stream, since the end of media will advance // this if we're playing a live stream, since the end of media will advance
// once we download more data! // once we download more data!

View File

@ -1112,6 +1112,10 @@ protected:
// until this completes. // until this completes.
bool mWaitingForDecoderSeek; bool mWaitingForDecoderSeek;
// True if we're in the process of canceling a seek. This allows us to avoid
// invoking CancelSeek() multiple times.
bool mCancelingSeek;
// We record the playback position before we seek in order to // We record the playback position before we seek in order to
// determine where the seek terminated relative to the playback position // determine where the seek terminated relative to the playback position
// we were at before the seek. // we were at before the seek.

View File

@ -627,7 +627,7 @@ MediaSourceReader::Seek(int64_t aTime, int64_t aIgnored /* Used only for ogg whi
MSE_DEBUG("MediaSourceReader(%p)::Seek(aTime=%lld, aEnd=%lld, aCurrent=%lld)", MSE_DEBUG("MediaSourceReader(%p)::Seek(aTime=%lld, aEnd=%lld, aCurrent=%lld)",
this, aTime); this, aTime);
mSeekPromise.RejectIfExists(NS_OK, __func__); MOZ_ASSERT(mSeekPromise.IsEmpty());
nsRefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__); nsRefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
if (IsShutdown()) { if (IsShutdown()) {
@ -639,9 +639,6 @@ MediaSourceReader::Seek(int64_t aTime, int64_t aIgnored /* Used only for ogg whi
// the desired time and we delay doing the seek. // the desired time and we delay doing the seek.
mPendingSeekTime = aTime; mPendingSeekTime = aTime;
// Only increment the number of expected OnSeekCompleted
// notifications if we weren't already waiting for AttemptSeek
// to complete (and they would have been accounted for already).
{ {
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mWaitingForSeekData = true; mWaitingForSeekData = true;
@ -651,6 +648,26 @@ MediaSourceReader::Seek(int64_t aTime, int64_t aIgnored /* Used only for ogg whi
return p; return p;
} }
void
MediaSourceReader::CancelSeek()
{
MOZ_ASSERT(OnDecodeThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mWaitingForSeekData) {
mSeekPromise.Reject(NS_OK, __func__);
mWaitingForSeekData = false;
mPendingSeekTime = -1;
} else if (mVideoIsSeeking) {
// NB: Currently all readers have sync Seeks(), so this is a no-op.
mVideoReader->CancelSeek();
} else if (mAudioIsSeeking) {
// NB: Currently all readers have sync Seeks(), so this is a no-op.
mAudioReader->CancelSeek();
} else {
MOZ_ASSERT(mSeekPromise.IsEmpty());
}
}
void void
MediaSourceReader::OnVideoSeekCompleted(int64_t aTime) MediaSourceReader::OnVideoSeekCompleted(int64_t aTime)
{ {

View File

@ -99,6 +99,8 @@ public:
nsRefPtr<SeekPromise> nsRefPtr<SeekPromise>
Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE;
void CancelSeek() MOZ_OVERRIDE;
// Acquires the decoder monitor, and is thus callable on any thread. // Acquires the decoder monitor, and is thus callable on any thread.
nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE; nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE;