gecko/content/media/nsBuiltinDecoder.cpp

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