/* -*- 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 "nsBuiltinDecoder.h" #include #include "nsNetUtil.h" #include "nsAudioStream.h" #include "nsHTMLVideoElement.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsTArray.h" #include "VideoUtils.h" #include "nsBuiltinDecoderStateMachine.h" #include "nsTimeRanges.h" #include "nsContentUtils.h" #include "ImageContainer.h" using namespace mozilla; #ifdef PR_LOGGING PRLogModuleInfo* gBuiltinDecoderLog; #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg) #else #define LOG(type, msg) #endif NS_IMPL_THREADSAFE_ISUPPORTS1(nsBuiltinDecoder, nsIObserver) void nsBuiltinDecoder::Pause() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mPlayState == PLAY_STATE_SEEKING || mPlayState == PLAY_STATE_ENDED) { mNextState = PLAY_STATE_PAUSED; return; } ChangeState(PLAY_STATE_PAUSED); } void nsBuiltinDecoder::SetVolume(double aVolume) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); mInitialVolume = aVolume; if (mDecoderStateMachine) { mDecoderStateMachine->SetVolume(aVolume); } } void nsBuiltinDecoder::SetAudioCaptured(bool aCaptured) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); mInitialAudioCaptured = aCaptured; if (mDecoderStateMachine) { mDecoderStateMachine->SetAudioCaptured(aCaptured); } } void nsBuiltinDecoder::ConnectDecodedStreamToOutputStream(OutputStreamData* aStream) { NS_ASSERTION(!aStream->mPort, "Already connected?"); // The output stream must stay in sync with the decoded stream, so if // either stream is blocked, we block the other. aStream->mPort = aStream->mStream->AllocateInputPort(mDecodedStream->mStream, MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT); // Unblock the output stream now. While it's connected to mDecodedStream, // mDecodedStream is responsible for controlling blocking. aStream->mStream->ChangeExplicitBlockerCount(-1); } nsBuiltinDecoder::DecodedStreamData::DecodedStreamData(nsBuiltinDecoder* aDecoder, int64_t aInitialTime, SourceMediaStream* aStream) : mLastAudioPacketTime(-1), mLastAudioPacketEndTime(-1), mAudioFramesWritten(0), mInitialTime(aInitialTime), mNextVideoTime(aInitialTime), mStreamInitialized(false), mHaveSentFinish(false), mHaveSentFinishAudio(false), mHaveSentFinishVideo(false), mStream(aStream), mMainThreadListener(new DecodedStreamMainThreadListener(aDecoder)), mHaveBlockedForPlayState(false) { mStream->AddMainThreadListener(mMainThreadListener); } nsBuiltinDecoder::DecodedStreamData::~DecodedStreamData() { mStream->RemoveMainThreadListener(mMainThreadListener); mStream->Destroy(); } void nsBuiltinDecoder::DestroyDecodedStream() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); GetReentrantMonitor().AssertCurrentThreadIn(); // All streams are having their SourceMediaStream disconnected, so they // need to be explicitly blocked again. for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) { OutputStreamData& os = mOutputStreams[i]; // During cycle collection, nsDOMMediaStream can be destroyed and send // its Destroy message before this decoder is destroyed. So we have to // be careful not to send any messages after the Destroy(). if (!os.mStream->IsDestroyed()) { os.mStream->ChangeExplicitBlockerCount(1); } // Explicitly remove all existing ports. This is not strictly necessary but it's // good form. os.mPort->Destroy(); os.mPort = nullptr; } mDecodedStream = nullptr; } void nsBuiltinDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); GetReentrantMonitor().AssertCurrentThreadIn(); LOG(PR_LOG_DEBUG, ("nsBuiltinDecoder::RecreateDecodedStream this=%p aStartTimeUSecs=%lld!", this, (long long)aStartTimeUSecs)); DestroyDecodedStream(); mDecodedStream = new DecodedStreamData(this, aStartTimeUSecs, MediaStreamGraph::GetInstance()->CreateSourceStream(nullptr)); // Note that the delay between removing ports in DestroyDecodedStream // and adding new ones won't cause a glitch since all graph operations // between main-thread stable states take effect atomically. for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) { ConnectDecodedStreamToOutputStream(&mOutputStreams[i]); } mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING; if (mDecodedStream->mHaveBlockedForPlayState) { mDecodedStream->mStream->ChangeExplicitBlockerCount(1); } } void nsBuiltinDecoder::NotifyDecodedStreamMainThreadStateChanged() { if (mTriggerPlaybackEndedWhenSourceStreamFinishes && mDecodedStream && mDecodedStream->mStream->IsFinished()) { mTriggerPlaybackEndedWhenSourceStreamFinishes = false; if (GetState() == PLAY_STATE_PLAYING) { nsCOMPtr event = NS_NewRunnableMethod(this, &nsBuiltinDecoder::PlaybackEnded); NS_DispatchToCurrentThread(event); } } } void nsBuiltinDecoder::AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); LOG(PR_LOG_DEBUG, ("nsBuiltinDecoder::AddOutputStream this=%p aStream=%p!", this, aStream)); { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (!mDecodedStream) { RecreateDecodedStream(mDecoderStateMachine ? int64_t(mDecoderStateMachine->GetCurrentTime()*USECS_PER_S) : 0); } OutputStreamData* os = mOutputStreams.AppendElement(); os->Init(aStream, aFinishWhenEnded); ConnectDecodedStreamToOutputStream(os); if (aFinishWhenEnded) { // Ensure that aStream finishes the moment mDecodedStream does. aStream->SetAutofinish(true); } } // This can be called before Load(), in which case our mDecoderStateMachine // won't have been created yet and we can rely on Load() to schedule it // once it is created. if (mDecoderStateMachine) { // Make sure the state machine thread runs so that any buffered data // is fed into our stream. ScheduleStateMachineThread(); } } double nsBuiltinDecoder::GetDuration() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mInfiniteStream) { return std::numeric_limits::infinity(); } if (mDuration >= 0) { return static_cast(mDuration) / static_cast(USECS_PER_S); } return std::numeric_limits::quiet_NaN(); } void nsBuiltinDecoder::SetInfinite(bool aInfinite) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); mInfiniteStream = aInfinite; } bool nsBuiltinDecoder::IsInfinite() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return mInfiniteStream; } nsBuiltinDecoder::nsBuiltinDecoder() : mDecoderPosition(0), mPlaybackPosition(0), mCurrentTime(0.0), mInitialVolume(0.0), mRequestedSeekTime(-1.0), mDuration(-1), mSeekable(true), mReentrantMonitor("media.decoder"), mPlayState(PLAY_STATE_PAUSED), mNextState(PLAY_STATE_PAUSED), mResourceLoaded(false), mIgnoreProgressData(false), mInfiniteStream(false), mTriggerPlaybackEndedWhenSourceStreamFinishes(false) { MOZ_COUNT_CTOR(nsBuiltinDecoder); NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); #ifdef PR_LOGGING if (!gBuiltinDecoderLog) { gBuiltinDecoderLog = PR_NewLogModule("nsBuiltinDecoder"); } #endif } bool nsBuiltinDecoder::Init(MediaDecoderOwner* aOwner) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (!nsMediaDecoder::Init(aOwner)) return false; nsContentUtils::RegisterShutdownObserver(this); return true; } void nsBuiltinDecoder::Shutdown() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; mShuttingDown = true; { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); DestroyDecodedStream(); } // This changes the decoder state to SHUTDOWN and does other things // necessary to unblock the state machine thread if it's blocked, so // the asynchronous shutdown in nsDestroyStateMachine won't deadlock. if (mDecoderStateMachine) { mDecoderStateMachine->Shutdown(); } // Force any outstanding seek and byterange requests to complete // to prevent shutdown from deadlocking. if (mResource) { mResource->Close(); } ChangeState(PLAY_STATE_SHUTDOWN); nsMediaDecoder::Shutdown(); nsContentUtils::UnregisterShutdownObserver(this); } nsBuiltinDecoder::~nsBuiltinDecoder() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); UnpinForSeek(); MOZ_COUNT_DTOR(nsBuiltinDecoder); } nsresult nsBuiltinDecoder::OpenResource(MediaResource* aResource, nsIStreamListener** aStreamListener) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (aStreamListener) { *aStreamListener = nullptr; } { // Hold the lock while we do this to set proper lock ordering // expectations for dynamic deadlock detectors: decoder lock(s) // should be grabbed before the cache lock ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); nsresult rv = aResource->Open(aStreamListener); if (NS_FAILED(rv)) { LOG(PR_LOG_DEBUG, ("%p Failed to open stream!", this)); delete aResource; return rv; } mResource = aResource; } return NS_OK; } nsresult nsBuiltinDecoder::Load(MediaResource* aResource, nsIStreamListener** aStreamListener, nsMediaDecoder* aCloneDonor) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); nsresult rv = OpenResource(aResource, aStreamListener); NS_ENSURE_SUCCESS(rv, rv); mDecoderStateMachine = CreateStateMachine(); if (!mDecoderStateMachine) { LOG(PR_LOG_DEBUG, ("%p Failed to create state machine!", this)); return NS_ERROR_FAILURE; } return InitializeStateMachine(aCloneDonor); } nsresult nsBuiltinDecoder::InitializeStateMachine(nsMediaDecoder* aCloneDonor) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); nsBuiltinDecoder* cloneDonor = static_cast(aCloneDonor); if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ? cloneDonor->mDecoderStateMachine : nullptr))) { LOG(PR_LOG_DEBUG, ("%p Failed to init state machine!", this)); return NS_ERROR_FAILURE; } { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mDecoderStateMachine->SetSeekable(mSeekable); mDecoderStateMachine->SetDuration(mDuration); mDecoderStateMachine->SetVolume(mInitialVolume); mDecoderStateMachine->SetAudioCaptured(mInitialAudioCaptured); if (mFrameBufferLength > 0) { // The valid mFrameBufferLength value was specified earlier mDecoderStateMachine->SetFrameBufferLength(mFrameBufferLength); } } ChangeState(PLAY_STATE_LOADING); return ScheduleStateMachineThread(); } nsresult nsBuiltinDecoder::RequestFrameBufferLength(uint32_t aLength) { nsresult res = nsMediaDecoder::RequestFrameBufferLength(aLength); NS_ENSURE_SUCCESS(res,res); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mDecoderStateMachine) { mDecoderStateMachine->SetFrameBufferLength(aLength); } return res; } nsresult nsBuiltinDecoder::ScheduleStateMachineThread() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); NS_ASSERTION(mDecoderStateMachine, "Must have state machine to start state machine thread"); NS_ENSURE_STATE(mDecoderStateMachine); if (mShuttingDown) return NS_OK; ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); nsBuiltinDecoderStateMachine* m = static_cast(mDecoderStateMachine.get()); return m->ScheduleStateMachine(); } nsresult nsBuiltinDecoder::Play() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine."); nsresult res = ScheduleStateMachineThread(); NS_ENSURE_SUCCESS(res,res); if (mPlayState == PLAY_STATE_SEEKING) { mNextState = PLAY_STATE_PLAYING; return NS_OK; } if (mPlayState == PLAY_STATE_ENDED) return Seek(0); ChangeState(PLAY_STATE_PLAYING); return NS_OK; } /** * Returns true if aValue is inside a range of aRanges, and put the range * index in aIntervalIndex if it is not null. * If aValue is not inside a range, false is returned, and aIntervalIndex, if * not null, is set to the index of the range which ends immediately before aValue * (and can be -1 if aValue is before aRanges.Start(0)). */ static bool IsInRanges(nsTimeRanges& aRanges, double aValue, int32_t& aIntervalIndex) { uint32_t length; aRanges.GetLength(&length); for (uint32_t i = 0; i < length; i++) { double start, end; aRanges.Start(i, &start); if (start > aValue) { aIntervalIndex = i - 1; return false; } aRanges.End(i, &end); if (aValue <= end) { aIntervalIndex = i; return true; } } aIntervalIndex = length - 1; return false; } nsresult nsBuiltinDecoder::Seek(double aTime) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); NS_ABORT_IF_FALSE(aTime >= 0.0, "Cannot seek to a negative value."); nsTimeRanges seekable; nsresult res; uint32_t length = 0; res = GetSeekable(&seekable); NS_ENSURE_SUCCESS(res, NS_OK); seekable.GetLength(&length); if (!length) { return NS_OK; } // If the position we want to seek to is not in a seekable range, we seek // to the closest position in the seekable ranges instead. If two positions // are equally close, we seek to the closest position from the currentTime. // See seeking spec, point 7 : // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking int32_t range = 0; if (!IsInRanges(seekable, aTime, range)) { if (range != -1) { // |range + 1| can't be negative, because the only possible negative value // for |range| is -1. if (uint32_t(range + 1) < length) { double leftBound, rightBound; res = seekable.End(range, &leftBound); NS_ENSURE_SUCCESS(res, NS_OK); res = seekable.Start(range + 1, &rightBound); NS_ENSURE_SUCCESS(res, NS_OK); double distanceLeft = NS_ABS(leftBound - aTime); double distanceRight = NS_ABS(rightBound - aTime); if (distanceLeft == distanceRight) { distanceLeft = NS_ABS(leftBound - mCurrentTime); distanceRight = NS_ABS(rightBound - mCurrentTime); } aTime = (distanceLeft < distanceRight) ? leftBound : rightBound; } else { // Seek target is after the end last range in seekable data. // Clamp the seek target to the end of the last seekable range. res = seekable.End(length - 1, &aTime); NS_ENSURE_SUCCESS(res, NS_OK); } } else { // aTime is before the first range in |seekable|, the closest point we can // seek to is the start of the first range. seekable.Start(0, &aTime); } } mRequestedSeekTime = aTime; mCurrentTime = aTime; // If we are already in the seeking state, then setting mRequestedSeekTime // above will result in the new seek occurring when the current seek // completes. if (mPlayState != PLAY_STATE_SEEKING) { bool paused = false; if (mOwner) { paused = mOwner->GetPaused(); } mNextState = paused ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING; PinForSeek(); ChangeState(PLAY_STATE_SEEKING); } return ScheduleStateMachineThread(); } nsresult nsBuiltinDecoder::PlaybackRateChanged() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return NS_ERROR_NOT_IMPLEMENTED; } double nsBuiltinDecoder::GetCurrentTime() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return mCurrentTime; } already_AddRefed nsBuiltinDecoder::GetCurrentPrincipal() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return mResource ? mResource->GetCurrentPrincipal() : nullptr; } void nsBuiltinDecoder::AudioAvailable(float* aFrameBuffer, uint32_t aFrameBufferLength, float aTime) { // Auto manage the frame buffer's memory. If we return due to an error // here, this ensures we free the memory. Otherwise, we pass off ownership // to HTMLMediaElement::NotifyAudioAvailable(). nsAutoArrayPtr frameBuffer(aFrameBuffer); NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown || !mOwner) { return; } mOwner->NotifyAudioAvailable(frameBuffer.forget(), aFrameBufferLength, aTime); } void nsBuiltinDecoder::MetadataLoaded(uint32_t aChannels, uint32_t aRate, bool aHasAudio, const MetadataTags* aTags) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) { return; } { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1; // Duration has changed so we should recompute playback rate UpdatePlaybackRate(); } if (mDuration == -1) { SetInfinite(true); } if (mOwner) { // Make sure the element and the frame (if any) are told about // our new size. Invalidate(); mOwner->MetadataLoaded(aChannels, aRate, aHasAudio, aTags); } if (!mResourceLoaded) { StartProgress(); } else if (mOwner) { // Resource was loaded during metadata loading, when progress // events are being ignored. Fire the final progress event. mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("progress")); } // Only inform the element of FirstFrameLoaded if not doing a load() in order // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events. ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); bool resourceIsLoaded = !mResourceLoaded && mResource && mResource->IsDataCachedToEndOfResource(mDecoderPosition); if (mOwner) { mOwner->FirstFrameLoaded(resourceIsLoaded); } // This can run cache callbacks. mResource->EnsureCacheUpToDate(); // The element can run javascript via events // before reaching here, so only change the // state if we're still set to the original // loading state. if (mPlayState == PLAY_STATE_LOADING) { if (mRequestedSeekTime >= 0.0) { ChangeState(PLAY_STATE_SEEKING); } else { ChangeState(mNextState); } } if (resourceIsLoaded) { ResourceLoaded(); } // Run NotifySuspendedStatusChanged now to give us a chance to notice // that autoplay should run. NotifySuspendedStatusChanged(); } void nsBuiltinDecoder::ResourceLoaded() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); // Don't handle ResourceLoaded if we are shutting down, or if // we need to ignore progress data due to seeking (in the case // that the seek results in reaching end of file, we get a bogus call // to ResourceLoaded). if (mShuttingDown) return; { // If we are seeking or loading then the resource loaded notification we get // should be ignored, since it represents the end of the seek request. ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mIgnoreProgressData || mResourceLoaded || mPlayState == PLAY_STATE_LOADING) return; Progress(false); mResourceLoaded = true; StopProgress(); } // Ensure the final progress event gets fired if (mOwner) { mOwner->ResourceLoaded(); } } void nsBuiltinDecoder::NetworkError() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; if (mOwner) mOwner->NetworkError(); Shutdown(); } void nsBuiltinDecoder::DecodeError() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; if (mOwner) mOwner->DecodeError(); Shutdown(); } bool nsBuiltinDecoder::IsSeeking() const { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return mPlayState == PLAY_STATE_SEEKING; } bool nsBuiltinDecoder::IsEnded() const { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN; } void nsBuiltinDecoder::PlaybackEnded() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown || mPlayState == nsBuiltinDecoder::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.mFinishWhenEnded) { // Shouldn't really be needed since mDecodedStream should already have // finished, but doesn't hurt. os.mStream->Finish(); os.mPort->Destroy(); os.mPort = nullptr; // Not really needed but it keeps the invariant that a stream not // connected to mDecodedStream is explicity blocked. os.mStream->ChangeExplicitBlockerCount(1); mOutputStreams.RemoveElementAt(i); } } } PlaybackPositionChanged(); ChangeState(PLAY_STATE_ENDED); if (mOwner) { UpdateReadyStateForData(); mOwner->PlaybackEnded(); } // This must be called after |mOwner->PlaybackEnded()| call above, in order // to fire the required durationchange. if (IsInfinite()) { SetInfinite(false); } } NS_IMETHODIMP nsBuiltinDecoder::Observe(nsISupports *aSubjet, const char *aTopic, const PRUnichar *someData) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { Shutdown(); } return NS_OK; } nsMediaDecoder::Statistics nsBuiltinDecoder::GetStatistics() { NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(), "Should be on main or state machine thread."); Statistics result; ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mResource) { result.mDownloadRate = mResource->GetDownloadRate(&result.mDownloadRateReliable); result.mDownloadPosition = mResource->GetCachedDataEnd(mDecoderPosition); result.mTotalBytes = mResource->GetLength(); result.mPlaybackRate = ComputePlaybackRate(&result.mPlaybackRateReliable); result.mDecoderPosition = mDecoderPosition; result.mPlaybackPosition = mPlaybackPosition; } else { result.mDownloadRate = 0; result.mDownloadRateReliable = true; result.mPlaybackRate = 0; result.mPlaybackRateReliable = true; result.mDecoderPosition = 0; result.mPlaybackPosition = 0; result.mDownloadPosition = 0; result.mTotalBytes = 0; } return result; } double nsBuiltinDecoder::ComputePlaybackRate(bool* aReliable) { GetReentrantMonitor().AssertCurrentThreadIn(); NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(), "Should be on main or state machine thread."); int64_t length = mResource ? mResource->GetLength() : -1; if (mDuration >= 0 && length >= 0) { *aReliable = true; return length * static_cast(USECS_PER_S) / mDuration; } return mPlaybackStatistics.GetRateAtLastStop(aReliable); } void nsBuiltinDecoder::UpdatePlaybackRate() { NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(), "Should be on main or state machine thread."); GetReentrantMonitor().AssertCurrentThreadIn(); if (!mResource) return; bool reliable; uint32_t rate = uint32_t(ComputePlaybackRate(&reliable)); if (reliable) { // Avoid passing a zero rate rate = NS_MAX(rate, 1u); } else { // Set a minimum rate of 10,000 bytes per second ... sometimes we just // don't have good data rate = NS_MAX(rate, 10000u); } mResource->SetPlaybackRate(rate); } void nsBuiltinDecoder::NotifySuspendedStatusChanged() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (!mResource) return; MediaResource* activeStream; bool suspended = mResource->IsSuspendedByCache(&activeStream); if (mOwner) { if (suspended) { // If this is an autoplay element, we need to kick off its autoplaying // now so we consume data and hopefully free up cache space. mOwner->NotifyAutoplayDataReady(); } mOwner->NotifySuspendedByCache(suspended); UpdateReadyStateForData(); } } void nsBuiltinDecoder::NotifyBytesDownloaded() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); UpdateReadyStateForData(); Progress(false); } void nsBuiltinDecoder::NotifyDownloadEnded(nsresult aStatus) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (aStatus == NS_BINDING_ABORTED) { // Download has been cancelled by user. if (mOwner) { mOwner->LoadAborted(); } return; } { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); UpdatePlaybackRate(); } if (NS_SUCCEEDED(aStatus)) { ResourceLoaded(); } else if (aStatus != NS_BASE_STREAM_CLOSED) { NetworkError(); } UpdateReadyStateForData(); } void nsBuiltinDecoder::NotifyPrincipalChanged() { if (mOwner) { mOwner->NotifyDecoderPrincipalChanged(); } } void nsBuiltinDecoder::NotifyBytesConsumed(int64_t aBytes) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); NS_ASSERTION(OnStateMachineThread() || mDecoderStateMachine->OnDecodeThread(), "Should be on play state machine or decode thread."); if (!mIgnoreProgressData) { mDecoderPosition += aBytes; mPlaybackStatistics.AddBytes(aBytes); } } void nsBuiltinDecoder::NextFrameUnavailableBuffering() { NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); if (!mOwner || mShuttingDown || !mDecoderStateMachine) return; mOwner->UpdateReadyStateForData(nsMediaDecoder::NEXT_FRAME_UNAVAILABLE_BUFFERING); } void nsBuiltinDecoder::NextFrameAvailable() { NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); if (!mOwner || mShuttingDown || !mDecoderStateMachine) return; mOwner->UpdateReadyStateForData(nsMediaDecoder::NEXT_FRAME_AVAILABLE); } void nsBuiltinDecoder::NextFrameUnavailable() { NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); if (!mOwner || mShuttingDown || !mDecoderStateMachine) return; mOwner->UpdateReadyStateForData(nsMediaDecoder::NEXT_FRAME_UNAVAILABLE); } void nsBuiltinDecoder::UpdateReadyStateForData() { NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); if (!mOwner || mShuttingDown || !mDecoderStateMachine) return; NextFrameStatus frameStatus = mDecoderStateMachine->GetNextFrameStatus(); mOwner->UpdateReadyStateForData(frameStatus); } void nsBuiltinDecoder::SeekingStopped() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; bool seekWasAborted = false; { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); // An additional seek was requested while the current seek was // in operation. if (mRequestedSeekTime >= 0.0) { ChangeState(PLAY_STATE_SEEKING); seekWasAborted = true; } else { UnpinForSeek(); ChangeState(mNextState); } } if (mOwner) { UpdateReadyStateForData(); if (!seekWasAborted) { mOwner->SeekCompleted(); } } } // This is called when seeking stopped *and* we're at the end of the // media. void nsBuiltinDecoder::SeekingStoppedAtEnd() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; bool fireEnded = false; bool seekWasAborted = false; { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); // An additional seek was requested while the current seek was // in operation. if (mRequestedSeekTime >= 0.0) { ChangeState(PLAY_STATE_SEEKING); seekWasAborted = true; } else { UnpinForSeek(); fireEnded = true; ChangeState(PLAY_STATE_ENDED); } } if (mOwner) { UpdateReadyStateForData(); if (!seekWasAborted) { mOwner->SeekCompleted(); if (fireEnded) { mOwner->PlaybackEnded(); } } } } void nsBuiltinDecoder::SeekingStarted() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; if (mOwner) { UpdateReadyStateForData(); mOwner->SeekStarted(); } } void nsBuiltinDecoder::ChangeState(PlayState aState) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mNextState == aState) { mNextState = PLAY_STATE_PAUSED; } if (mPlayState == PLAY_STATE_SHUTDOWN) { GetReentrantMonitor().NotifyAll(); return; } if (mDecodedStream) { bool blockForPlayState = aState != PLAY_STATE_PLAYING; if (mDecodedStream->mHaveBlockedForPlayState != blockForPlayState) { mDecodedStream->mStream->ChangeExplicitBlockerCount(blockForPlayState ? 1 : -1); mDecodedStream->mHaveBlockedForPlayState = blockForPlayState; } } mPlayState = aState; if (mDecoderStateMachine) { switch (aState) { case PLAY_STATE_PLAYING: mDecoderStateMachine->Play(); break; case PLAY_STATE_SEEKING: mDecoderStateMachine->Seek(mRequestedSeekTime); mRequestedSeekTime = -1.0; break; default: /* No action needed */ break; } } GetReentrantMonitor().NotifyAll(); } void nsBuiltinDecoder::PlaybackPositionChanged() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mShuttingDown) return; double lastTime = mCurrentTime; // Control the scope of the monitor so it is not // held while the timeupdate and the invalidate is run. { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mDecoderStateMachine) { if (!IsSeeking()) { // Only update the current playback position if we're not seeking. // If we are seeking, the update could have been scheduled on the // state machine thread while we were playing but after the seek // algorithm set the current playback position on the main thread, // and we don't want to override the seek algorithm and change the // current time after the seek has started but before it has // completed. mCurrentTime = mDecoderStateMachine->GetCurrentTime(); } mDecoderStateMachine->ClearPositionChangeFlag(); } } // Invalidate the frame so any video data is displayed. // Do this before the timeupdate event so that if that // event runs JavaScript that queries the media size, the // frame has reflowed and the size updated beforehand. Invalidate(); if (mOwner && lastTime != mCurrentTime) { FireTimeUpdate(); } } void nsBuiltinDecoder::DurationChanged() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); int64_t oldDuration = mDuration; mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1; // Duration has changed so we should recompute playback rate UpdatePlaybackRate(); if (mOwner && oldDuration != mDuration && !IsInfinite()) { LOG(PR_LOG_DEBUG, ("%p duration changed to %lld", this, mDuration)); mOwner->DispatchEvent(NS_LITERAL_STRING("durationchange")); } } void nsBuiltinDecoder::SetDuration(double aDuration) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); mDuration = static_cast(NS_round(aDuration * static_cast(USECS_PER_S))); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mDecoderStateMachine) { mDecoderStateMachine->SetDuration(mDuration); } // Duration has changed so we should recompute playback rate UpdatePlaybackRate(); } void nsBuiltinDecoder::SetSeekable(bool aSeekable) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); mSeekable = aSeekable; if (mDecoderStateMachine) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mDecoderStateMachine->SetSeekable(aSeekable); } } bool nsBuiltinDecoder::IsSeekable() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); return mSeekable; } nsresult nsBuiltinDecoder::GetSeekable(nsTimeRanges* aSeekable) { //TODO : change 0.0 to GetInitialTime() when available double initialTime = 0.0; if (IsSeekable()) { double end = IsInfinite() ? std::numeric_limits::infinity() : initialTime + GetDuration(); aSeekable->Add(initialTime, end); return NS_OK; } if (mDecoderStateMachine && mDecoderStateMachine->IsSeekableInBufferedRanges()) { return GetBuffered(aSeekable); } else { // The stream is not seekable using only buffered ranges, and is not // seekable. Don't allow seeking (return no ranges in |seekable|). return NS_OK; } } void nsBuiltinDecoder::SetEndTime(double aTime) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mDecoderStateMachine) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mDecoderStateMachine->SetFragmentEndTime(static_cast(aTime * USECS_PER_S)); } } void nsBuiltinDecoder::Suspend() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mResource) { mResource->Suspend(true); } } void nsBuiltinDecoder::Resume(bool aForceBuffering) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mResource) { mResource->Resume(); } if (aForceBuffering) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mDecoderStateMachine) { mDecoderStateMachine->StartBuffering(); } } } void nsBuiltinDecoder::StopProgressUpdates() { NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine or decode thread."); GetReentrantMonitor().AssertCurrentThreadIn(); mIgnoreProgressData = true; if (mResource) { mResource->SetReadMode(nsMediaCacheStream::MODE_METADATA); } } void nsBuiltinDecoder::StartProgressUpdates() { NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine or decode thread."); GetReentrantMonitor().AssertCurrentThreadIn(); mIgnoreProgressData = false; if (mResource) { mResource->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK); mDecoderPosition = mPlaybackPosition = mResource->Tell(); } } void nsBuiltinDecoder::MoveLoadsToBackground() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mResource) { mResource->MoveLoadsToBackground(); } } void nsBuiltinDecoder::UpdatePlaybackOffset(int64_t aOffset) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mPlaybackPosition = NS_MAX(aOffset, mPlaybackPosition); } bool nsBuiltinDecoder::OnStateMachineThread() const { return IsCurrentThread(nsBuiltinDecoderStateMachine::GetStateMachineThread()); } void nsBuiltinDecoder::NotifyAudioAvailableListener() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mDecoderStateMachine) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mDecoderStateMachine->NotifyAudioAvailableListener(); } } bool nsBuiltinDecoder::OnDecodeThread() const { return mDecoderStateMachine->OnDecodeThread(); } ReentrantMonitor& nsBuiltinDecoder::GetReentrantMonitor() { return mReentrantMonitor.GetReentrantMonitor(); } // Constructs the time ranges representing what segments of the media // are buffered and playable. nsresult nsBuiltinDecoder::GetBuffered(nsTimeRanges* aBuffered) { if (mDecoderStateMachine) { return mDecoderStateMachine->GetBuffered(aBuffered); } return NS_ERROR_FAILURE; } int64_t nsBuiltinDecoder::VideoQueueMemoryInUse() { if (mDecoderStateMachine) { return mDecoderStateMachine->VideoQueueMemoryInUse(); } return 0; } int64_t nsBuiltinDecoder::AudioQueueMemoryInUse() { if (mDecoderStateMachine) { return mDecoderStateMachine->AudioQueueMemoryInUse(); } return 0; } void nsBuiltinDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) { if (mDecoderStateMachine) { mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset); } } void nsBuiltinDecoder::UpdatePlaybackPosition(int64_t aTime) { mDecoderStateMachine->UpdatePlaybackPosition(aTime); } // Provide access to the state machine object nsBuiltinDecoderStateMachine* nsBuiltinDecoder::GetStateMachine() { return mDecoderStateMachine; } // Drop reference to state machine. Only called during shutdown dance. void nsBuiltinDecoder::ReleaseStateMachine() { mDecoderStateMachine = nullptr; }