gecko/content/media/MediaDecoder.cpp
Thomas Zimmermann 92f7e9a735 Bug 831224: Allow update of the media duration, r=padenot
With MP3 streams, the media duration might change after we received
new data. This patch adds infrastructure to signal the new duration
to the media decoder and state machine. It will also fire an event
of type 'durationchange' to the DOM.
2013-05-03 09:48:37 +02:00

1766 lines
48 KiB
C++

/* -*- 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 "MediaDecoder.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"
#include <limits>
#include "nsNetUtil.h"
#include "AudioStream.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsTArray.h"
#include "VideoUtils.h"
#include "MediaDecoderStateMachine.h"
#include "mozilla/dom/TimeRanges.h"
#include "nsContentUtils.h"
#include "ImageContainer.h"
#include "MediaResource.h"
#include "nsError.h"
#include "mozilla/Preferences.h"
#include <algorithm>
#ifdef MOZ_WMF
#include "WMFDecoder.h"
#endif
using namespace mozilla::layers;
using namespace mozilla::dom;
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
// MediaDecoder::CanPlayThrough() calculation more stable in the case of
// fluctuating bitrates.
static const int64_t CAN_PLAY_THROUGH_MARGIN = 1;
#ifdef PR_LOGGING
PRLogModuleInfo* gMediaDecoderLog;
#define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
#else
#define LOG(type, msg)
#endif
class MediaMemoryReporter
{
MediaMemoryReporter();
~MediaMemoryReporter();
static MediaMemoryReporter* sUniqueInstance;
static MediaMemoryReporter* UniqueInstance() {
if (!sUniqueInstance) {
sUniqueInstance = new MediaMemoryReporter;
}
return sUniqueInstance;
}
typedef nsTArray<MediaDecoder*> DecodersArray;
static DecodersArray& Decoders() {
return UniqueInstance()->mDecoders;
}
DecodersArray mDecoders;
nsCOMPtr<nsIMemoryReporter> mMediaDecodedVideoMemory;
nsCOMPtr<nsIMemoryReporter> mMediaDecodedAudioMemory;
public:
static void AddMediaDecoder(MediaDecoder* aDecoder) {
Decoders().AppendElement(aDecoder);
}
static void RemoveMediaDecoder(MediaDecoder* aDecoder) {
DecodersArray& decoders = Decoders();
decoders.RemoveElement(aDecoder);
if (decoders.IsEmpty()) {
delete sUniqueInstance;
sUniqueInstance = nullptr;
}
}
static int64_t GetDecodedVideoMemory() {
DecodersArray& decoders = Decoders();
int64_t result = 0;
for (size_t i = 0; i < decoders.Length(); ++i) {
result += decoders[i]->VideoQueueMemoryInUse();
}
return result;
}
static int64_t GetDecodedAudioMemory() {
DecodersArray& decoders = Decoders();
int64_t result = 0;
for (size_t i = 0; i < decoders.Length(); ++i) {
result += decoders[i]->AudioQueueMemoryInUse();
}
return result;
}
};
NS_IMPL_ISUPPORTS1(MediaDecoder, nsIObserver)
void MediaDecoder::SetDormantIfNecessary(bool aDormant)
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (!mDecoderStateMachine || !mDecoderStateMachine->IsDormantNeeded() || (mPlayState == PLAY_STATE_SHUTDOWN)) {
return;
}
if (mIsDormant == aDormant) {
// no change to dormant state
return;
}
if(aDormant) {
// enter dormant state
StopProgress();
DestroyDecodedStream();
mDecoderStateMachine->SetDormant(true);
mRequestedSeekTime = mCurrentTime;
if (mPlayState == PLAY_STATE_PLAYING){
mNextState = PLAY_STATE_PLAYING;
} else {
mNextState = PLAY_STATE_PAUSED;
}
mNextState = mPlayState;
mIsDormant = aDormant;
ChangeState(PLAY_STATE_LOADING);
} else if ((aDormant != true) && (mPlayState == PLAY_STATE_LOADING)) {
// exit dormant state
// just trigger to state machine.
mDecoderStateMachine->SetDormant(false);
}
}
void MediaDecoder::Pause()
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if ((mPlayState == PLAY_STATE_LOADING && mIsDormant) || mPlayState == PLAY_STATE_SEEKING || mPlayState == PLAY_STATE_ENDED) {
mNextState = PLAY_STATE_PAUSED;
return;
}
ChangeState(PLAY_STATE_PAUSED);
}
void MediaDecoder::SetVolume(double aVolume)
{
MOZ_ASSERT(NS_IsMainThread());
mInitialVolume = aVolume;
if (mDecoderStateMachine) {
mDecoderStateMachine->SetVolume(aVolume);
}
}
void MediaDecoder::SetAudioCaptured(bool aCaptured)
{
MOZ_ASSERT(NS_IsMainThread());
mInitialAudioCaptured = aCaptured;
if (mDecoderStateMachine) {
mDecoderStateMachine->SetAudioCaptured(aCaptured);
}
}
void MediaDecoder::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);
}
MediaDecoder::DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder,
int64_t aInitialTime,
SourceMediaStream* aStream)
: mLastAudioPacketTime(-1),
mLastAudioPacketEndTime(-1),
mAudioFramesWritten(0),
mInitialTime(aInitialTime),
mNextVideoTime(aInitialTime),
mDecoder(aDecoder),
mStreamInitialized(false),
mHaveSentFinish(false),
mHaveSentFinishAudio(false),
mHaveSentFinishVideo(false),
mStream(aStream),
mHaveBlockedForPlayState(false)
{
mStream->AddMainThreadListener(this);
}
MediaDecoder::DecodedStreamData::~DecodedStreamData()
{
mStream->RemoveMainThreadListener(this);
mStream->Destroy();
}
void
MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
{
mDecoder->NotifyDecodedStreamMainThreadStateChanged();
}
void MediaDecoder::DestroyDecodedStream()
{
MOZ_ASSERT(NS_IsMainThread());
GetReentrantMonitor().AssertCurrentThreadIn();
// All streams are having their SourceMediaStream disconnected, so they
// need to be explicitly blocked again.
for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --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()) {
// Probably the DOM MediaStream was GCed. Clean up.
os.mPort->Destroy();
mOutputStreams.RemoveElementAt(i);
continue;
}
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 MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs)
{
MOZ_ASSERT(NS_IsMainThread());
GetReentrantMonitor().AssertCurrentThreadIn();
LOG(PR_LOG_DEBUG, ("MediaDecoder::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 (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
OutputStreamData& os = mOutputStreams[i];
if (os.mStream->IsDestroyed()) {
// Probably the DOM MediaStream was GCed. Clean up.
// No need to destroy the port; all ports have been destroyed here.
mOutputStreams.RemoveElementAt(i);
continue;
}
ConnectDecodedStreamToOutputStream(&os);
}
mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING;
if (mDecodedStream->mHaveBlockedForPlayState) {
mDecodedStream->mStream->ChangeExplicitBlockerCount(1);
}
}
void MediaDecoder::NotifyDecodedStreamMainThreadStateChanged()
{
if (mTriggerPlaybackEndedWhenSourceStreamFinishes && mDecodedStream &&
mDecodedStream->mStream->IsFinished()) {
mTriggerPlaybackEndedWhenSourceStreamFinishes = false;
if (GetState() == PLAY_STATE_PLAYING) {
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaDecoder::PlaybackEnded);
NS_DispatchToCurrentThread(event);
}
}
}
void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
bool aFinishWhenEnded)
{
MOZ_ASSERT(NS_IsMainThread());
LOG(PR_LOG_DEBUG, ("MediaDecoder::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 MediaDecoder::GetDuration()
{
MOZ_ASSERT(NS_IsMainThread());
if (mInfiniteStream) {
return std::numeric_limits<double>::infinity();
}
if (mDuration >= 0) {
return static_cast<double>(mDuration) / static_cast<double>(USECS_PER_S);
}
return std::numeric_limits<double>::quiet_NaN();
}
int64_t MediaDecoder::GetMediaDuration()
{
NS_ENSURE_TRUE(GetStateMachine(), -1);
return GetStateMachine()->GetDuration();
}
void MediaDecoder::SetInfinite(bool aInfinite)
{
MOZ_ASSERT(NS_IsMainThread());
mInfiniteStream = aInfinite;
}
bool MediaDecoder::IsInfinite()
{
MOZ_ASSERT(NS_IsMainThread());
return mInfiniteStream;
}
MediaDecoder::MediaDecoder() :
mDecoderPosition(0),
mPlaybackPosition(0),
mCurrentTime(0.0),
mInitialVolume(0.0),
mInitialPlaybackRate(1.0),
mInitialPreservesPitch(true),
mRequestedSeekTime(-1.0),
mDuration(-1),
mTransportSeekable(true),
mMediaSeekable(true),
mSameOriginMedia(false),
mReentrantMonitor("media.decoder"),
mIsDormant(false),
mPlayState(PLAY_STATE_PAUSED),
mNextState(PLAY_STATE_PAUSED),
mCalledResourceLoaded(false),
mIgnoreProgressData(false),
mInfiniteStream(false),
mTriggerPlaybackEndedWhenSourceStreamFinishes(false),
mOwner(nullptr),
mFrameBufferLength(0),
mPinnedForSeek(false),
mShuttingDown(false),
mPausedForPlaybackRateNull(false),
mAudioChannelType(AUDIO_CHANNEL_NORMAL)
{
MOZ_COUNT_CTOR(MediaDecoder);
MOZ_ASSERT(NS_IsMainThread());
MediaMemoryReporter::AddMediaDecoder(this);
#ifdef PR_LOGGING
if (!gMediaDecoderLog) {
gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
}
#endif
}
bool MediaDecoder::Init(MediaDecoderOwner* aOwner)
{
MOZ_ASSERT(NS_IsMainThread());
mOwner = aOwner;
mVideoFrameContainer = aOwner->GetVideoFrameContainer();
nsContentUtils::RegisterShutdownObserver(this);
return true;
}
void MediaDecoder::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
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);
StopProgress();
mOwner = nullptr;
nsContentUtils::UnregisterShutdownObserver(this);
}
MediaDecoder::~MediaDecoder()
{
MOZ_ASSERT(NS_IsMainThread());
MediaMemoryReporter::RemoveMediaDecoder(this);
UnpinForSeek();
MOZ_COUNT_DTOR(MediaDecoder);
}
nsresult MediaDecoder::OpenResource(nsIStreamListener** aStreamListener)
{
MOZ_ASSERT(NS_IsMainThread());
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 = mResource->Open(aStreamListener);
if (NS_FAILED(rv)) {
LOG(PR_LOG_DEBUG, ("%p Failed to open stream!", this));
return rv;
}
}
return NS_OK;
}
nsresult MediaDecoder::Load(nsIStreamListener** aStreamListener,
MediaDecoder* aCloneDonor)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = OpenResource(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 MediaDecoder::InitializeStateMachine(MediaDecoder* aCloneDonor)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ASSERTION(mDecoderStateMachine, "Cannot initialize null state machine!");
MediaDecoder* cloneDonor = static_cast<MediaDecoder*>(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->SetTransportSeekable(mTransportSeekable);
mDecoderStateMachine->SetMediaSeekable(mMediaSeekable);
mDecoderStateMachine->SetDuration(mDuration);
mDecoderStateMachine->SetVolume(mInitialVolume);
mDecoderStateMachine->SetAudioCaptured(mInitialAudioCaptured);
SetPlaybackRate(mInitialPlaybackRate);
mDecoderStateMachine->SetPreservesPitch(mInitialPreservesPitch);
if (mFrameBufferLength > 0) {
// The valid mFrameBufferLength value was specified earlier
mDecoderStateMachine->SetFrameBufferLength(mFrameBufferLength);
}
}
ChangeState(PLAY_STATE_LOADING);
return ScheduleStateMachineThread();
}
nsresult MediaDecoder::RequestFrameBufferLength(uint32_t aLength)
{
if (aLength < FRAMEBUFFER_LENGTH_MIN || aLength > FRAMEBUFFER_LENGTH_MAX) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
mFrameBufferLength = aLength;
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mDecoderStateMachine) {
mDecoderStateMachine->SetFrameBufferLength(aLength);
}
return NS_OK;
}
nsresult MediaDecoder::ScheduleStateMachineThread()
{
MOZ_ASSERT(NS_IsMainThread());
NS_ASSERTION(mDecoderStateMachine,
"Must have state machine to start state machine thread");
NS_ENSURE_STATE(mDecoderStateMachine);
if (mShuttingDown)
return NS_OK;
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MediaDecoderStateMachine* m =
static_cast<MediaDecoderStateMachine*>(mDecoderStateMachine.get());
return m->ScheduleStateMachine();
}
nsresult MediaDecoder::Play()
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
nsresult res = ScheduleStateMachineThread();
NS_ENSURE_SUCCESS(res,res);
if ((mPlayState == PLAY_STATE_LOADING && mIsDormant) || 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(TimeRanges& 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 MediaDecoder::Seek(double aTime)
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
NS_ABORT_IF_FALSE(aTime >= 0.0, "Cannot seek to a negative value.");
TimeRanges 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 = Abs(leftBound - aTime);
double distanceRight = Abs(rightBound - aTime);
if (distanceLeft == distanceRight) {
distanceLeft = Abs(leftBound - mCurrentTime);
distanceRight = 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_LOADING || !mIsDormant) && 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();
}
double MediaDecoder::GetCurrentTime()
{
MOZ_ASSERT(NS_IsMainThread());
return mCurrentTime;
}
already_AddRefed<nsIPrincipal> MediaDecoder::GetCurrentPrincipal()
{
MOZ_ASSERT(NS_IsMainThread());
return mResource ? mResource->GetCurrentPrincipal() : nullptr;
}
void MediaDecoder::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<float> frameBuffer(aFrameBuffer);
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown || !mOwner) {
return;
}
mOwner->NotifyAudioAvailable(frameBuffer.forget(), aFrameBufferLength, aTime);
}
void MediaDecoder::QueueMetadata(int64_t aPublishTime,
int aChannels,
int aRate,
bool aHasAudio,
bool aHasVideo,
MetadataTags* aTags)
{
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
GetReentrantMonitor().AssertCurrentThreadIn();
mDecoderStateMachine->QueueMetadata(aPublishTime, aChannels, aRate, aHasAudio, aHasVideo, aTags);
}
bool
MediaDecoder::IsDataCachedToEndOfResource()
{
NS_ASSERTION(!mShuttingDown, "Don't call during shutdown!");
GetReentrantMonitor().AssertCurrentThreadIn();
return (mResource &&
mResource->IsDataCachedToEndOfResource(mDecoderPosition));
}
void MediaDecoder::MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags)
{
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown) {
return;
}
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mPlayState == PLAY_STATE_LOADING && mIsDormant) {
mIsDormant = false;
}
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, aHasVideo, aTags);
}
if (!mCalledResourceLoaded) {
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 notifyResourceIsLoaded = !mCalledResourceLoaded &&
IsDataCachedToEndOfResource();
if (mOwner) {
mOwner->FirstFrameLoaded(notifyResourceIsLoaded);
}
// 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 (notifyResourceIsLoaded) {
ResourceLoaded();
}
// Run NotifySuspendedStatusChanged now to give us a chance to notice
// that autoplay should run.
NotifySuspendedStatusChanged();
}
void MediaDecoder::ResourceLoaded()
{
MOZ_ASSERT(NS_IsMainThread());
// 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 || mCalledResourceLoaded || mPlayState == PLAY_STATE_LOADING)
return;
Progress(false);
mCalledResourceLoaded = true;
StopProgress();
}
// Ensure the final progress event gets fired
if (mOwner) {
mOwner->ResourceLoaded();
}
}
void MediaDecoder::NetworkError()
{
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown)
return;
if (mOwner)
mOwner->NetworkError();
Shutdown();
}
void MediaDecoder::DecodeError()
{
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown)
return;
if (mOwner)
mOwner->DecodeError();
Shutdown();
}
void MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
mSameOriginMedia = aSameOrigin;
}
bool MediaDecoder::IsSameOriginMedia()
{
GetReentrantMonitor().AssertCurrentThreadIn();
return mSameOriginMedia;
}
bool MediaDecoder::IsSeeking() const
{
MOZ_ASSERT(NS_IsMainThread());
return mPlayState == PLAY_STATE_SEEKING;
}
bool MediaDecoder::IsEnded() const
{
MOZ_ASSERT(NS_IsMainThread());
return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN;
}
void MediaDecoder::PlaybackEnded()
{
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown || mPlayState == MediaDecoder::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.mStream->IsDestroyed()) {
// Probably the DOM MediaStream was GCed. Clean up.
os.mPort->Destroy();
mOutputStreams.RemoveElementAt(i);
continue;
}
if (os.mFinishWhenEnded) {
// Shouldn't really be needed since mDecodedStream should already have
// finished, but doesn't hurt.
os.mStream->Finish();
os.mPort->Destroy();
// 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);
UpdateReadyStateForData();
if (mOwner) {
mOwner->PlaybackEnded();
}
// This must be called after |mOwner->PlaybackEnded()| call above, in order
// to fire the required durationchange.
if (IsInfinite()) {
SetInfinite(false);
}
}
NS_IMETHODIMP MediaDecoder::Observe(nsISupports *aSubjet,
const char *aTopic,
const PRUnichar *someData)
{
MOZ_ASSERT(NS_IsMainThread());
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
Shutdown();
}
return NS_OK;
}
MediaDecoder::Statistics
MediaDecoder::GetStatistics()
{
MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread());
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 MediaDecoder::ComputePlaybackRate(bool* aReliable)
{
GetReentrantMonitor().AssertCurrentThreadIn();
MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread());
int64_t length = mResource ? mResource->GetLength() : -1;
if (mDuration >= 0 && length >= 0) {
*aReliable = true;
return length * static_cast<double>(USECS_PER_S) / mDuration;
}
return mPlaybackStatistics.GetRateAtLastStop(aReliable);
}
void MediaDecoder::UpdatePlaybackRate()
{
MOZ_ASSERT(NS_IsMainThread() || OnStateMachineThread());
GetReentrantMonitor().AssertCurrentThreadIn();
if (!mResource)
return;
bool reliable;
uint32_t rate = uint32_t(ComputePlaybackRate(&reliable));
if (reliable) {
// Avoid passing a zero rate
rate = std::max(rate, 1u);
}
else {
// Set a minimum rate of 10,000 bytes per second ... sometimes we just
// don't have good data
rate = std::max(rate, 10000u);
}
mResource->SetPlaybackRate(rate);
}
void MediaDecoder::NotifySuspendedStatusChanged()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mResource)
return;
bool suspended = mResource->IsSuspendedByCache();
if (mOwner) {
mOwner->NotifySuspendedByCache(suspended);
UpdateReadyStateForData();
}
}
void MediaDecoder::NotifyBytesDownloaded()
{
MOZ_ASSERT(NS_IsMainThread());
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
UpdatePlaybackRate();
}
UpdateReadyStateForData();
Progress(false);
}
void MediaDecoder::NotifyDownloadEnded(nsresult aStatus)
{
MOZ_ASSERT(NS_IsMainThread());
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 MediaDecoder::NotifyPrincipalChanged()
{
if (mOwner) {
mOwner->NotifyDecoderPrincipalChanged();
}
}
void MediaDecoder::NotifyBytesConsumed(int64_t aBytes)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
NS_ENSURE_TRUE_VOID(mDecoderStateMachine);
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
if (!mIgnoreProgressData) {
mDecoderPosition += aBytes;
mPlaybackStatistics.AddBytes(aBytes);
}
}
void MediaDecoder::UpdateReadyStateForData()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mOwner || mShuttingDown || !mDecoderStateMachine)
return;
MediaDecoderOwner::NextFrameStatus frameStatus =
mDecoderStateMachine->GetNextFrameStatus();
mOwner->UpdateReadyStateForData(frameStatus);
}
void MediaDecoder::SeekingStopped()
{
MOZ_ASSERT(NS_IsMainThread());
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 MediaDecoder::SeekingStoppedAtEnd()
{
MOZ_ASSERT(NS_IsMainThread());
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 MediaDecoder::SeekingStarted()
{
MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown)
return;
if (mOwner) {
UpdateReadyStateForData();
mOwner->SeekStarted();
}
}
void MediaDecoder::ChangeState(PlayState aState)
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mNextState == aState) {
mNextState = PLAY_STATE_PAUSED;
}
if ((mPlayState == PLAY_STATE_LOADING && mIsDormant && aState != PLAY_STATE_SHUTDOWN) ||
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;
}
}
if (aState!= PLAY_STATE_LOADING) {
mIsDormant = false;
}
GetReentrantMonitor().NotifyAll();
}
void MediaDecoder::PlaybackPositionChanged()
{
MOZ_ASSERT(NS_IsMainThread());
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 MediaDecoder::DurationChanged()
{
MOZ_ASSERT(NS_IsMainThread());
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 MediaDecoder::SetDuration(double aDuration)
{
MOZ_ASSERT(NS_IsMainThread());
if (mozilla::IsInfinite(aDuration)) {
SetInfinite(true);
} else if (IsNaN(aDuration)) {
mDuration = -1;
SetInfinite(true);
} else {
mDuration = static_cast<int64_t>(NS_round(aDuration * static_cast<double>(USECS_PER_S)));
}
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mDecoderStateMachine) {
mDecoderStateMachine->SetDuration(mDuration);
}
// Duration has changed so we should recompute playback rate
UpdatePlaybackRate();
}
void MediaDecoder::SetMediaDuration(int64_t aDuration)
{
NS_ENSURE_TRUE_VOID(GetStateMachine());
GetStateMachine()->SetDuration(aDuration);
}
void MediaDecoder::UpdateMediaDuration(int64_t aDuration)
{
NS_ENSURE_TRUE_VOID(GetStateMachine());
GetStateMachine()->UpdateDuration(aDuration);
}
void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
mMediaSeekable = aMediaSeekable;
if (mDecoderStateMachine) {
mDecoderStateMachine->SetMediaSeekable(aMediaSeekable);
}
}
void MediaDecoder::SetTransportSeekable(bool aTransportSeekable)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
mTransportSeekable = aTransportSeekable;
if (mDecoderStateMachine) {
mDecoderStateMachine->SetTransportSeekable(aTransportSeekable);
}
}
bool MediaDecoder::IsTransportSeekable()
{
MOZ_ASSERT(NS_IsMainThread());
return mTransportSeekable;
}
bool MediaDecoder::IsMediaSeekable()
{
NS_ENSURE_TRUE(GetStateMachine(), false);
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
return mMediaSeekable;
}
nsresult MediaDecoder::GetSeekable(TimeRanges* aSeekable)
{
double initialTime = 0.0;
// We can seek in buffered range if the media is seekable. Also, we can seek
// in unbuffered ranges if the transport level is seekable (local file or the
// server supports range requests, etc.)
if (!IsMediaSeekable()) {
return NS_OK;
} else if (!IsTransportSeekable()) {
return GetBuffered(aSeekable);
} else {
double end = IsInfinite() ? std::numeric_limits<double>::infinity()
: initialTime + GetDuration();
aSeekable->Add(initialTime, end);
return NS_OK;
}
}
void MediaDecoder::SetFragmentEndTime(double aTime)
{
MOZ_ASSERT(NS_IsMainThread());
if (mDecoderStateMachine) {
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
mDecoderStateMachine->SetFragmentEndTime(static_cast<int64_t>(aTime * USECS_PER_S));
}
}
void MediaDecoder::SetMediaEndTime(int64_t aTime)
{
NS_ENSURE_TRUE_VOID(GetStateMachine());
GetStateMachine()->SetMediaEndTime(aTime);
}
void MediaDecoder::Suspend()
{
MOZ_ASSERT(NS_IsMainThread());
if (mResource) {
mResource->Suspend(true);
}
}
void MediaDecoder::Resume(bool aForceBuffering)
{
MOZ_ASSERT(NS_IsMainThread());
if (mResource) {
mResource->Resume();
}
if (aForceBuffering) {
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mDecoderStateMachine) {
mDecoderStateMachine->StartBuffering();
}
}
}
void MediaDecoder::StopProgressUpdates()
{
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
GetReentrantMonitor().AssertCurrentThreadIn();
mIgnoreProgressData = true;
if (mResource) {
mResource->SetReadMode(MediaCacheStream::MODE_METADATA);
}
}
void MediaDecoder::StartProgressUpdates()
{
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
GetReentrantMonitor().AssertCurrentThreadIn();
mIgnoreProgressData = false;
if (mResource) {
mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
mDecoderPosition = mPlaybackPosition = mResource->Tell();
}
}
void MediaDecoder::MoveLoadsToBackground()
{
MOZ_ASSERT(NS_IsMainThread());
if (mResource) {
mResource->MoveLoadsToBackground();
}
}
void MediaDecoder::UpdatePlaybackOffset(int64_t aOffset)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
mPlaybackPosition = std::max(aOffset, mPlaybackPosition);
}
bool MediaDecoder::OnStateMachineThread() const
{
return IsCurrentThread(MediaDecoderStateMachine::GetStateMachineThread());
}
void MediaDecoder::NotifyAudioAvailableListener()
{
MOZ_ASSERT(NS_IsMainThread());
if (mDecoderStateMachine) {
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
mDecoderStateMachine->NotifyAudioAvailableListener();
}
}
void MediaDecoder::SetPlaybackRate(double aPlaybackRate)
{
if (aPlaybackRate == 0) {
mPausedForPlaybackRateNull = true;
Pause();
return;
} else if (mPausedForPlaybackRateNull) {
// If the playbackRate is no longer null, restart the playback, iff the
// media was playing.
if (mOwner && !mOwner->GetPaused()) {
Play();
}
mPausedForPlaybackRateNull = false;
}
if (mDecoderStateMachine) {
mDecoderStateMachine->SetPlaybackRate(aPlaybackRate);
} else {
mInitialPlaybackRate = aPlaybackRate;
}
}
void MediaDecoder::SetPreservesPitch(bool aPreservesPitch)
{
if (mDecoderStateMachine) {
mDecoderStateMachine->SetPreservesPitch(aPreservesPitch);
} else {
mInitialPreservesPitch = aPreservesPitch;
}
}
bool MediaDecoder::OnDecodeThread() const {
NS_WARN_IF_FALSE(mDecoderStateMachine, "mDecoderStateMachine is null");
return mDecoderStateMachine ? mDecoderStateMachine->OnDecodeThread() : false;
}
ReentrantMonitor& MediaDecoder::GetReentrantMonitor() {
return mReentrantMonitor.GetReentrantMonitor();
}
ImageContainer* MediaDecoder::GetImageContainer()
{
return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer() : nullptr;
}
void MediaDecoder::Invalidate()
{
if (mVideoFrameContainer) {
mVideoFrameContainer->Invalidate();
}
}
// Constructs the time ranges representing what segments of the media
// are buffered and playable.
nsresult MediaDecoder::GetBuffered(TimeRanges* aBuffered) {
if (mDecoderStateMachine) {
return mDecoderStateMachine->GetBuffered(aBuffered);
}
return NS_ERROR_FAILURE;
}
int64_t MediaDecoder::VideoQueueMemoryInUse() {
if (mDecoderStateMachine) {
return mDecoderStateMachine->VideoQueueMemoryInUse();
}
return 0;
}
int64_t MediaDecoder::AudioQueueMemoryInUse() {
if (mDecoderStateMachine) {
return mDecoderStateMachine->AudioQueueMemoryInUse();
}
return 0;
}
void MediaDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {
if (mDecoderStateMachine) {
mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
}
}
void MediaDecoder::UpdatePlaybackPosition(int64_t aTime)
{
mDecoderStateMachine->UpdatePlaybackPosition(aTime);
}
// Provide access to the state machine object
MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const {
return mDecoderStateMachine;
}
bool MediaDecoder::IsShutdown() const {
NS_ENSURE_TRUE(GetStateMachine(), true);
return GetStateMachine()->IsShutdown();
}
int64_t MediaDecoder::GetEndMediaTime() const {
NS_ENSURE_TRUE(GetStateMachine(), -1);
return GetStateMachine()->GetEndMediaTime();
}
// Drop reference to state machine. Only called during shutdown dance.
void MediaDecoder::ReleaseStateMachine() {
mDecoderStateMachine = nullptr;
}
MediaDecoderOwner* MediaDecoder::GetMediaOwner() const
{
return mOwner;
}
static void ProgressCallback(nsITimer* aTimer, void* aClosure)
{
MediaDecoder* decoder = static_cast<MediaDecoder*>(aClosure);
decoder->Progress(true);
}
void MediaDecoder::Progress(bool aTimer)
{
if (!mOwner)
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)) {
mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
mProgressTime = now;
}
if (!mDataTime.IsNull() &&
now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
mOwner->DownloadStalled();
// Null it out
mDataTime = TimeStamp();
}
}
nsresult MediaDecoder::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 MediaDecoder::StopProgress()
{
if (!mProgressTimer)
return NS_OK;
nsresult rv = mProgressTimer->Cancel();
mProgressTimer = nullptr;
return rv;
}
void MediaDecoder::FireTimeUpdate()
{
if (!mOwner)
return;
mOwner->FireTimeUpdate(true);
}
void MediaDecoder::PinForSeek()
{
MediaResource* resource = GetResource();
if (!resource || mPinnedForSeek) {
return;
}
mPinnedForSeek = true;
resource->Pin();
}
void MediaDecoder::UnpinForSeek()
{
MediaResource* resource = GetResource();
if (!resource || !mPinnedForSeek) {
return;
}
mPinnedForSeek = false;
resource->Unpin();
}
bool MediaDecoder::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<int64_t>(stats.mPlaybackRate * CAN_PLAY_THROUGH_MARGIN);
return stats.mTotalBytes == stats.mDownloadPosition ||
stats.mDownloadPosition > stats.mPlaybackPosition + readAheadMargin;
}
#ifdef MOZ_RAW
bool
MediaDecoder::IsRawEnabled()
{
return Preferences::GetBool("media.raw.enabled");
}
#endif
#ifdef MOZ_OGG
bool
MediaDecoder::IsOpusEnabled()
{
#ifdef MOZ_OPUS
return Preferences::GetBool("media.opus.enabled");
#else
return false;
#endif
}
bool
MediaDecoder::IsOggEnabled()
{
return Preferences::GetBool("media.ogg.enabled");
}
#endif
#ifdef MOZ_WAVE
bool
MediaDecoder::IsWaveEnabled()
{
return Preferences::GetBool("media.wave.enabled");
}
#endif
#ifdef MOZ_WEBM
bool
MediaDecoder::IsWebMEnabled()
{
return Preferences::GetBool("media.webm.enabled");
}
#endif
#ifdef MOZ_GSTREAMER
bool
MediaDecoder::IsGStreamerEnabled()
{
return Preferences::GetBool("media.gstreamer.enabled");
}
#endif
#ifdef MOZ_OMX_DECODER
bool
MediaDecoder::IsOmxEnabled()
{
return Preferences::GetBool("media.omx.enabled", false);
}
#endif
#ifdef MOZ_MEDIA_PLUGINS
bool
MediaDecoder::IsMediaPluginsEnabled()
{
return Preferences::GetBool("media.plugins.enabled");
}
#endif
#ifdef MOZ_DASH
bool
MediaDecoder::IsDASHEnabled()
{
return Preferences::GetBool("media.dash.enabled");
}
#endif
#ifdef MOZ_WMF
bool
MediaDecoder::IsWMFEnabled()
{
return WMFDecoder::IsEnabled();
}
#endif
MediaDecoderOwner*
MediaDecoder::GetOwner()
{
MOZ_ASSERT(NS_IsMainThread());
return mOwner;
}
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