gecko/content/media/nsBuiltinDecoder.cpp
Ralph Giles f63a794f44 Bug 763010 - Expose media element metadata. r=cpearce
Implements a media.mozGetMetadata() method returning a new javascript object whose properties are key value pairs respresenting metadata tags from the media resource. This data is available after readystate enters METADATA_LOADED.

Currently this is only implemented for Ogg Vorbis streams.

Media format metadata is parsed out by the media decoders. In the nsCodecStateMachine::ReadMetadata subclasses we fill in an nsDataHashtable pointer using the format-specifc api.

The hash pointer is passed up to the media element as part of the MetadataLoaded event.

The hash is deleted if the load is aborted. The audio metadata is also reset to zero (as in the constructor), resolving a todo comment.
2012-07-30 20:14:29 -04:00

1050 lines
30 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"
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(mReentrantMonitor);
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::AddOutputStream(SourceMediaStream* aStream, bool aFinishWhenEnded)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
OutputMediaStream* ms = mOutputStreams.AppendElement();
ms->Init(PRInt64(mCurrentTime*USECS_PER_S), aStream, aFinishWhenEnded);
}
// 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)
{
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;
// 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::Load(MediaResource* aResource,
nsIStreamListener** aStreamListener,
nsMediaDecoder* aCloneDonor)
{
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(mReentrantMonitor);
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;
}
mDecoderStateMachine = CreateStateMachine();
if (!mDecoderStateMachine) {
LOG(PR_LOG_DEBUG, ("%p Failed to create state machine!", this));
return NS_ERROR_FAILURE;
}
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(mReentrantMonitor);
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(PRUint32 aLength)
{
nsresult res = nsMediaDecoder::RequestFrameBufferLength(aLength);
NS_ENSURE_SUCCESS(res,res);
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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(mReentrantMonitor);
nsBuiltinDecoderStateMachine* m =
static_cast<nsBuiltinDecoderStateMachine*>(mDecoderStateMachine.get());
return m->ScheduleStateMachine();
}
nsresult nsBuiltinDecoder::Play()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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, PRInt32& aIntervalIndex) {
PRUint32 length;
aRanges.GetLength(&length);
for (PRUint32 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(mReentrantMonitor);
NS_ABORT_IF_FALSE(aTime >= 0.0, "Cannot seek to a negative value.");
nsTimeRanges seekable;
nsresult res;
PRUint32 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
PRInt32 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 (PRUint32(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,
PRUint32 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(PRUint32 aChannels,
PRUint32 aRate,
bool aHasAudio,
const nsHTMLMediaElement::MetadataTags* aTags)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mShuttingDown) {
return;
}
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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(mReentrantMonitor);
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(mReentrantMonitor);
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()
{
if (mShuttingDown || mPlayState == nsBuiltinDecoder::PLAY_STATE_SEEKING)
return;
printf("nsBuiltinDecoder::PlaybackEnded mPlayState=%d\n", mPlayState);
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(mReentrantMonitor);
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.");
PRInt64 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;
PRUint32 rate = PRUint32(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(mReentrantMonitor);
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(PRInt64 aBytes)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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(mReentrantMonitor);
// 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::SeekingStopped, next state=%d\n", mNextState);
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(mReentrantMonitor);
// 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(mReentrantMonitor);
if (mNextState == aState) {
mNextState = PLAY_STATE_PAUSED;
}
if (mPlayState == PLAY_STATE_SHUTDOWN) {
mReentrantMonitor.NotifyAll();
return;
}
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;
}
}
mReentrantMonitor.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(mReentrantMonitor);
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(mReentrantMonitor);
PRInt64 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<PRInt64>(NS_round(aDuration * static_cast<double>(USECS_PER_S)));
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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(mReentrantMonitor);
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(mReentrantMonitor);
mDecoderStateMachine->SetFragmentEndTime(static_cast<PRInt64>(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(mReentrantMonitor);
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(PRInt64 aOffset)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
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(mReentrantMonitor);
mDecoderStateMachine->NotifyAudioAvailableListener();
}
}