--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1359,77 +1359,116 @@ public:
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob aSeekJob,
EventVisibility aVisibility)
{
MOZ_ASSERT(aSeekJob.mTarget.IsNextFrame());
return SeekingState::Enter(Move(aSeekJob), aVisibility);
}
+ void Exit() override
+ {
+ // Disconnect my async seek operation.
+ mAsyncSeekTask->Cancel();
+
+ // Disconnect MediaDecoder.
+ mSeekJob.RejectIfExists(__func__);
+ }
+
private:
+ // VisualStudio does not allow inner class to access protected member of the
+ // enclosing class' parent class. Redefine AudioQueue()/VideoQueue() here so
+ // that AysncNextFrameSeekTask could use these version instead of
+ // StateObject::{Audio,Video}Queue().
+ MediaQueue<MediaData>& AudioQueue() const { return mMaster->mAudioQueue; }
+ MediaQueue<MediaData>& VideoQueue() const { return mMaster->mVideoQueue; }
+
+ class AysncNextFrameSeekTask : public Runnable
+ {
+ public:
+ explicit AysncNextFrameSeekTask(NextFrameSeekingState* aStateObject)
+ : mStateObj(aStateObject)
+ {
+ }
+
+ ~AysncNextFrameSeekTask() {}
+
+ void Cancel() { mIsCancelled = true; }
+
+ NS_IMETHOD Run()
+ {
+ if (!mIsCancelled) {
+ auto currentTime = mStateObj->mCurrentTime;
+ DiscardFrames(mStateObj->VideoQueue(), [currentTime] (int64_t aSampleTime) {
+ return aSampleTime <= currentTime;
+ });
+
+ if (!mStateObj->IsVideoRequestPending() && mStateObj->NeedMoreVideo()) {
+ mStateObj->RequestVideoData();
+ }
+
+ mStateObj->MaybeFinishSeek(); // Might resolve mSeekTaskPromise and modify audio queue.
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ bool mIsCancelled = false;
+ NextFrameSeekingState* mStateObj;
+ };
+
void CreateSeekTask() override
{
mCurrentTime = mMaster->GetMediaTime();
mDuration = mMaster->Duration();
-
- mSeekTask = new NextFrameSeekTask(
- mMaster->mDecoderID, OwnerThread(), Reader(), mSeekJob.mTarget,
- Info(), mMaster->Duration(),mMaster->GetMediaTime(),
- AudioQueue(), VideoQueue());
-
- mTask = static_cast<NextFrameSeekTask*>(mSeekTask.get());
}
void ResetMDSM() override
{
// Do nothing.
}
void DoSeek() override
{
- auto currentTime = mCurrentTime;
- DiscardFrames(VideoQueue(), [currentTime] (int64_t aSampleTime) {
- return aSampleTime <= currentTime;
- });
-
- mSeekTaskRequest.Begin(mSeekTask->Seek(mMaster->Duration())
- ->Then(OwnerThread(), __func__,
- [this] (const SeekTaskResolveValue& aValue) {
- OnSeekTaskResolved(aValue);
- },
- [this] (const SeekTaskRejectValue& aValue) {
- OnSeekTaskRejected(aValue);
- }));
-
- if (!IsVideoRequestPending() && NeedMoreVideo()) {
- RequestVideoData();
- }
- MaybeFinishSeek(); // Might resolve mSeekTaskPromise and modify audio queue.
+ // We need to do the seek operation asynchronously. Because for a special
+ // case (bug504613.ogv) which has no data at all, the 1st seekToNextFrame()
+ // operation reaches to the end of the media. If we did the seek operation
+ // synchronously, we immediately resolve the SeekPromise in mSeekJob and
+ // then switch to the CompletedState which dispatches an "ended" event.
+ // However, the ThenValue of the SeekPromise has not yet been set, so the
+ // promise resolving is postponed and then the JS developer receives the
+ // "ended" event before the seek promise is resolved.
+ // An asynchronous seek operation helps to solve this issue since while the
+ // seek is actually performed, the ThenValue of SeekPromise has already
+ // been set so that it won't be postponed.
+ RefPtr<Runnable> r = mAsyncSeekTask = new AysncNextFrameSeekTask(this);
+ OwnerThread()->Dispatch(r.forget());
}
void HandleAudioDecoded(MediaData* aAudio) override
{
MOZ_ASSERT(aAudio);
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+ MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
// The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
// resolved.
SSAMPLELOG("OnAudioDecoded [%lld,%lld]", aAudio->mTime, aAudio->GetEndTime());
// We accept any audio data here.
mSeekedAudioData = aAudio;
MaybeFinishSeek();
}
void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
{
MOZ_ASSERT(aVideo);
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+ MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
// The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
// resolved.
SSAMPLELOG("OnVideoDecoded [%lld,%lld]", aVideo->mTime, aVideo->GetEndTime());
if (aVideo->mTime > mCurrentTime) {
mSeekedVideoData = aVideo;
@@ -1440,34 +1479,32 @@ private:
return;
}
MaybeFinishSeek();
}
void HandleNotDecoded(MediaData::Type aType, const MediaResult& aError) override
{
+ MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
+
switch (aType) {
case MediaData::AUDIO_DATA:
{
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
-
SSAMPLELOG("OnAudioNotDecoded (aError=%u)", aError.Code());
// We don't really handle audio deocde error here. Let MDSM to trigger further
// audio decoding tasks if it needs to play audio, and MDSM will then receive
// the decoding state from MediaDecoderReader.
MaybeFinishSeek();
break;
}
case MediaData::VIDEO_DATA:
{
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
-
SSAMPLELOG("OnVideoNotDecoded (aError=%u)", aError.Code());
if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
mIsVideoQueueFinished = true;
}
// Video seek not finished.
if (NeedMoreVideo()) {
@@ -1478,67 +1515,67 @@ private:
case NS_ERROR_DOM_MEDIA_CANCELED:
RequestVideoData();
break;
case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
MOZ_ASSERT(false, "Shouldn't want more data for ended video.");
break;
default:
// Reject the promise since we can't finish video seek anyway.
- mTask->RejectIfExist(aError, __func__);
+ OnSeekTaskRejected(aError);
break;
}
return;
}
MaybeFinishSeek();
break;
}
default:
MOZ_ASSERT_UNREACHABLE("We cannot handle RAW_DATA or NULL_DATA here.");
}
}
void HandleAudioWaited(MediaData::Type aType) override
{
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+ MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
// We don't make an audio decode request here, instead, let MDSM to
// trigger further audio decode tasks if MDSM itself needs to play audio.
MaybeFinishSeek();
}
void HandleVideoWaited(MediaData::Type aType) override
{
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+ MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
if (NeedMoreVideo()) {
RequestVideoData();
return;
}
MaybeFinishSeek();
}
void HandleNotWaited(const WaitForDataRejectValue& aRejection) override
{
- MOZ_ASSERT(mSeekTaskRequest.Exists(), "Seek shouldn't be finished");
+ MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
switch(aRejection.mType) {
case MediaData::AUDIO_DATA:
{
// We don't make an audio decode request here, instead, let MDSM to
// trigger further audio decode tasks if MDSM itself needs to play audio.
MaybeFinishSeek();
break;
}
case MediaData::VIDEO_DATA:
{
if (NeedMoreVideo()) {
// Reject if we can't finish video seeking.
- mTask->RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ OnSeekTaskRejected(NS_ERROR_DOM_MEDIA_CANCELED);
return;
}
MaybeFinishSeek();
break;
}
default:
MOZ_ASSERT_UNREACHABLE("We cannot handle RAW_DATA or NULL_DATA here.");
}
@@ -1546,56 +1583,52 @@ private:
int64_t CalculateNewCurrentTime() const override
{
// The HTMLMediaElement.currentTime should be updated to the seek target
// which has been updated to the next frame's time.
return mSeekJob.mTarget.GetTime().ToMicroseconds();
}
- void OnSeekTaskResolved(const SeekTaskResolveValue& aValue)
+ void OnSeekTaskResolved()
{
- mSeekTaskRequest.Complete();
-
- if (aValue.mSeekedAudioData) {
- mMaster->Push(aValue.mSeekedAudioData);
+ if (mSeekedAudioData) {
+ mMaster->Push(mSeekedAudioData);
mMaster->mDecodedAudioEndTime = std::max(
- aValue.mSeekedAudioData->GetEndTime(), mMaster->mDecodedAudioEndTime);
+ mSeekedAudioData->GetEndTime(), mMaster->mDecodedAudioEndTime);
}
- if (aValue.mSeekedVideoData) {
- mMaster->Push(aValue.mSeekedVideoData);
+ if (mSeekedVideoData) {
+ mMaster->Push(mSeekedVideoData);
mMaster->mDecodedVideoEndTime = std::max(
- aValue.mSeekedVideoData->GetEndTime(), mMaster->mDecodedVideoEndTime);
+ mSeekedVideoData->GetEndTime(), mMaster->mDecodedVideoEndTime);
}
- if (aValue.mIsAudioQueueFinished) {
+ if (mIsAudioQueueFinished) {
AudioQueue().Finish();
}
- if (aValue.mIsVideoQueueFinished) {
+ if (mIsVideoQueueFinished) {
VideoQueue().Finish();
}
SeekCompleted();
}
- void OnSeekTaskRejected(const SeekTaskRejectValue& aValue)
+ void OnSeekTaskRejected(MediaResult aError)
{
- mSeekTaskRequest.Complete();
-
- if (aValue.mIsAudioQueueFinished) {
+ if (mIsAudioQueueFinished) {
AudioQueue().Finish();
}
- if (aValue.mIsVideoQueueFinished) {
+ if (mIsVideoQueueFinished) {
VideoQueue().Finish();
}
- mMaster->DecodeError(aValue.mError);
+ mMaster->DecodeError(aError);
}
void RequestVideoData()
{
Reader()->RequestVideoData(false, media::TimeUnit());
}
bool NeedMoreVideo() const
@@ -1647,37 +1680,34 @@ private:
if (IsAudioSeekComplete() && IsVideoSeekComplete()) {
UpdateSeekTargetTime();
auto time = mSeekJob.mTarget.GetTime().ToMicroseconds();
DiscardFrames(AudioQueue(), [time] (int64_t aSampleTime) {
return aSampleTime < time;
});
- mTask->Resolve(__func__); // Call to MDSM::SeekCompleted();
+ OnSeekTaskResolved();
}
}
/*
* Internal state.
*/
int64_t mCurrentTime;
media::TimeUnit mDuration;
+ RefPtr<AysncNextFrameSeekTask> mAsyncSeekTask;
/*
* Information which are going to be returned to MDSM.
*/
RefPtr<MediaData> mSeekedAudioData;
RefPtr<MediaData> mSeekedVideoData;
bool mIsAudioQueueFinished = false;
bool mIsVideoQueueFinished = false;
-
- // For refactoring only, will be removed later.
- RefPtr<NextFrameSeekTask> mTask;
-
};
/**
* Purpose: stop playback until enough data is decoded to continue playback.
*
* Transition to:
* SEEKING if any seek request.
* SHUTDOWN if any decode error.