/* -*- 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 "nsMediaDecoder.h" #include "MediaResource.h" #include "nsHTMLMediaElement.h" #include "nsError.h" using namespace mozilla; // Number of milliseconds between progress events as defined by spec static const uint32_t PROGRESS_MS = 350; // Number of milliseconds of no data before a stall event is fired as defined by spec static const uint32_t STALL_MS = 3000; // Number of estimated seconds worth of data we need to have buffered // ahead of the current playback position before we allow the media decoder // to report that it can play through the entire media without the decode // catching up with the download. Having this margin make the // nsMediaDecoder::CanPlayThrough() calculation more stable in the case of // fluctuating bitrates. static const int64_t CAN_PLAY_THROUGH_MARGIN = 10; nsMediaDecoder::nsMediaDecoder() : mElement(nullptr), mFrameBufferLength(0), mPinnedForSeek(false), mShuttingDown(false) { MOZ_COUNT_CTOR(nsMediaDecoder); MediaMemoryReporter::AddMediaDecoder(this); } nsMediaDecoder::~nsMediaDecoder() { MOZ_COUNT_DTOR(nsMediaDecoder); MediaMemoryReporter::RemoveMediaDecoder(this); } bool nsMediaDecoder::Init(nsHTMLMediaElement* aElement) { mElement = aElement; mVideoFrameContainer = aElement->GetVideoFrameContainer(); return true; } void nsMediaDecoder::Shutdown() { StopProgress(); mElement = nullptr; } nsHTMLMediaElement* nsMediaDecoder::GetMediaElement() { return mElement; } nsresult nsMediaDecoder::RequestFrameBufferLength(uint32_t aLength) { if (aLength < FRAMEBUFFER_LENGTH_MIN || aLength > FRAMEBUFFER_LENGTH_MAX) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } mFrameBufferLength = aLength; return NS_OK; } static void ProgressCallback(nsITimer* aTimer, void* aClosure) { nsMediaDecoder* decoder = static_cast(aClosure); decoder->Progress(true); } void nsMediaDecoder::Progress(bool aTimer) { if (!mElement) return; TimeStamp now = TimeStamp::Now(); if (!aTimer) { mDataTime = now; } // If PROGRESS_MS has passed since the last progress event fired and more // data has arrived since then, fire another progress event. if ((mProgressTime.IsNull() || now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS)) && !mDataTime.IsNull() && now - mDataTime <= TimeDuration::FromMilliseconds(PROGRESS_MS)) { mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress")); mProgressTime = now; } if (!mDataTime.IsNull() && now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) { mElement->DownloadStalled(); // Null it out mDataTime = TimeStamp(); } } nsresult nsMediaDecoder::StartProgress() { if (mProgressTimer) return NS_OK; mProgressTimer = do_CreateInstance("@mozilla.org/timer;1"); return mProgressTimer->InitWithFuncCallback(ProgressCallback, this, PROGRESS_MS, nsITimer::TYPE_REPEATING_SLACK); } nsresult nsMediaDecoder::StopProgress() { if (!mProgressTimer) return NS_OK; nsresult rv = mProgressTimer->Cancel(); mProgressTimer = nullptr; return rv; } void nsMediaDecoder::FireTimeUpdate() { if (!mElement) return; mElement->FireTimeUpdate(true); } void nsMediaDecoder::PinForSeek() { MediaResource* resource = GetResource(); if (!resource || mPinnedForSeek) { return; } mPinnedForSeek = true; resource->Pin(); } void nsMediaDecoder::UnpinForSeek() { MediaResource* resource = GetResource(); if (!resource || !mPinnedForSeek) { return; } mPinnedForSeek = false; resource->Unpin(); } bool nsMediaDecoder::CanPlayThrough() { Statistics stats = GetStatistics(); if (!stats.mDownloadRateReliable || !stats.mPlaybackRateReliable) { return false; } int64_t bytesToDownload = stats.mTotalBytes - stats.mDownloadPosition; int64_t bytesToPlayback = stats.mTotalBytes - stats.mPlaybackPosition; double timeToDownload = bytesToDownload / stats.mDownloadRate; double timeToPlay = bytesToPlayback / stats.mPlaybackRate; if (timeToDownload > timeToPlay) { // Estimated time to download is greater than the estimated time to play. // We probably can't play through without having to stop to buffer. return false; } // Estimated time to download is less than the estimated time to play. // We can probably play through without having to buffer, but ensure that // we've got a reasonable amount of data buffered after the current // playback position, so that if the bitrate of the media fluctuates, or if // our download rate or decode rate estimation is otherwise inaccurate, // we don't suddenly discover that we need to buffer. This is particularly // required near the start of the media, when not much data is downloaded. int64_t readAheadMargin = static_cast(stats.mPlaybackRate * CAN_PLAY_THROUGH_MARGIN); return stats.mTotalBytes == stats.mDownloadPosition || stats.mDownloadPosition > stats.mPlaybackPosition + readAheadMargin; } namespace mozilla { MediaMemoryReporter* MediaMemoryReporter::sUniqueInstance; NS_MEMORY_REPORTER_IMPLEMENT(MediaDecodedVideoMemory, "explicit/media/decoded-video", KIND_HEAP, UNITS_BYTES, MediaMemoryReporter::GetDecodedVideoMemory, "Memory used by decoded video frames.") NS_MEMORY_REPORTER_IMPLEMENT(MediaDecodedAudioMemory, "explicit/media/decoded-audio", KIND_HEAP, UNITS_BYTES, MediaMemoryReporter::GetDecodedAudioMemory, "Memory used by decoded audio chunks.") MediaMemoryReporter::MediaMemoryReporter() : mMediaDecodedVideoMemory(new NS_MEMORY_REPORTER_NAME(MediaDecodedVideoMemory)) , mMediaDecodedAudioMemory(new NS_MEMORY_REPORTER_NAME(MediaDecodedAudioMemory)) { NS_RegisterMemoryReporter(mMediaDecodedVideoMemory); NS_RegisterMemoryReporter(mMediaDecodedAudioMemory); } MediaMemoryReporter::~MediaMemoryReporter() { NS_UnregisterMemoryReporter(mMediaDecodedVideoMemory); NS_UnregisterMemoryReporter(mMediaDecodedAudioMemory); } } // namespace mozilla