diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 3d5cdc1e520..757705b3b00 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -1215,10 +1215,9 @@ void MediaDecoder::UpdateReadyStateForData() mOwner->UpdateReadyStateForData(frameStatus); } -void MediaDecoder::OnSeekResolved(SeekResolveValue aVal) +void MediaDecoder::OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility) { MOZ_ASSERT(NS_IsMainThread()); - mSeekRequest.Complete(); if (mShuttingDown) return; @@ -1235,20 +1234,20 @@ void MediaDecoder::OnSeekResolved(SeekResolveValue aVal) seekWasAborted = true; } else { UnpinForSeek(); - fireEnded = aVal.mAtEnd; - if (aVal.mAtEnd) { + fireEnded = aAtEnd; + if (aAtEnd) { ChangeState(PLAY_STATE_ENDED); - } else if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) { - ChangeState(aVal.mAtEnd ? PLAY_STATE_ENDED : mNextState); + } else if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) { + ChangeState(aAtEnd ? PLAY_STATE_ENDED : mNextState); } } } - PlaybackPositionChanged(aVal.mEventVisibility); + PlaybackPositionChanged(aEventVisibility); if (mOwner) { UpdateReadyStateForData(); - if (!seekWasAborted && (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed)) { + if (!seekWasAborted && (aEventVisibility != MediaDecoderEventVisibility::Suppressed)) { mOwner->SeekCompleted(); if (fireEnded) { mOwner->PlaybackEnded(); diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 5dddc43a3c6..cff5b625bd7 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -806,7 +806,21 @@ public: void PlaybackEnded(); void OnSeekRejected() { mSeekRequest.Complete(); } - void OnSeekResolved(SeekResolveValue aVal); + void OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility); + + void OnSeekResolved(SeekResolveValue aVal) + { + mSeekRequest.Complete(); + OnSeekResolvedInternal(aVal.mAtEnd, aVal.mEventVisibility); + } + +#ifdef MOZ_AUDIO_OFFLOAD + // Temporary hack - see bug 1139206. + void SimulateSeekResolvedForAudioOffload(MediaDecoderEventVisibility aEventVisibility) + { + OnSeekResolvedInternal(false, aEventVisibility); + } +#endif // Seeking has started. Inform the element on the main // thread. diff --git a/dom/media/omx/AudioOffloadPlayer.cpp b/dom/media/omx/AudioOffloadPlayer.cpp index 5a7705c080a..3f7952b94db 100644 --- a/dom/media/omx/AudioOffloadPlayer.cpp +++ b/dom/media/omx/AudioOffloadPlayer.cpp @@ -57,10 +57,13 @@ static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll; AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxCommonDecoder* aObserver) : mStarted(false), mPlaying(false), + mSeeking(false), mReachedEOS(false), + mSeekDuringPause(false), mIsElementVisible(true), mSampleRate(0), mStartPosUs(0), + mSeekTimeUs(0), mPositionTimeMediaUs(-1), mInputBuffer(nullptr), mObserver(aObserver) @@ -196,6 +199,13 @@ status_t AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState) StartTimeUpdate(); } break; + case MediaDecoder::PLAY_STATE_SEEKING: { + int64_t seekTimeUs + = mObserver->GetSeekTime(); + SeekTo(seekTimeUs, true); + mObserver->ResetSeekTime(); + } break; + case MediaDecoder::PLAY_STATE_PAUSED: case MediaDecoder::PLAY_STATE_SHUTDOWN: // Just pause here during play state shutdown as well to stop playing @@ -268,12 +278,8 @@ status_t AudioOffloadPlayer::Play() return err; } // Seek to last play position only when there was no seek during last pause - android::Mutex::Autolock autoLock(mLock); - if (!mSeekTarget.IsValid()) { - mSeekTarget = SeekTarget(mPositionTimeMediaUs, - SeekTarget::Accurate, - MediaDecoderEventVisibility::Suppressed); - DoSeek(); + if (!mSeeking) { + SeekTo(mPositionTimeMediaUs); } } @@ -337,36 +343,28 @@ void AudioOffloadPlayer::Reset() WakeLockRelease(); } -nsRefPtr AudioOffloadPlayer::Seek(SeekTarget aTarget) +status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents) { MOZ_ASSERT(NS_IsMainThread()); - android::Mutex::Autolock autoLock(mLock); - - mSeekPromise.RejectIfExists(true, __func__); - mSeekTarget = aTarget; - nsRefPtr p = mSeekPromise.Ensure(__func__); - DoSeek(); - return p; -} - -status_t AudioOffloadPlayer::DoSeek() -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(mSeekTarget.IsValid()); CHECK(mAudioSink.get()); - AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("DoSeek ( %lld )", mSeekTarget.mTime)); + android::Mutex::Autolock autoLock(mLock); + AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SeekTo ( %lld )", aTimeUs)); + + mSeeking = true; mReachedEOS = false; mPositionTimeMediaUs = -1; - mStartPosUs = mSeekTarget.mTime; + mSeekTimeUs = aTimeUs; + mStartPosUs = aTimeUs; + mDispatchSeekEvents = aDispatchSeekEvents; - if (!mSeekPromise.IsEmpty()) { + if (mDispatchSeekEvents) { nsCOMPtr nsEvent = NS_NewRunnableMethodWithArg( mObserver, &MediaDecoder::SeekingStarted, - mSeekTarget.mEventVisibility); + MediaDecoderEventVisibility::Observable); NS_DispatchToCurrentThread(nsEvent); } @@ -376,15 +374,21 @@ status_t AudioOffloadPlayer::DoSeek() mAudioSink->Start(); } else { + mSeekDuringPause = true; + if (mStarted) { mAudioSink->Flush(); } - if (!mSeekPromise.IsEmpty()) { + if (mDispatchSeekEvents) { + mDispatchSeekEvents = false; AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause")); - // We do not reset mSeekTarget here. - MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility); - mSeekPromise.Resolve(val, __func__); + nsCOMPtr nsEvent = + NS_NewRunnableMethodWithArg( + mObserver, + &MediaDecoder::SimulateSeekResolvedForAudioOffload, + MediaDecoderEventVisibility::Observable); + NS_DispatchToCurrentThread(nsEvent); } } @@ -403,8 +407,8 @@ int64_t AudioOffloadPlayer::GetMediaTimeUs() android::Mutex::Autolock autoLock(mLock); int64_t playPosition = 0; - if (mSeekTarget.IsValid()) { - return mSeekTarget.mTime; + if (mSeeking) { + return mSeekTimeUs; } if (!mStarted) { return mPositionTimeMediaUs; @@ -435,12 +439,6 @@ int64_t AudioOffloadPlayer::GetOutputPlayPositionUs_l() const void AudioOffloadPlayer::NotifyAudioEOS() { - android::Mutex::Autolock autoLock(mLock); - // We do not reset mSeekTarget here. - if (!mSeekPromise.IsEmpty()) { - MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility); - mSeekPromise.Resolve(val, __func__); - } nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, &MediaDecoder::PlaybackEnded); NS_DispatchToMainThread(nsEvent); @@ -458,15 +456,6 @@ void AudioOffloadPlayer::NotifyPositionChanged() void AudioOffloadPlayer::NotifyAudioTearDown() { - // Fallback to state machine. - // state machine's seeks will be done with - // MediaDecoderEventVisibility::Suppressed. - android::Mutex::Autolock autoLock(mLock); - // We do not reset mSeekTarget here. - if (!mSeekPromise.IsEmpty()) { - MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility); - mSeekPromise.Resolve(val, __func__); - } nsCOMPtr nsEvent = NS_NewRunnableMethod(mObserver, &MediaOmxCommonDecoder::AudioOffloadTearDown); NS_DispatchToMainThread(nsEvent); @@ -517,24 +506,27 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize) size_t sizeDone = 0; size_t sizeRemaining = aSize; - int64_t seekTimeUs = -1; while (sizeRemaining > 0) { MediaSource::ReadOptions options; + bool refreshSeekTime = false; + { android::Mutex::Autolock autoLock(mLock); - if (mSeekTarget.IsValid()) { - seekTimeUs = mSeekTarget.mTime; - options.setSeekTo(seekTimeUs); + if (mSeeking) { + options.setSeekTo(mSeekTimeUs); + refreshSeekTime = true; if (mInputBuffer) { mInputBuffer->release(); mInputBuffer = nullptr; } + mSeeking = false; } } if (!mInputBuffer) { + status_t err; err = mSource->read(&mInputBuffer, &options); @@ -543,9 +535,6 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize) android::Mutex::Autolock autoLock(mLock); if (err != OK) { - if (mSeekTarget.IsValid()) { - mSeekTarget.Reset(); - } AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Error while reading media source %d " "Ok to receive EOS error at end", err)); if (!mReachedEOS) { @@ -575,24 +564,41 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize) kKeyTime, &mPositionTimeMediaUs)); } - if (mSeekTarget.IsValid() && seekTimeUs == mSeekTarget.mTime) { - mSeekTarget.Reset(); - if (!mSeekPromise.IsEmpty()) { + if (refreshSeekTime) { + if (mDispatchSeekEvents && !mSeekDuringPause) { + mDispatchSeekEvents = false; AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE")); - MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility); - mSeekPromise.Resolve(val, __func__); + nsCOMPtr nsEvent = + NS_NewRunnableMethodWithArg( + mObserver, + &MediaDecoder::SimulateSeekResolvedForAudioOffload, + MediaDecoderEventVisibility::Observable); + NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); + + } else if (mSeekDuringPause) { + // Callback is already called for seek during pause. Just reset the + // flag + AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its" + " already faked")); + mSeekDuringPause = false; + } + + NotifyPositionChanged(); + + // need to adjust the mStartPosUs for offload decoding since parser + // might not be able to get the exact seek time requested. + mStartPosUs = mPositionTimeMediaUs; + AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f", + mStartPosUs / 1E6)); + + // clear seek time with mLock locked and once we have valid + // mPositionTimeMediaUs + // before clearing mSeekTimeUs check if a new seek request has been + // received while we were reading from the source with mLock released. + if (!mSeeking) { + mSeekTimeUs = 0; } - } else if (mSeekTarget.IsValid()) { - AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("seek is updated during unlocking mLock")); } - - NotifyPositionChanged(); - - // need to adjust the mStartPosUs for offload decoding since parser - // might not be able to get the exact seek time requested. - mStartPosUs = mPositionTimeMediaUs; - AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f", - mStartPosUs / 1E6)); } if (mInputBuffer->range_length() == 0) { diff --git a/dom/media/omx/AudioOffloadPlayer.h b/dom/media/omx/AudioOffloadPlayer.h index 6bfb1e63e95..aef8961c2ea 100644 --- a/dom/media/omx/AudioOffloadPlayer.h +++ b/dom/media/omx/AudioOffloadPlayer.h @@ -78,27 +78,25 @@ public: ~AudioOffloadPlayer(); // Caller retains ownership of "aSource". - virtual void SetSource(const android::sp &aSource) override; + void SetSource(const android::sp &aSource); // Start the source if it's not already started and open the AudioSink to // create an offloaded audio track - virtual status_t Start(bool aSourceAlreadyStarted = false) override; + status_t Start(bool aSourceAlreadyStarted = false); - virtual status_t ChangeState(MediaDecoder::PlayState aState) override; - - virtual void SetVolume(double aVolume) override; - - virtual double GetMediaTimeSecs() override; + double GetMediaTimeSecs(); // To update progress bar when the element is visible - virtual void SetElementVisibility(bool aIsVisible) override;; + void SetElementVisibility(bool aIsVisible); + + status_t ChangeState(MediaDecoder::PlayState aState); + + void SetVolume(double aVolume); // Update ready state based on current play state. Not checking data // availability since offloading is currently done only when whole compressed // data is available - virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus() override; - - virtual nsRefPtr Seek(SeekTarget aTarget) override; + MediaDecoderOwner::NextFrameStatus GetNextFrameStatus(); void TimeUpdate(); @@ -114,12 +112,28 @@ private: // Used only in main thread bool mPlaying; + // Set when playstate is seeking and reset when FillBUffer() acknowledged + // seeking by seeking audio source. Used in main thread and offload + // callback thread, protected by Mutex mLock + bool mSeeking; + // Once playback reached end of stream (last ~100ms), position provided by DSP // may be reset/corrupted. This bool is used to avoid that. // Used in main thread and offload callback thread, protected by Mutex // mLock bool mReachedEOS; + // Set when there is a seek request during pause. + // Used in main thread and offload callback thread, protected by Mutex + // mLock + bool mSeekDuringPause; + + // Seek can be triggered internally or by MediaDecoder. This bool is to + // to track seek triggered by MediaDecoder so that we can send back + // SeekingStarted and SeekingStopped events. + // Used in main thread and offload callback thread, protected by Mutex mLock + bool mDispatchSeekEvents; + // Set when the HTML Audio Element is visible to the user. // Used only in main thread bool mIsElementVisible; @@ -141,15 +155,10 @@ private: // mLock int64_t mStartPosUs; - // The target of current seek when there is a request to seek + // Given seek time when there is a request to seek // Used in main thread and offload callback thread, protected by Mutex // mLock - SeekTarget mSeekTarget; - - // MediaPromise of current seek. - // Used in main thread and offload callback thread, protected by Mutex - // mLock - MediaPromiseHolder mSeekPromise; + int64_t mSeekTimeUs; // Positions obtained from offlaoded tracks (DSP) // Used in main thread and offload callback thread, protected by Mutex @@ -212,15 +221,15 @@ private: bool IsSeeking(); - // Set mSeekTarget to the given position and restart the sink. Actual seek - // happens in FillBuffer(). If mSeekPromise is not empty, send + // Set mSeekTime to the given position and restart the sink. Actual seek + // happens in FillBuffer(). If aDispatchSeekEvents is true, send // SeekingStarted event always and SeekingStopped event when the play state is // paused to MediaDecoder. // When decoding and playing happens separately, if there is a seek during // pause, we can decode and keep data ready. // In case of offload player, no way to seek during pause. So just fake that // seek is done. - status_t DoSeek(); + status_t SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents = false); // Start/Resume the audio sink so that callback will start being called to get // compressed data diff --git a/dom/media/omx/AudioOffloadPlayerBase.h b/dom/media/omx/AudioOffloadPlayerBase.h index 73c6310466a..b332f8b0fce 100644 --- a/dom/media/omx/AudioOffloadPlayerBase.h +++ b/dom/media/omx/AudioOffloadPlayerBase.h @@ -66,8 +66,6 @@ public: { return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; } - - virtual nsRefPtr Seek(SeekTarget aTarget) = 0; }; } // namespace mozilla diff --git a/dom/media/omx/MediaOmxCommonDecoder.cpp b/dom/media/omx/MediaOmxCommonDecoder.cpp index 07df9c471b1..fb37d1c53b6 100644 --- a/dom/media/omx/MediaOmxCommonDecoder.cpp +++ b/dom/media/omx/MediaOmxCommonDecoder.cpp @@ -126,9 +126,8 @@ MediaOmxCommonDecoder::ResumeStateMachine() mAudioOffloadPlayer = nullptr; int64_t timeUsecs = 0; SecondsToUsecs(mCurrentTime, timeUsecs); - mRequestedSeekTarget = SeekTarget(timeUsecs, - SeekTarget::Accurate, - MediaDecoderEventVisibility::Suppressed); + mRequestedSeekTarget = SeekTarget(timeUsecs, SeekTarget::Accurate); + mNextState = mPlayState; ChangeState(PLAY_STATE_LOADING); // exit dormant state @@ -194,25 +193,10 @@ MediaOmxCommonDecoder::ChangeState(PlayState aState) // in between MediaDecoder::ChangeState(aState); - if (!mAudioOffloadPlayer) { - return; - } - - status_t err = mAudioOffloadPlayer->ChangeState(aState); - if (err != OK) { - ResumeStateMachine(); - return; - } - - switch (mPlayState) { - case PLAY_STATE_SEEKING: - mSeekRequest.Begin(mAudioOffloadPlayer->Seek(mRequestedSeekTarget) - ->RefableThen(NS_GetCurrentThread(), __func__, static_cast(this), - &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected)); - mRequestedSeekTarget.Reset(); - break; - default: { - break; + if (mAudioOffloadPlayer) { + status_t err = mAudioOffloadPlayer->ChangeState(aState); + if (err != OK) { + ResumeStateMachine(); } } }