diff options
Diffstat (limited to 'dom/media/NextFrameSeekTask.cpp')
-rw-r--r-- | dom/media/NextFrameSeekTask.cpp | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/dom/media/NextFrameSeekTask.cpp b/dom/media/NextFrameSeekTask.cpp new file mode 100644 index 000000000..2a7e90bf1 --- /dev/null +++ b/dom/media/NextFrameSeekTask.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NextFrameSeekTask.h" +#include "MediaDecoderReaderWrapper.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Assertions.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +extern LazyLogModule gMediaSampleLog; + +#define SAMPLE_LOG(x, ...) MOZ_LOG(gMediaSampleLog, LogLevel::Debug, \ + ("[NextFrameSeekTask] Decoder=%p " x, mDecoderID, ##__VA_ARGS__)) + +namespace media { + +NextFrameSeekTask::NextFrameSeekTask(const void* aDecoderID, + AbstractThread* aThread, + MediaDecoderReaderWrapper* aReader, + const SeekTarget& aTarget, + const MediaInfo& aInfo, + const media::TimeUnit& aDuration, + int64_t aCurrentTime, + MediaQueue<MediaData>& aAudioQueue, + MediaQueue<MediaData>& aVideoQueue) + : SeekTask(aDecoderID, aThread, aReader, aTarget) + , mAudioQueue(aAudioQueue) + , mVideoQueue(aVideoQueue) + , mCurrentTime(aCurrentTime) + , mDuration(aDuration) +{ + AssertOwnerThread(); + MOZ_ASSERT(aInfo.HasVideo()); + + // Configure MediaDecoderReaderWrapper. + SetCallbacks(); +} + +NextFrameSeekTask::~NextFrameSeekTask() +{ + AssertOwnerThread(); + MOZ_ASSERT(mIsDiscarded); +} + +void +NextFrameSeekTask::Discard() +{ + AssertOwnerThread(); + + // Disconnect MDSM. + RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + + // Disconnect MediaDecoderReader. + CancelCallbacks(); + + mIsDiscarded = true; +} + +bool +NextFrameSeekTask::NeedToResetMDSM() const +{ + AssertOwnerThread(); + return false; +} + +/* + * Remove samples from the queue until aCompare() returns false. + * aCompare A function object with the signature bool(int64_t) which returns + * true for samples that should be removed. + */ +template <typename Function> static void +DiscardFrames(MediaQueue<MediaData>& aQueue, const Function& aCompare) +{ + while(aQueue.GetSize() > 0) { + if (aCompare(aQueue.PeekFront()->mTime)) { + RefPtr<MediaData> releaseMe = aQueue.PopFront(); + continue; + } + break; + } +} + +RefPtr<NextFrameSeekTask::SeekTaskPromise> +NextFrameSeekTask::Seek(const media::TimeUnit&) +{ + AssertOwnerThread(); + + auto currentTime = mCurrentTime; + DiscardFrames(mVideoQueue, [currentTime] (int64_t aSampleTime) { + return aSampleTime <= currentTime; + }); + + RefPtr<SeekTaskPromise> promise = mSeekTaskPromise.Ensure(__func__); + if (!IsVideoRequestPending() && NeedMoreVideo()) { + RequestVideoData(); + } + MaybeFinishSeek(); // Might resolve mSeekTaskPromise and modify audio queue. + return promise; +} + +void +NextFrameSeekTask::RequestVideoData() +{ + AssertOwnerThread(); + mReader->RequestVideoData(false, media::TimeUnit()); +} + +bool +NextFrameSeekTask::NeedMoreVideo() const +{ + AssertOwnerThread(); + // Need to request video when we have none and video queue is not finished. + return mVideoQueue.GetSize() == 0 && + !mSeekedVideoData && + !mVideoQueue.IsFinished() && + !mIsVideoQueueFinished; +} + +bool +NextFrameSeekTask::IsVideoRequestPending() const +{ + AssertOwnerThread(); + return mReader->IsRequestingVideoData() || mReader->IsWaitingVideoData(); +} + +bool +NextFrameSeekTask::IsAudioSeekComplete() const +{ + AssertOwnerThread(); + // Don't finish seek until there are no pending requests. Otherwise, we might + // lose audio samples for the promise is resolved asynchronously. + return !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData(); +} + +bool +NextFrameSeekTask::IsVideoSeekComplete() const +{ + AssertOwnerThread(); + // Don't finish seek until there are no pending requests. Otherwise, we might + // lose video samples for the promise is resolved asynchronously. + return !IsVideoRequestPending() && !NeedMoreVideo(); +} + +void +NextFrameSeekTask::MaybeFinishSeek() +{ + AssertOwnerThread(); + if (IsAudioSeekComplete() && IsVideoSeekComplete()) { + UpdateSeekTargetTime(); + + auto time = mTarget.GetTime().ToMicroseconds(); + DiscardFrames(mAudioQueue, [time] (int64_t aSampleTime) { + return aSampleTime < time; + }); + + Resolve(__func__); // Call to MDSM::SeekCompleted(); + } +} + +void +NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample) +{ + AssertOwnerThread(); + MOZ_ASSERT(aAudioSample); + MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); + + // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is + // resolved. + + SAMPLE_LOG("OnAudioDecoded [%lld,%lld]", + aAudioSample->mTime, + aAudioSample->GetEndTime()); + + // We accept any audio data here. + mSeekedAudioData = aAudioSample; + + MaybeFinishSeek(); +} + +void +NextFrameSeekTask::OnAudioNotDecoded(const MediaResult& aError) +{ + AssertOwnerThread(); + MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); + + SAMPLE_LOG("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(); +} + +void +NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample) +{ + AssertOwnerThread(); + MOZ_ASSERT(aVideoSample); + MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); + + // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is + // resolved. + + SAMPLE_LOG("OnVideoDecoded [%lld,%lld]", + aVideoSample->mTime, + aVideoSample->GetEndTime()); + + if (aVideoSample->mTime > mCurrentTime) { + mSeekedVideoData = aVideoSample; + } + + if (NeedMoreVideo()) { + RequestVideoData(); + return; + } + + MaybeFinishSeek(); +} + +void +NextFrameSeekTask::OnVideoNotDecoded(const MediaResult& aError) +{ + AssertOwnerThread(); + MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished"); + + SAMPLE_LOG("OnVideoNotDecoded (aError=%u)", aError.Code()); + + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { + mIsVideoQueueFinished = true; + } + + // Video seek not finished. + if (NeedMoreVideo()) { + switch (aError.Code()) { + case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA: + mReader->WaitForData(MediaData::VIDEO_DATA); + break; + 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: + // We might lose the audio sample after canceling the callbacks. + // However it doesn't really matter because MDSM is gonna shut down + // when seek fails. + CancelCallbacks(); + // Reject the promise since we can't finish video seek anyway. + RejectIfExist(aError, __func__); + break; + } + return; + } + + MaybeFinishSeek(); +} + +void +NextFrameSeekTask::SetCallbacks() +{ + AssertOwnerThread(); + + // Register dummy callbcak for audio decoding since we don't need to handle + // the decoded audio samples. + RefPtr<NextFrameSeekTask> self = this; + mAudioCallback = mReader->AudioCallback().Connect( + OwnerThread(), [self] (AudioCallbackData aData) { + if (aData.is<MediaData*>()) { + self->OnAudioDecoded(aData.as<MediaData*>()); + } else { + self->OnAudioNotDecoded(aData.as<MediaResult>()); + } + }); + + mVideoCallback = mReader->VideoCallback().Connect( + OwnerThread(), [self] (VideoCallbackData aData) { + typedef Tuple<MediaData*, TimeStamp> Type; + if (aData.is<Type>()) { + self->OnVideoDecoded(Get<0>(aData.as<Type>())); + } else { + self->OnVideoNotDecoded(aData.as<MediaResult>()); + } + }); + + mAudioWaitCallback = mReader->AudioWaitCallback().Connect( + OwnerThread(), [self] (WaitCallbackData aData) { + // 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. + self->MaybeFinishSeek(); + }); + + mVideoWaitCallback = mReader->VideoWaitCallback().Connect( + OwnerThread(), [self] (WaitCallbackData aData) { + if (self->NeedMoreVideo()) { + if (aData.is<MediaData::Type>()) { + self->RequestVideoData(); + } else { + // Reject if we can't finish video seeking. + self->CancelCallbacks(); + self->RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + } + return; + } + self->MaybeFinishSeek(); + }); +} + +void +NextFrameSeekTask::CancelCallbacks() +{ + AssertOwnerThread(); + mAudioCallback.DisconnectIfExists(); + mVideoCallback.DisconnectIfExists(); + mAudioWaitCallback.DisconnectIfExists(); + mVideoWaitCallback.DisconnectIfExists(); +} + +void +NextFrameSeekTask::UpdateSeekTargetTime() +{ + AssertOwnerThread(); + + RefPtr<MediaData> data = mVideoQueue.PeekFront(); + if (data) { + mTarget.SetTime(TimeUnit::FromMicroseconds(data->mTime)); + } else if (mSeekedVideoData) { + mTarget.SetTime(TimeUnit::FromMicroseconds(mSeekedVideoData->mTime)); + } else if (mIsVideoQueueFinished || mVideoQueue.AtEndOfStream()) { + mTarget.SetTime(mDuration); + } else { + MOZ_ASSERT(false, "No data!"); + } +} +} // namespace media +} // namespace mozilla |