michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MediaDecoder.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include michael@0: #include "nsIObserver.h" michael@0: #include "nsTArray.h" michael@0: #include "VideoUtils.h" michael@0: #include "MediaDecoderStateMachine.h" michael@0: #include "mozilla/dom/TimeRanges.h" michael@0: #include "ImageContainer.h" michael@0: #include "MediaResource.h" michael@0: #include "nsError.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsITimer.h" michael@0: #include michael@0: #include "MediaShutdownManager.h" michael@0: #include "AudioChannelService.h" michael@0: michael@0: #ifdef MOZ_WMF michael@0: #include "WMFDecoder.h" michael@0: #endif michael@0: michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace mozilla { michael@0: michael@0: // Number of milliseconds between progress events as defined by spec michael@0: static const uint32_t PROGRESS_MS = 350; michael@0: michael@0: // Number of milliseconds of no data before a stall event is fired as defined by spec michael@0: static const uint32_t STALL_MS = 3000; michael@0: michael@0: // Number of estimated seconds worth of data we need to have buffered michael@0: // ahead of the current playback position before we allow the media decoder michael@0: // to report that it can play through the entire media without the decode michael@0: // catching up with the download. Having this margin make the michael@0: // MediaDecoder::CanPlayThrough() calculation more stable in the case of michael@0: // fluctuating bitrates. michael@0: static const int64_t CAN_PLAY_THROUGH_MARGIN = 1; michael@0: michael@0: // avoid redefined macro in unified build michael@0: #undef DECODER_LOG michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gMediaDecoderLog; michael@0: #define DECODER_LOG(type, msg, ...) \ michael@0: PR_LOG(gMediaDecoderLog, type, ("Decoder=%p " msg, this, ##__VA_ARGS__)) michael@0: #else michael@0: #define DECODER_LOG(type, msg, ...) michael@0: #endif michael@0: michael@0: class MediaMemoryTracker : public nsIMemoryReporter michael@0: { michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIMEMORYREPORTER michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); michael@0: michael@0: MediaMemoryTracker(); michael@0: virtual ~MediaMemoryTracker(); michael@0: void InitMemoryReporter(); michael@0: michael@0: static StaticRefPtr sUniqueInstance; michael@0: michael@0: static MediaMemoryTracker* UniqueInstance() { michael@0: if (!sUniqueInstance) { michael@0: sUniqueInstance = new MediaMemoryTracker(); michael@0: sUniqueInstance->InitMemoryReporter(); michael@0: } michael@0: return sUniqueInstance; michael@0: } michael@0: michael@0: typedef nsTArray DecodersArray; michael@0: static DecodersArray& Decoders() { michael@0: return UniqueInstance()->mDecoders; michael@0: } michael@0: michael@0: DecodersArray mDecoders; michael@0: michael@0: public: michael@0: static void AddMediaDecoder(MediaDecoder* aDecoder) michael@0: { michael@0: Decoders().AppendElement(aDecoder); michael@0: } michael@0: michael@0: static void RemoveMediaDecoder(MediaDecoder* aDecoder) michael@0: { michael@0: DecodersArray& decoders = Decoders(); michael@0: decoders.RemoveElement(aDecoder); michael@0: if (decoders.IsEmpty()) { michael@0: sUniqueInstance = nullptr; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: StaticRefPtr MediaMemoryTracker::sUniqueInstance; michael@0: michael@0: NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter) michael@0: michael@0: NS_IMPL_ISUPPORTS(MediaDecoder, nsIObserver) michael@0: michael@0: void MediaDecoder::SetDormantIfNecessary(bool aDormant) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: if (!mDecoderStateMachine || !mDecoderStateMachine->IsDormantNeeded() || (mPlayState == PLAY_STATE_SHUTDOWN)) { michael@0: return; michael@0: } michael@0: michael@0: if (mIsDormant == aDormant) { michael@0: // no change to dormant state michael@0: return; michael@0: } michael@0: michael@0: if(aDormant) { michael@0: // enter dormant state michael@0: StopProgress(); michael@0: DestroyDecodedStream(); michael@0: mDecoderStateMachine->SetDormant(true); michael@0: michael@0: mRequestedSeekTarget = SeekTarget(mCurrentTime, SeekTarget::Accurate); michael@0: if (mPlayState == PLAY_STATE_PLAYING){ michael@0: mNextState = PLAY_STATE_PLAYING; michael@0: } else { michael@0: mNextState = PLAY_STATE_PAUSED; michael@0: } michael@0: mNextState = mPlayState; michael@0: mIsDormant = true; michael@0: mIsExitingDormant = false; michael@0: ChangeState(PLAY_STATE_LOADING); michael@0: } else if ((aDormant != true) && (mPlayState == PLAY_STATE_LOADING)) { michael@0: // exit dormant state michael@0: // trigger to state machine. michael@0: mDecoderStateMachine->SetDormant(false); michael@0: mIsExitingDormant = true; michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::Pause() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if ((mPlayState == PLAY_STATE_LOADING && mIsDormant) || mPlayState == PLAY_STATE_SEEKING || mPlayState == PLAY_STATE_ENDED) { michael@0: mNextState = PLAY_STATE_PAUSED; michael@0: return; michael@0: } michael@0: michael@0: ChangeState(PLAY_STATE_PAUSED); michael@0: } michael@0: michael@0: void MediaDecoder::SetVolume(double aVolume) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mInitialVolume = aVolume; michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetVolume(aVolume); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SetAudioCaptured(bool aCaptured) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mInitialAudioCaptured = aCaptured; michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetAudioCaptured(aCaptured); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::ConnectDecodedStreamToOutputStream(OutputStreamData* aStream) michael@0: { michael@0: NS_ASSERTION(!aStream->mPort, "Already connected?"); michael@0: michael@0: // The output stream must stay in sync with the decoded stream, so if michael@0: // either stream is blocked, we block the other. michael@0: aStream->mPort = aStream->mStream->AllocateInputPort(mDecodedStream->mStream, michael@0: MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT); michael@0: // Unblock the output stream now. While it's connected to mDecodedStream, michael@0: // mDecodedStream is responsible for controlling blocking. michael@0: aStream->mStream->ChangeExplicitBlockerCount(-1); michael@0: } michael@0: michael@0: MediaDecoder::DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder, michael@0: int64_t aInitialTime, michael@0: SourceMediaStream* aStream) michael@0: : mLastAudioPacketTime(-1), michael@0: mLastAudioPacketEndTime(-1), michael@0: mAudioFramesWritten(0), michael@0: mInitialTime(aInitialTime), michael@0: mNextVideoTime(aInitialTime), michael@0: mDecoder(aDecoder), michael@0: mStreamInitialized(false), michael@0: mHaveSentFinish(false), michael@0: mHaveSentFinishAudio(false), michael@0: mHaveSentFinishVideo(false), michael@0: mStream(aStream), michael@0: mHaveBlockedForPlayState(false), michael@0: mHaveBlockedForStateMachineNotPlaying(false) michael@0: { michael@0: mListener = new DecodedStreamGraphListener(mStream, this); michael@0: mStream->AddListener(mListener); michael@0: } michael@0: michael@0: MediaDecoder::DecodedStreamData::~DecodedStreamData() michael@0: { michael@0: mListener->Forget(); michael@0: mStream->Destroy(); michael@0: } michael@0: michael@0: MediaDecoder::DecodedStreamGraphListener::DecodedStreamGraphListener(MediaStream* aStream, michael@0: DecodedStreamData* aData) michael@0: : mData(aData), michael@0: mMutex("MediaDecoder::DecodedStreamData::mMutex"), michael@0: mStream(aStream), michael@0: mLastOutputTime(aStream->GetCurrentTime()), michael@0: mStreamFinishedOnMainThread(false) michael@0: { michael@0: } michael@0: michael@0: void michael@0: MediaDecoder::DecodedStreamGraphListener::NotifyOutput(MediaStreamGraph* aGraph, michael@0: GraphTime aCurrentTime) michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: if (mStream) { michael@0: mLastOutputTime = mStream->GraphTimeToStreamTime(aCurrentTime); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MediaDecoder::DecodedStreamGraphListener::DoNotifyFinished() michael@0: { michael@0: if (mData && mData->mDecoder) { michael@0: if (mData->mDecoder->GetState() == PLAY_STATE_PLAYING) { michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(mData->mDecoder, &MediaDecoder::PlaybackEnded); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: mStreamFinishedOnMainThread = true; michael@0: } michael@0: michael@0: void michael@0: MediaDecoder::DecodedStreamGraphListener::NotifyFinished(MediaStreamGraph* aGraph) michael@0: { michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished); michael@0: aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget()); michael@0: } michael@0: michael@0: void MediaDecoder::DestroyDecodedStream() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: michael@0: // All streams are having their SourceMediaStream disconnected, so they michael@0: // need to be explicitly blocked again. michael@0: for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) { michael@0: OutputStreamData& os = mOutputStreams[i]; michael@0: // During cycle collection, nsDOMMediaStream can be destroyed and send michael@0: // its Destroy message before this decoder is destroyed. So we have to michael@0: // be careful not to send any messages after the Destroy(). michael@0: if (os.mStream->IsDestroyed()) { michael@0: // Probably the DOM MediaStream was GCed. Clean up. michael@0: os.mPort->Destroy(); michael@0: mOutputStreams.RemoveElementAt(i); michael@0: continue; michael@0: } michael@0: os.mStream->ChangeExplicitBlockerCount(1); michael@0: // Explicitly remove all existing ports. This is not strictly necessary but it's michael@0: // good form. michael@0: os.mPort->Destroy(); michael@0: os.mPort = nullptr; michael@0: } michael@0: michael@0: mDecodedStream = nullptr; michael@0: } michael@0: michael@0: void MediaDecoder::UpdateStreamBlockingForStateMachinePlaying() michael@0: { michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: if (!mDecodedStream) { michael@0: return; michael@0: } michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetSyncPointForMediaStream(); michael@0: } michael@0: bool blockForStateMachineNotPlaying = michael@0: mDecoderStateMachine && !mDecoderStateMachine->IsPlaying() && michael@0: mDecoderStateMachine->GetState() != MediaDecoderStateMachine::DECODER_STATE_COMPLETED; michael@0: if (blockForStateMachineNotPlaying != mDecodedStream->mHaveBlockedForStateMachineNotPlaying) { michael@0: mDecodedStream->mHaveBlockedForStateMachineNotPlaying = blockForStateMachineNotPlaying; michael@0: int32_t delta = blockForStateMachineNotPlaying ? 1 : -1; michael@0: if (NS_IsMainThread()) { michael@0: mDecodedStream->mStream->ChangeExplicitBlockerCount(delta); michael@0: } else { michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethodWithArg(mDecodedStream->mStream.get(), michael@0: &MediaStream::ChangeExplicitBlockerCount, delta); michael@0: NS_DispatchToMainThread(runnable); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: DECODER_LOG(PR_LOG_DEBUG, "RecreateDecodedStream aStartTimeUSecs=%lld!", aStartTimeUSecs); michael@0: michael@0: DestroyDecodedStream(); michael@0: michael@0: mDecodedStream = new DecodedStreamData(this, aStartTimeUSecs, michael@0: MediaStreamGraph::GetInstance()->CreateSourceStream(nullptr)); michael@0: michael@0: // Note that the delay between removing ports in DestroyDecodedStream michael@0: // and adding new ones won't cause a glitch since all graph operations michael@0: // between main-thread stable states take effect atomically. michael@0: for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) { michael@0: OutputStreamData& os = mOutputStreams[i]; michael@0: if (os.mStream->IsDestroyed()) { michael@0: // Probably the DOM MediaStream was GCed. Clean up. michael@0: // No need to destroy the port; all ports have been destroyed here. michael@0: mOutputStreams.RemoveElementAt(i); michael@0: continue; michael@0: } michael@0: ConnectDecodedStreamToOutputStream(&os); michael@0: } michael@0: UpdateStreamBlockingForStateMachinePlaying(); michael@0: michael@0: mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING; michael@0: if (mDecodedStream->mHaveBlockedForPlayState) { michael@0: mDecodedStream->mStream->ChangeExplicitBlockerCount(1); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream, michael@0: bool aFinishWhenEnded) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: DECODER_LOG(PR_LOG_DEBUG, "AddOutputStream aStream=%p!", aStream); michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (!mDecodedStream) { michael@0: RecreateDecodedStream(mDecoderStateMachine ? michael@0: int64_t(mDecoderStateMachine->GetCurrentTime()*USECS_PER_S) : 0); michael@0: } michael@0: OutputStreamData* os = mOutputStreams.AppendElement(); michael@0: os->Init(aStream, aFinishWhenEnded); michael@0: ConnectDecodedStreamToOutputStream(os); michael@0: if (aFinishWhenEnded) { michael@0: // Ensure that aStream finishes the moment mDecodedStream does. michael@0: aStream->SetAutofinish(true); michael@0: } michael@0: } michael@0: michael@0: // This can be called before Load(), in which case our mDecoderStateMachine michael@0: // won't have been created yet and we can rely on Load() to schedule it michael@0: // once it is created. michael@0: if (mDecoderStateMachine) { michael@0: // Make sure the state machine thread runs so that any buffered data michael@0: // is fed into our stream. michael@0: ScheduleStateMachineThread(); michael@0: } michael@0: } michael@0: michael@0: double MediaDecoder::GetDuration() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mInfiniteStream) { michael@0: return std::numeric_limits::infinity(); michael@0: } michael@0: if (mDuration >= 0) { michael@0: return static_cast(mDuration) / static_cast(USECS_PER_S); michael@0: } michael@0: return std::numeric_limits::quiet_NaN(); michael@0: } michael@0: michael@0: int64_t MediaDecoder::GetMediaDuration() michael@0: { michael@0: NS_ENSURE_TRUE(GetStateMachine(), -1); michael@0: return GetStateMachine()->GetDuration(); michael@0: } michael@0: michael@0: void MediaDecoder::SetInfinite(bool aInfinite) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mInfiniteStream = aInfinite; michael@0: } michael@0: michael@0: bool MediaDecoder::IsInfinite() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mInfiniteStream; michael@0: } michael@0: michael@0: MediaDecoder::MediaDecoder() : michael@0: mDecoderPosition(0), michael@0: mPlaybackPosition(0), michael@0: mCurrentTime(0.0), michael@0: mInitialVolume(0.0), michael@0: mInitialPlaybackRate(1.0), michael@0: mInitialPreservesPitch(true), michael@0: mDuration(-1), michael@0: mTransportSeekable(true), michael@0: mMediaSeekable(true), michael@0: mSameOriginMedia(false), michael@0: mReentrantMonitor("media.decoder"), michael@0: mIsDormant(false), michael@0: mIsExitingDormant(false), michael@0: mPlayState(PLAY_STATE_PAUSED), michael@0: mNextState(PLAY_STATE_PAUSED), michael@0: mCalledResourceLoaded(false), michael@0: mIgnoreProgressData(false), michael@0: mInfiniteStream(false), michael@0: mOwner(nullptr), michael@0: mPinnedForSeek(false), michael@0: mShuttingDown(false), michael@0: mPausedForPlaybackRateNull(false), michael@0: mMinimizePreroll(false) michael@0: { michael@0: MOZ_COUNT_CTOR(MediaDecoder); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MediaMemoryTracker::AddMediaDecoder(this); michael@0: #ifdef PR_LOGGING michael@0: if (!gMediaDecoderLog) { michael@0: gMediaDecoderLog = PR_NewLogModule("MediaDecoder"); michael@0: } michael@0: #endif michael@0: michael@0: mAudioChannel = AudioChannelService::GetDefaultAudioChannel(); michael@0: } michael@0: michael@0: bool MediaDecoder::Init(MediaDecoderOwner* aOwner) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mOwner = aOwner; michael@0: mVideoFrameContainer = aOwner->GetVideoFrameContainer(); michael@0: MediaShutdownManager::Instance().Register(this); michael@0: return true; michael@0: } michael@0: michael@0: void MediaDecoder::Shutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: mShuttingDown = true; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: DestroyDecodedStream(); michael@0: } michael@0: michael@0: // This changes the decoder state to SHUTDOWN and does other things michael@0: // necessary to unblock the state machine thread if it's blocked, so michael@0: // the asynchronous shutdown in nsDestroyStateMachine won't deadlock. michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->Shutdown(); michael@0: } michael@0: michael@0: // Force any outstanding seek and byterange requests to complete michael@0: // to prevent shutdown from deadlocking. michael@0: if (mResource) { michael@0: mResource->Close(); michael@0: } michael@0: michael@0: ChangeState(PLAY_STATE_SHUTDOWN); michael@0: michael@0: StopProgress(); michael@0: mOwner = nullptr; michael@0: michael@0: MediaShutdownManager::Instance().Unregister(this); michael@0: } michael@0: michael@0: MediaDecoder::~MediaDecoder() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MediaMemoryTracker::RemoveMediaDecoder(this); michael@0: UnpinForSeek(); michael@0: MOZ_COUNT_DTOR(MediaDecoder); michael@0: } michael@0: michael@0: nsresult MediaDecoder::OpenResource(nsIStreamListener** aStreamListener) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (aStreamListener) { michael@0: *aStreamListener = nullptr; michael@0: } michael@0: michael@0: { michael@0: // Hold the lock while we do this to set proper lock ordering michael@0: // expectations for dynamic deadlock detectors: decoder lock(s) michael@0: // should be grabbed before the cache lock michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: nsresult rv = mResource->Open(aStreamListener); michael@0: if (NS_FAILED(rv)) { michael@0: DECODER_LOG(PR_LOG_WARNING, "Failed to open stream!"); michael@0: return rv; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult MediaDecoder::Load(nsIStreamListener** aStreamListener, michael@0: MediaDecoder* aCloneDonor) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv = OpenResource(aStreamListener); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mDecoderStateMachine = CreateStateMachine(); michael@0: if (!mDecoderStateMachine) { michael@0: DECODER_LOG(PR_LOG_WARNING, "Failed to create state machine!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return InitializeStateMachine(aCloneDonor); michael@0: } michael@0: michael@0: nsresult MediaDecoder::InitializeStateMachine(MediaDecoder* aCloneDonor) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: NS_ASSERTION(mDecoderStateMachine, "Cannot initialize null state machine!"); michael@0: michael@0: MediaDecoder* cloneDonor = static_cast(aCloneDonor); michael@0: if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ? michael@0: cloneDonor->mDecoderStateMachine : nullptr))) { michael@0: DECODER_LOG(PR_LOG_WARNING, "Failed to init state machine!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: mDecoderStateMachine->SetTransportSeekable(mTransportSeekable); michael@0: mDecoderStateMachine->SetMediaSeekable(mMediaSeekable); michael@0: mDecoderStateMachine->SetDuration(mDuration); michael@0: mDecoderStateMachine->SetVolume(mInitialVolume); michael@0: mDecoderStateMachine->SetAudioCaptured(mInitialAudioCaptured); michael@0: SetPlaybackRate(mInitialPlaybackRate); michael@0: mDecoderStateMachine->SetPreservesPitch(mInitialPreservesPitch); michael@0: if (mMinimizePreroll) { michael@0: mDecoderStateMachine->SetMinimizePrerollUntilPlaybackStarts(); michael@0: } michael@0: } michael@0: michael@0: ChangeState(PLAY_STATE_LOADING); michael@0: michael@0: return ScheduleStateMachineThread(); michael@0: } michael@0: michael@0: void MediaDecoder::SetMinimizePrerollUntilPlaybackStarts() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mMinimizePreroll = true; michael@0: } michael@0: michael@0: nsresult MediaDecoder::ScheduleStateMachineThread() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: NS_ASSERTION(mDecoderStateMachine, michael@0: "Must have state machine to start state machine thread"); michael@0: NS_ENSURE_STATE(mDecoderStateMachine); michael@0: michael@0: if (mShuttingDown) michael@0: return NS_OK; michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: return mDecoderStateMachine->ScheduleStateMachine(); michael@0: } michael@0: michael@0: nsresult MediaDecoder::Play() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine."); michael@0: nsresult res = ScheduleStateMachineThread(); michael@0: NS_ENSURE_SUCCESS(res,res); michael@0: if ((mPlayState == PLAY_STATE_LOADING && mIsDormant) || mPlayState == PLAY_STATE_SEEKING) { michael@0: mNextState = PLAY_STATE_PLAYING; michael@0: return NS_OK; michael@0: } michael@0: if (mPlayState == PLAY_STATE_ENDED) michael@0: return Seek(0, SeekTarget::PrevSyncPoint); michael@0: michael@0: ChangeState(PLAY_STATE_PLAYING); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: NS_ABORT_IF_FALSE(aTime >= 0.0, "Cannot seek to a negative value."); michael@0: michael@0: int64_t timeUsecs = 0; michael@0: nsresult rv = SecondsToUsecs(aTime, timeUsecs); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType); michael@0: mCurrentTime = aTime; michael@0: michael@0: // If we are already in the seeking state, then setting mRequestedSeekTarget michael@0: // above will result in the new seek occurring when the current seek michael@0: // completes. michael@0: if ((mPlayState != PLAY_STATE_LOADING || !mIsDormant) && mPlayState != PLAY_STATE_SEEKING) { michael@0: bool paused = false; michael@0: if (mOwner) { michael@0: paused = mOwner->GetPaused(); michael@0: } michael@0: mNextState = paused ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING; michael@0: PinForSeek(); michael@0: ChangeState(PLAY_STATE_SEEKING); michael@0: } michael@0: michael@0: return ScheduleStateMachineThread(); michael@0: } michael@0: michael@0: bool MediaDecoder::IsLogicallyPlaying() michael@0: { michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: return mPlayState == PLAY_STATE_PLAYING || michael@0: mNextState == PLAY_STATE_PLAYING; michael@0: } michael@0: michael@0: double MediaDecoder::GetCurrentTime() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mCurrentTime; michael@0: } michael@0: michael@0: already_AddRefed MediaDecoder::GetCurrentPrincipal() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mResource ? mResource->GetCurrentPrincipal() : nullptr; michael@0: } michael@0: michael@0: void MediaDecoder::QueueMetadata(int64_t aPublishTime, michael@0: int aChannels, michael@0: int aRate, michael@0: bool aHasAudio, michael@0: bool aHasVideo, michael@0: MetadataTags* aTags) michael@0: { michael@0: NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: mDecoderStateMachine->QueueMetadata(aPublishTime, aChannels, aRate, aHasAudio, aHasVideo, aTags); michael@0: } michael@0: michael@0: bool michael@0: MediaDecoder::IsDataCachedToEndOfResource() michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: return (mResource && michael@0: mResource->IsDataCachedToEndOfResource(mDecoderPosition)); michael@0: } michael@0: michael@0: void MediaDecoder::MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mShuttingDown) { michael@0: return; michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mPlayState == PLAY_STATE_LOADING && mIsDormant && !mIsExitingDormant) { michael@0: return; michael@0: } else if (mPlayState == PLAY_STATE_LOADING && mIsDormant && mIsExitingDormant) { michael@0: mIsDormant = false; michael@0: mIsExitingDormant = false; michael@0: } michael@0: mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1; michael@0: // Duration has changed so we should recompute playback rate michael@0: UpdatePlaybackRate(); michael@0: } michael@0: michael@0: if (mDuration == -1) { michael@0: SetInfinite(true); michael@0: } michael@0: michael@0: if (mOwner) { michael@0: // Make sure the element and the frame (if any) are told about michael@0: // our new size. michael@0: Invalidate(); michael@0: mOwner->MetadataLoaded(aChannels, aRate, aHasAudio, aHasVideo, aTags); michael@0: } michael@0: michael@0: if (!mCalledResourceLoaded) { michael@0: StartProgress(); michael@0: } else if (mOwner) { michael@0: // Resource was loaded during metadata loading, when progress michael@0: // events are being ignored. Fire the final progress event. michael@0: mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("progress")); michael@0: } michael@0: michael@0: // Only inform the element of FirstFrameLoaded if not doing a load() in order michael@0: // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events. michael@0: bool notifyResourceIsLoaded = !mCalledResourceLoaded && michael@0: IsDataCachedToEndOfResource(); michael@0: if (mOwner) { michael@0: mOwner->FirstFrameLoaded(notifyResourceIsLoaded); michael@0: } michael@0: michael@0: // This can run cache callbacks. michael@0: mResource->EnsureCacheUpToDate(); michael@0: michael@0: // The element can run javascript via events michael@0: // before reaching here, so only change the michael@0: // state if we're still set to the original michael@0: // loading state. michael@0: if (mPlayState == PLAY_STATE_LOADING) { michael@0: if (mRequestedSeekTarget.IsValid()) { michael@0: ChangeState(PLAY_STATE_SEEKING); michael@0: } michael@0: else { michael@0: ChangeState(mNextState); michael@0: } michael@0: } michael@0: michael@0: if (notifyResourceIsLoaded) { michael@0: ResourceLoaded(); michael@0: } michael@0: michael@0: // Run NotifySuspendedStatusChanged now to give us a chance to notice michael@0: // that autoplay should run. michael@0: NotifySuspendedStatusChanged(); michael@0: } michael@0: michael@0: void MediaDecoder::ResourceLoaded() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Don't handle ResourceLoaded if we are shutting down, or if michael@0: // we need to ignore progress data due to seeking (in the case michael@0: // that the seek results in reaching end of file, we get a bogus call michael@0: // to ResourceLoaded). michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: { michael@0: // If we are seeking or loading then the resource loaded notification we get michael@0: // should be ignored, since it represents the end of the seek request. michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mIgnoreProgressData || mCalledResourceLoaded || mPlayState == PLAY_STATE_LOADING) michael@0: return; michael@0: michael@0: Progress(false); michael@0: michael@0: mCalledResourceLoaded = true; michael@0: StopProgress(); michael@0: } michael@0: michael@0: // Ensure the final progress event gets fired michael@0: if (mOwner) { michael@0: mOwner->ResourceLoaded(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::ResetConnectionState() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: if (mOwner) { michael@0: // Notify the media element that connection gets lost. michael@0: mOwner->ResetConnectionState(); michael@0: } michael@0: michael@0: // Since we have notified the media element the connection michael@0: // lost event, the decoder will be reloaded when user tries michael@0: // to play the Rtsp streaming next time. michael@0: Shutdown(); michael@0: } michael@0: michael@0: void MediaDecoder::NetworkError() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: if (mOwner) michael@0: mOwner->NetworkError(); michael@0: michael@0: Shutdown(); michael@0: } michael@0: michael@0: void MediaDecoder::DecodeError() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: if (mOwner) michael@0: mOwner->DecodeError(); michael@0: michael@0: Shutdown(); michael@0: } michael@0: michael@0: void MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: mSameOriginMedia = aSameOrigin; michael@0: } michael@0: michael@0: bool MediaDecoder::IsSameOriginMedia() michael@0: { michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: return mSameOriginMedia; michael@0: } michael@0: michael@0: bool MediaDecoder::IsSeeking() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mPlayState == PLAY_STATE_SEEKING; michael@0: } michael@0: michael@0: bool MediaDecoder::IsEnded() const michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN; michael@0: } michael@0: michael@0: void MediaDecoder::PlaybackEnded() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mShuttingDown || mPlayState == MediaDecoder::PLAY_STATE_SEEKING) michael@0: return; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) { michael@0: OutputStreamData& os = mOutputStreams[i]; michael@0: if (os.mStream->IsDestroyed()) { michael@0: // Probably the DOM MediaStream was GCed. Clean up. michael@0: os.mPort->Destroy(); michael@0: mOutputStreams.RemoveElementAt(i); michael@0: continue; michael@0: } michael@0: if (os.mFinishWhenEnded) { michael@0: // Shouldn't really be needed since mDecodedStream should already have michael@0: // finished, but doesn't hurt. michael@0: os.mStream->Finish(); michael@0: os.mPort->Destroy(); michael@0: // Not really needed but it keeps the invariant that a stream not michael@0: // connected to mDecodedStream is explicity blocked. michael@0: os.mStream->ChangeExplicitBlockerCount(1); michael@0: mOutputStreams.RemoveElementAt(i); michael@0: } michael@0: } michael@0: } michael@0: michael@0: PlaybackPositionChanged(); michael@0: ChangeState(PLAY_STATE_ENDED); michael@0: InvalidateWithFlags(VideoFrameContainer::INVALIDATE_FORCE); michael@0: michael@0: UpdateReadyStateForData(); michael@0: if (mOwner) { michael@0: mOwner->PlaybackEnded(); michael@0: } michael@0: michael@0: // This must be called after |mOwner->PlaybackEnded()| call above, in order michael@0: // to fire the required durationchange. michael@0: if (IsInfinite()) { michael@0: SetInfinite(false); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP MediaDecoder::Observe(nsISupports *aSubjet, michael@0: const char *aTopic, michael@0: const char16_t *someData) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { michael@0: Shutdown(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: MediaDecoder::Statistics michael@0: MediaDecoder::GetStatistics() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread()); michael@0: Statistics result; michael@0: michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mResource) { michael@0: result.mDownloadRate = michael@0: mResource->GetDownloadRate(&result.mDownloadRateReliable); michael@0: result.mDownloadPosition = michael@0: mResource->GetCachedDataEnd(mDecoderPosition); michael@0: result.mTotalBytes = mResource->GetLength(); michael@0: result.mPlaybackRate = ComputePlaybackRate(&result.mPlaybackRateReliable); michael@0: result.mDecoderPosition = mDecoderPosition; michael@0: result.mPlaybackPosition = mPlaybackPosition; michael@0: } michael@0: else { michael@0: result.mDownloadRate = 0; michael@0: result.mDownloadRateReliable = true; michael@0: result.mPlaybackRate = 0; michael@0: result.mPlaybackRateReliable = true; michael@0: result.mDecoderPosition = 0; michael@0: result.mPlaybackPosition = 0; michael@0: result.mDownloadPosition = 0; michael@0: result.mTotalBytes = 0; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: double MediaDecoder::ComputePlaybackRate(bool* aReliable) michael@0: { michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread() || OnDecodeThread()); michael@0: michael@0: int64_t length = mResource ? mResource->GetLength() : -1; michael@0: if (mDuration >= 0 && length >= 0) { michael@0: *aReliable = true; michael@0: return length * static_cast(USECS_PER_S) / mDuration; michael@0: } michael@0: return mPlaybackStatistics.GetRateAtLastStop(aReliable); michael@0: } michael@0: michael@0: void MediaDecoder::UpdatePlaybackRate() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread()); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: if (!mResource) michael@0: return; michael@0: bool reliable; michael@0: uint32_t rate = uint32_t(ComputePlaybackRate(&reliable)); michael@0: if (reliable) { michael@0: // Avoid passing a zero rate michael@0: rate = std::max(rate, 1u); michael@0: } michael@0: else { michael@0: // Set a minimum rate of 10,000 bytes per second ... sometimes we just michael@0: // don't have good data michael@0: rate = std::max(rate, 10000u); michael@0: } michael@0: mResource->SetPlaybackRate(rate); michael@0: } michael@0: michael@0: void MediaDecoder::NotifySuspendedStatusChanged() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!mResource) michael@0: return; michael@0: bool suspended = mResource->IsSuspendedByCache(); michael@0: if (mOwner) { michael@0: mOwner->NotifySuspendedByCache(suspended); michael@0: UpdateReadyStateForData(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::NotifyBytesDownloaded() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: UpdatePlaybackRate(); michael@0: } michael@0: UpdateReadyStateForData(); michael@0: Progress(false); michael@0: } michael@0: michael@0: void MediaDecoder::NotifyDownloadEnded(nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (aStatus == NS_BINDING_ABORTED) { michael@0: // Download has been cancelled by user. michael@0: if (mOwner) { michael@0: mOwner->LoadAborted(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: UpdatePlaybackRate(); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(aStatus)) { michael@0: ResourceLoaded(); michael@0: } michael@0: else if (aStatus != NS_BASE_STREAM_CLOSED) { michael@0: NetworkError(); michael@0: } michael@0: UpdateReadyStateForData(); michael@0: } michael@0: michael@0: void MediaDecoder::NotifyPrincipalChanged() michael@0: { michael@0: if (mOwner) { michael@0: mOwner->NotifyDecoderPrincipalChanged(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: NS_ENSURE_TRUE_VOID(mDecoderStateMachine); michael@0: if (mIgnoreProgressData) { michael@0: return; michael@0: } michael@0: if (aOffset >= mDecoderPosition) { michael@0: mPlaybackStatistics.AddBytes(aBytes); michael@0: } michael@0: mDecoderPosition = aOffset + aBytes; michael@0: } michael@0: michael@0: void MediaDecoder::UpdateReadyStateForData() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!mOwner || mShuttingDown || !mDecoderStateMachine) michael@0: return; michael@0: MediaDecoderOwner::NextFrameStatus frameStatus = michael@0: mDecoderStateMachine->GetNextFrameStatus(); michael@0: mOwner->UpdateReadyStateForData(frameStatus); michael@0: } michael@0: michael@0: void MediaDecoder::SeekingStopped() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: bool seekWasAborted = false; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: // An additional seek was requested while the current seek was michael@0: // in operation. michael@0: if (mRequestedSeekTarget.IsValid()) { michael@0: ChangeState(PLAY_STATE_SEEKING); michael@0: seekWasAborted = true; michael@0: } else { michael@0: UnpinForSeek(); michael@0: ChangeState(mNextState); michael@0: } michael@0: } michael@0: michael@0: PlaybackPositionChanged(); michael@0: michael@0: if (mOwner) { michael@0: UpdateReadyStateForData(); michael@0: if (!seekWasAborted) { michael@0: mOwner->SeekCompleted(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // This is called when seeking stopped *and* we're at the end of the michael@0: // media. michael@0: void MediaDecoder::SeekingStoppedAtEnd() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: bool fireEnded = false; michael@0: bool seekWasAborted = false; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: // An additional seek was requested while the current seek was michael@0: // in operation. michael@0: if (mRequestedSeekTarget.IsValid()) { michael@0: ChangeState(PLAY_STATE_SEEKING); michael@0: seekWasAborted = true; michael@0: } else { michael@0: UnpinForSeek(); michael@0: fireEnded = true; michael@0: ChangeState(PLAY_STATE_ENDED); michael@0: } michael@0: } michael@0: michael@0: PlaybackPositionChanged(); michael@0: michael@0: if (mOwner) { michael@0: UpdateReadyStateForData(); michael@0: if (!seekWasAborted) { michael@0: mOwner->SeekCompleted(); michael@0: if (fireEnded) { michael@0: mOwner->PlaybackEnded(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SeekingStarted() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: if (mOwner) { michael@0: UpdateReadyStateForData(); michael@0: mOwner->SeekStarted(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::ChangeState(PlayState aState) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: michael@0: if (mNextState == aState) { michael@0: mNextState = PLAY_STATE_PAUSED; michael@0: } michael@0: michael@0: if ((mPlayState == PLAY_STATE_LOADING && mIsDormant && aState != PLAY_STATE_SHUTDOWN) || michael@0: mPlayState == PLAY_STATE_SHUTDOWN) { michael@0: GetReentrantMonitor().NotifyAll(); michael@0: return; michael@0: } michael@0: michael@0: if (mDecodedStream) { michael@0: bool blockForPlayState = aState != PLAY_STATE_PLAYING; michael@0: if (mDecodedStream->mHaveBlockedForPlayState != blockForPlayState) { michael@0: mDecodedStream->mStream->ChangeExplicitBlockerCount(blockForPlayState ? 1 : -1); michael@0: mDecodedStream->mHaveBlockedForPlayState = blockForPlayState; michael@0: } michael@0: } michael@0: mPlayState = aState; michael@0: michael@0: ApplyStateToStateMachine(mPlayState); michael@0: michael@0: if (aState!= PLAY_STATE_LOADING) { michael@0: mIsDormant = false; michael@0: mIsExitingDormant = false; michael@0: } michael@0: michael@0: GetReentrantMonitor().NotifyAll(); michael@0: } michael@0: michael@0: void MediaDecoder::ApplyStateToStateMachine(PlayState aState) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: michael@0: if (mDecoderStateMachine) { michael@0: switch (aState) { michael@0: case PLAY_STATE_PLAYING: michael@0: mDecoderStateMachine->Play(); michael@0: break; michael@0: case PLAY_STATE_SEEKING: michael@0: mDecoderStateMachine->Seek(mRequestedSeekTarget); michael@0: mRequestedSeekTarget.Reset(); michael@0: break; michael@0: default: michael@0: /* No action needed */ michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::PlaybackPositionChanged() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mShuttingDown) michael@0: return; michael@0: michael@0: double lastTime = mCurrentTime; michael@0: michael@0: // Control the scope of the monitor so it is not michael@0: // held while the timeupdate and the invalidate is run. michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mDecoderStateMachine) { michael@0: if (!IsSeeking()) { michael@0: // Only update the current playback position if we're not seeking. michael@0: // If we are seeking, the update could have been scheduled on the michael@0: // state machine thread while we were playing but after the seek michael@0: // algorithm set the current playback position on the main thread, michael@0: // and we don't want to override the seek algorithm and change the michael@0: // current time after the seek has started but before it has michael@0: // completed. michael@0: if (GetDecodedStream()) { michael@0: mCurrentTime = mDecoderStateMachine->GetCurrentTimeViaMediaStreamSync()/ michael@0: static_cast(USECS_PER_S); michael@0: } else { michael@0: mCurrentTime = mDecoderStateMachine->GetCurrentTime(); michael@0: } michael@0: } michael@0: mDecoderStateMachine->ClearPositionChangeFlag(); michael@0: } michael@0: } michael@0: michael@0: // Invalidate the frame so any video data is displayed. michael@0: // Do this before the timeupdate event so that if that michael@0: // event runs JavaScript that queries the media size, the michael@0: // frame has reflowed and the size updated beforehand. michael@0: Invalidate(); michael@0: michael@0: if (mOwner && lastTime != mCurrentTime) { michael@0: FireTimeUpdate(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::DurationChanged() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: int64_t oldDuration = mDuration; michael@0: mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1; michael@0: // Duration has changed so we should recompute playback rate michael@0: UpdatePlaybackRate(); michael@0: michael@0: SetInfinite(mDuration == -1); michael@0: michael@0: if (mOwner && oldDuration != mDuration && !IsInfinite()) { michael@0: DECODER_LOG(PR_LOG_DEBUG, "Duration changed to %lld", mDuration); michael@0: mOwner->DispatchEvent(NS_LITERAL_STRING("durationchange")); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SetDuration(double aDuration) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mozilla::IsInfinite(aDuration)) { michael@0: SetInfinite(true); michael@0: } else if (IsNaN(aDuration)) { michael@0: mDuration = -1; michael@0: SetInfinite(true); michael@0: } else { michael@0: mDuration = static_cast(NS_round(aDuration * static_cast(USECS_PER_S))); michael@0: } michael@0: michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetDuration(mDuration); michael@0: } michael@0: michael@0: // Duration has changed so we should recompute playback rate michael@0: UpdatePlaybackRate(); michael@0: } michael@0: michael@0: void MediaDecoder::SetMediaDuration(int64_t aDuration) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(GetStateMachine()); michael@0: GetStateMachine()->SetDuration(aDuration); michael@0: } michael@0: michael@0: void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration) michael@0: { michael@0: if (mPlayState <= PLAY_STATE_LOADING) { michael@0: return; michael@0: } michael@0: NS_ENSURE_TRUE_VOID(GetStateMachine()); michael@0: GetStateMachine()->UpdateEstimatedDuration(aDuration); michael@0: } michael@0: michael@0: void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread()); michael@0: mMediaSeekable = aMediaSeekable; michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetMediaSeekable(aMediaSeekable); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SetTransportSeekable(bool aTransportSeekable) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread()); michael@0: mTransportSeekable = aTransportSeekable; michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetTransportSeekable(aTransportSeekable); michael@0: } michael@0: } michael@0: michael@0: bool MediaDecoder::IsTransportSeekable() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mTransportSeekable; michael@0: } michael@0: michael@0: bool MediaDecoder::IsMediaSeekable() michael@0: { michael@0: NS_ENSURE_TRUE(GetStateMachine(), false); michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread()); michael@0: return mMediaSeekable; michael@0: } michael@0: michael@0: nsresult MediaDecoder::GetSeekable(dom::TimeRanges* aSeekable) michael@0: { michael@0: double initialTime = 0.0; michael@0: michael@0: // We can seek in buffered range if the media is seekable. Also, we can seek michael@0: // in unbuffered ranges if the transport level is seekable (local file or the michael@0: // server supports range requests, etc.) michael@0: if (!IsMediaSeekable()) { michael@0: return NS_OK; michael@0: } else if (!IsTransportSeekable()) { michael@0: return GetBuffered(aSeekable); michael@0: } else { michael@0: double end = IsInfinite() ? std::numeric_limits::infinity() michael@0: : initialTime + GetDuration(); michael@0: aSeekable->Add(initialTime, end); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SetFragmentEndTime(double aTime) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mDecoderStateMachine) { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: mDecoderStateMachine->SetFragmentEndTime(static_cast(aTime * USECS_PER_S)); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SetMediaEndTime(int64_t aTime) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(GetStateMachine()); michael@0: GetStateMachine()->SetMediaEndTime(aTime); michael@0: } michael@0: michael@0: void MediaDecoder::Suspend() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mResource) { michael@0: mResource->Suspend(true); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::Resume(bool aForceBuffering) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mResource) { michael@0: mResource->Resume(); michael@0: } michael@0: if (aForceBuffering) { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->StartBuffering(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::StopProgressUpdates() michael@0: { michael@0: MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread()); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: mIgnoreProgressData = true; michael@0: if (mResource) { michael@0: mResource->SetReadMode(MediaCacheStream::MODE_METADATA); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::StartProgressUpdates() michael@0: { michael@0: MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread()); michael@0: GetReentrantMonitor().AssertCurrentThreadIn(); michael@0: mIgnoreProgressData = false; michael@0: if (mResource) { michael@0: mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK); michael@0: mDecoderPosition = mPlaybackPosition = mResource->Tell(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::MoveLoadsToBackground() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mResource) { michael@0: mResource->MoveLoadsToBackground(); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::UpdatePlaybackOffset(int64_t aOffset) michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: mPlaybackPosition = std::max(aOffset, mPlaybackPosition); michael@0: } michael@0: michael@0: bool MediaDecoder::OnStateMachineThread() const michael@0: { michael@0: return mDecoderStateMachine->OnStateMachineThread(); michael@0: } michael@0: michael@0: void MediaDecoder::SetPlaybackRate(double aPlaybackRate) michael@0: { michael@0: if (aPlaybackRate == 0) { michael@0: mPausedForPlaybackRateNull = true; michael@0: Pause(); michael@0: return; michael@0: } else if (mPausedForPlaybackRateNull) { michael@0: // If the playbackRate is no longer null, restart the playback, iff the michael@0: // media was playing. michael@0: if (mOwner && !mOwner->GetPaused()) { michael@0: Play(); michael@0: } michael@0: mPausedForPlaybackRateNull = false; michael@0: } michael@0: michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetPlaybackRate(aPlaybackRate); michael@0: } else { michael@0: mInitialPlaybackRate = aPlaybackRate; michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::SetPreservesPitch(bool aPreservesPitch) michael@0: { michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->SetPreservesPitch(aPreservesPitch); michael@0: } else { michael@0: mInitialPreservesPitch = aPreservesPitch; michael@0: } michael@0: } michael@0: michael@0: bool MediaDecoder::OnDecodeThread() const { michael@0: NS_WARN_IF_FALSE(mDecoderStateMachine, "mDecoderStateMachine is null"); michael@0: return mDecoderStateMachine ? mDecoderStateMachine->OnDecodeThread() : false; michael@0: } michael@0: michael@0: ReentrantMonitor& MediaDecoder::GetReentrantMonitor() { michael@0: return mReentrantMonitor.GetReentrantMonitor(); michael@0: } michael@0: michael@0: ImageContainer* MediaDecoder::GetImageContainer() michael@0: { michael@0: return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer() : nullptr; michael@0: } michael@0: michael@0: void MediaDecoder::InvalidateWithFlags(uint32_t aFlags) michael@0: { michael@0: if (mVideoFrameContainer) { michael@0: mVideoFrameContainer->InvalidateWithFlags(aFlags); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::Invalidate() michael@0: { michael@0: if (mVideoFrameContainer) { michael@0: mVideoFrameContainer->Invalidate(); michael@0: } michael@0: } michael@0: michael@0: // Constructs the time ranges representing what segments of the media michael@0: // are buffered and playable. michael@0: nsresult MediaDecoder::GetBuffered(dom::TimeRanges* aBuffered) { michael@0: if (mDecoderStateMachine) { michael@0: return mDecoderStateMachine->GetBuffered(aBuffered); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: size_t MediaDecoder::SizeOfVideoQueue() { michael@0: if (mDecoderStateMachine) { michael@0: return mDecoderStateMachine->SizeOfVideoQueue(); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: size_t MediaDecoder::SizeOfAudioQueue() { michael@0: if (mDecoderStateMachine) { michael@0: return mDecoderStateMachine->SizeOfAudioQueue(); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: void MediaDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) { michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset); michael@0: } michael@0: } michael@0: michael@0: void MediaDecoder::UpdatePlaybackPosition(int64_t aTime) michael@0: { michael@0: mDecoderStateMachine->UpdatePlaybackPosition(aTime); michael@0: } michael@0: michael@0: // Provide access to the state machine object michael@0: MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const { michael@0: return mDecoderStateMachine; michael@0: } michael@0: michael@0: void michael@0: MediaDecoder::NotifyWaitingForResourcesStatusChanged() michael@0: { michael@0: ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); michael@0: if (mDecoderStateMachine) { michael@0: mDecoderStateMachine->NotifyWaitingForResourcesStatusChanged(); michael@0: } michael@0: } michael@0: michael@0: bool MediaDecoder::IsShutdown() const { michael@0: NS_ENSURE_TRUE(GetStateMachine(), true); michael@0: return GetStateMachine()->IsShutdown(); michael@0: } michael@0: michael@0: int64_t MediaDecoder::GetEndMediaTime() const { michael@0: NS_ENSURE_TRUE(GetStateMachine(), -1); michael@0: return GetStateMachine()->GetEndMediaTime(); michael@0: } michael@0: michael@0: // Drop reference to state machine. Only called during shutdown dance. michael@0: void MediaDecoder::ReleaseStateMachine() { michael@0: mDecoderStateMachine = nullptr; michael@0: } michael@0: michael@0: MediaDecoderOwner* MediaDecoder::GetMediaOwner() const michael@0: { michael@0: return mOwner; michael@0: } michael@0: michael@0: static void ProgressCallback(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: MediaDecoder* decoder = static_cast(aClosure); michael@0: decoder->Progress(true); michael@0: } michael@0: michael@0: void MediaDecoder::Progress(bool aTimer) michael@0: { michael@0: if (!mOwner) michael@0: return; michael@0: michael@0: TimeStamp now = TimeStamp::Now(); michael@0: michael@0: if (!aTimer) { michael@0: mDataTime = now; michael@0: } michael@0: michael@0: // If PROGRESS_MS has passed since the last progress event fired and more michael@0: // data has arrived since then, fire another progress event. michael@0: if ((mProgressTime.IsNull() || michael@0: now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS)) && michael@0: !mDataTime.IsNull() && michael@0: now - mDataTime <= TimeDuration::FromMilliseconds(PROGRESS_MS)) { michael@0: mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("progress")); michael@0: mProgressTime = now; michael@0: } michael@0: michael@0: if (!mDataTime.IsNull() && michael@0: now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) { michael@0: mOwner->DownloadStalled(); michael@0: // Null it out michael@0: mDataTime = TimeStamp(); michael@0: } michael@0: } michael@0: michael@0: nsresult MediaDecoder::StartProgress() michael@0: { michael@0: if (mProgressTimer) michael@0: return NS_OK; michael@0: michael@0: mProgressTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: return mProgressTimer->InitWithFuncCallback(ProgressCallback, michael@0: this, michael@0: PROGRESS_MS, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: michael@0: nsresult MediaDecoder::StopProgress() michael@0: { michael@0: if (!mProgressTimer) michael@0: return NS_OK; michael@0: michael@0: nsresult rv = mProgressTimer->Cancel(); michael@0: mProgressTimer = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void MediaDecoder::FireTimeUpdate() michael@0: { michael@0: if (!mOwner) michael@0: return; michael@0: mOwner->FireTimeUpdate(true); michael@0: } michael@0: michael@0: void MediaDecoder::PinForSeek() michael@0: { michael@0: MediaResource* resource = GetResource(); michael@0: if (!resource || mPinnedForSeek) { michael@0: return; michael@0: } michael@0: mPinnedForSeek = true; michael@0: resource->Pin(); michael@0: } michael@0: michael@0: void MediaDecoder::UnpinForSeek() michael@0: { michael@0: MediaResource* resource = GetResource(); michael@0: if (!resource || !mPinnedForSeek) { michael@0: return; michael@0: } michael@0: mPinnedForSeek = false; michael@0: resource->Unpin(); michael@0: } michael@0: michael@0: bool MediaDecoder::CanPlayThrough() michael@0: { michael@0: Statistics stats = GetStatistics(); michael@0: if (!stats.mDownloadRateReliable || !stats.mPlaybackRateReliable) { michael@0: return false; michael@0: } michael@0: int64_t bytesToDownload = stats.mTotalBytes - stats.mDownloadPosition; michael@0: int64_t bytesToPlayback = stats.mTotalBytes - stats.mPlaybackPosition; michael@0: double timeToDownload = bytesToDownload / stats.mDownloadRate; michael@0: double timeToPlay = bytesToPlayback / stats.mPlaybackRate; michael@0: michael@0: if (timeToDownload > timeToPlay) { michael@0: // Estimated time to download is greater than the estimated time to play. michael@0: // We probably can't play through without having to stop to buffer. michael@0: return false; michael@0: } michael@0: michael@0: // Estimated time to download is less than the estimated time to play. michael@0: // We can probably play through without having to buffer, but ensure that michael@0: // we've got a reasonable amount of data buffered after the current michael@0: // playback position, so that if the bitrate of the media fluctuates, or if michael@0: // our download rate or decode rate estimation is otherwise inaccurate, michael@0: // we don't suddenly discover that we need to buffer. This is particularly michael@0: // required near the start of the media, when not much data is downloaded. michael@0: int64_t readAheadMargin = michael@0: static_cast(stats.mPlaybackRate * CAN_PLAY_THROUGH_MARGIN); michael@0: return stats.mTotalBytes == stats.mDownloadPosition || michael@0: stats.mDownloadPosition > stats.mPlaybackPosition + readAheadMargin; michael@0: } michael@0: michael@0: #ifdef MOZ_RAW michael@0: bool michael@0: MediaDecoder::IsRawEnabled() michael@0: { michael@0: return Preferences::GetBool("media.raw.enabled"); michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: MediaDecoder::IsOpusEnabled() michael@0: { michael@0: #ifdef MOZ_OPUS michael@0: return Preferences::GetBool("media.opus.enabled"); michael@0: #else michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: MediaDecoder::IsOggEnabled() michael@0: { michael@0: return Preferences::GetBool("media.ogg.enabled"); michael@0: } michael@0: michael@0: #ifdef MOZ_WAVE michael@0: bool michael@0: MediaDecoder::IsWaveEnabled() michael@0: { michael@0: return Preferences::GetBool("media.wave.enabled"); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_WEBM michael@0: bool michael@0: MediaDecoder::IsWebMEnabled() michael@0: { michael@0: return Preferences::GetBool("media.webm.enabled"); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef NECKO_PROTOCOL_rtsp michael@0: bool michael@0: MediaDecoder::IsRtspEnabled() michael@0: { michael@0: //Currently the Rtsp decoded by omx. michael@0: return (Preferences::GetBool("media.rtsp.enabled", false) && IsOmxEnabled()); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_GSTREAMER michael@0: bool michael@0: MediaDecoder::IsGStreamerEnabled() michael@0: { michael@0: return Preferences::GetBool("media.gstreamer.enabled"); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_OMX_DECODER michael@0: bool michael@0: MediaDecoder::IsOmxEnabled() michael@0: { michael@0: return Preferences::GetBool("media.omx.enabled", false); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_MEDIA_PLUGINS michael@0: bool michael@0: MediaDecoder::IsMediaPluginsEnabled() michael@0: { michael@0: return Preferences::GetBool("media.plugins.enabled"); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_WMF michael@0: bool michael@0: MediaDecoder::IsWMFEnabled() michael@0: { michael@0: return WMFDecoder::IsEnabled(); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_APPLEMEDIA michael@0: bool michael@0: MediaDecoder::IsAppleMP3Enabled() michael@0: { michael@0: return Preferences::GetBool("media.apple.mp3.enabled"); michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: MediaMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: int64_t video = 0, audio = 0; michael@0: size_t resources = 0; michael@0: DecodersArray& decoders = Decoders(); michael@0: for (size_t i = 0; i < decoders.Length(); ++i) { michael@0: MediaDecoder* decoder = decoders[i]; michael@0: video += decoder->SizeOfVideoQueue(); michael@0: audio += decoder->SizeOfAudioQueue(); michael@0: michael@0: if (decoder->GetResource()) { michael@0: resources += decoder->GetResource()->SizeOfIncludingThis(MallocSizeOf); michael@0: } michael@0: } michael@0: michael@0: #define REPORT(_path, _amount, _desc) \ michael@0: do { \ michael@0: nsresult rv; \ michael@0: rv = aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \ michael@0: KIND_HEAP, UNITS_BYTES, _amount, \ michael@0: NS_LITERAL_CSTRING(_desc), aData); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: } while (0) michael@0: michael@0: REPORT("explicit/media/decoded/video", video, michael@0: "Memory used by decoded video frames."); michael@0: michael@0: REPORT("explicit/media/decoded/audio", audio, michael@0: "Memory used by decoded audio chunks."); michael@0: michael@0: REPORT("explicit/media/resources", resources, michael@0: "Memory used by media resources including streaming buffers, caches, " michael@0: "etc."); michael@0: michael@0: #undef REPORT michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: MediaDecoderOwner* michael@0: MediaDecoder::GetOwner() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return mOwner; michael@0: } michael@0: michael@0: MediaMemoryTracker::MediaMemoryTracker() michael@0: { michael@0: } michael@0: michael@0: void michael@0: MediaMemoryTracker::InitMemoryReporter() michael@0: { michael@0: RegisterWeakMemoryReporter(this); michael@0: } michael@0: michael@0: MediaMemoryTracker::~MediaMemoryTracker() michael@0: { michael@0: UnregisterWeakMemoryReporter(this); michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: michael@0: // avoid redefined macro in unified build michael@0: #undef DECODER_LOG