summaryrefslogtreecommitdiffstats
path: root/dom/media/MediaDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaDecoder.cpp')
-rw-r--r--dom/media/MediaDecoder.cpp1803
1 files changed, 1803 insertions, 0 deletions
diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp
new file mode 100644
index 000000000..ab39886ba
--- /dev/null
+++ b/dom/media/MediaDecoder.cpp
@@ -0,0 +1,1803 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsIObserver.h"
+#include "nsTArray.h"
+#include "VideoUtils.h"
+#include "MediaDecoderStateMachine.h"
+#include "ImageContainer.h"
+#include "MediaResource.h"
+#include "VideoFrameContainer.h"
+#include "nsError.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIMemoryReporter.h"
+#include "nsComponentManagerUtils.h"
+#include <algorithm>
+#include "MediaShutdownManager.h"
+#include "AudioChannelService.h"
+#include "mozilla/dom/AudioTrack.h"
+#include "mozilla/dom/AudioTrackList.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/VideoTrack.h"
+#include "mozilla/dom/VideoTrackList.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Telemetry.h"
+#include "GMPService.h"
+#include "Layers.h"
+#include "mozilla/layers/ShadowLayers.h"
+
+#ifdef MOZ_ANDROID_OMX
+#include "AndroidBridge.h"
+#endif
+
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::media;
+
+namespace mozilla {
+
+// The amount of instability we tollerate in calls to
+// MediaDecoder::UpdateEstimatedMediaDuration(); changes of duration
+// less than this are ignored, as they're assumed to be the result of
+// instability in the duration estimation.
+static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
+
+// avoid redefined macro in unified build
+#undef DECODER_LOG
+#undef DUMP_LOG
+
+LazyLogModule gMediaDecoderLog("MediaDecoder");
+#define DECODER_LOG(x, ...) \
+ MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, this, ##__VA_ARGS__))
+
+#define DUMP_LOG(x, ...) \
+ NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString("Decoder=%p " x, this, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
+
+static const char*
+ToPlayStateStr(MediaDecoder::PlayState aState)
+{
+ switch (aState) {
+ case MediaDecoder::PLAY_STATE_START: return "START";
+ case MediaDecoder::PLAY_STATE_LOADING: return "LOADING";
+ case MediaDecoder::PLAY_STATE_PAUSED: return "PAUSED";
+ case MediaDecoder::PLAY_STATE_PLAYING: return "PLAYING";
+ case MediaDecoder::PLAY_STATE_ENDED: return "ENDED";
+ case MediaDecoder::PLAY_STATE_SHUTDOWN: return "SHUTDOWN";
+ default: MOZ_ASSERT_UNREACHABLE("Invalid playState.");
+ }
+ return "UNKNOWN";
+}
+
+class MediaMemoryTracker : public nsIMemoryReporter
+{
+ virtual ~MediaMemoryTracker();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
+
+ MediaMemoryTracker();
+ void InitMemoryReporter();
+
+ static StaticRefPtr<MediaMemoryTracker> sUniqueInstance;
+
+ static MediaMemoryTracker* UniqueInstance() {
+ if (!sUniqueInstance) {
+ sUniqueInstance = new MediaMemoryTracker();
+ sUniqueInstance->InitMemoryReporter();
+ }
+ return sUniqueInstance;
+ }
+
+ typedef nsTArray<MediaDecoder*> DecodersArray;
+ static DecodersArray& Decoders() {
+ return UniqueInstance()->mDecoders;
+ }
+
+ DecodersArray mDecoders;
+
+public:
+ static void AddMediaDecoder(MediaDecoder* aDecoder)
+ {
+ Decoders().AppendElement(aDecoder);
+ }
+
+ static void RemoveMediaDecoder(MediaDecoder* aDecoder)
+ {
+ DecodersArray& decoders = Decoders();
+ decoders.RemoveElement(aDecoder);
+ if (decoders.IsEmpty()) {
+ sUniqueInstance = nullptr;
+ }
+ }
+};
+
+StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
+
+#if defined(PR_LOGGING)
+LazyLogModule gMediaTimerLog("MediaTimer");
+LazyLogModule gMediaSampleLog("MediaSample");
+#endif
+
+void
+MediaDecoder::InitStatics()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // Eagerly init gMediaDecoderLog to work around bug 1415441.
+ MOZ_LOG(gMediaDecoderLog, LogLevel::Info, ("MediaDecoder::InitStatics"));
+}
+
+NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter)
+
+NS_IMPL_ISUPPORTS0(MediaDecoder)
+
+
+void
+MediaDecoder::ResourceCallback::Connect(MediaDecoder* aDecoder)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mDecoder = aDecoder;
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+}
+
+void
+MediaDecoder::ResourceCallback::Disconnect()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mDecoder = nullptr;
+ mTimer->Cancel();
+ mTimer = nullptr;
+}
+
+MediaDecoderOwner*
+MediaDecoder::ResourceCallback::GetMediaOwner() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDecoder ? mDecoder->mOwner : nullptr;
+}
+
+void
+MediaDecoder::ResourceCallback::SetInfinite(bool aInfinite)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoder) {
+ mDecoder->SetInfinite(aInfinite);
+ }
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyNetworkError()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoder) {
+ mDecoder->NetworkError();
+ }
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyDecodeError()
+{
+ RefPtr<ResourceCallback> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ if (self->mDecoder) {
+ self->mDecoder->DecodeError(NS_ERROR_DOM_MEDIA_FATAL_ERR);
+ }
+ });
+ AbstractThread::MainThread()->Dispatch(r.forget());
+}
+
+/* static */ void
+MediaDecoder::ResourceCallback::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ ResourceCallback* thiz = static_cast<ResourceCallback*>(aClosure);
+ MOZ_ASSERT(thiz->mDecoder);
+ thiz->mDecoder->NotifyDataArrived();
+ thiz->mTimerArmed = false;
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyDataArrived()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mDecoder || mTimerArmed) {
+ return;
+ }
+ // In situations where these notifications come from stochastic network
+ // activity, we can save significant computation by throttling the
+ // calls to MediaDecoder::NotifyDataArrived() which will update the buffer
+ // ranges of the reader.
+ mTimerArmed = true;
+ mTimer->InitWithFuncCallback(
+ TimerCallback, this, sDelay, nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyBytesDownloaded()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoder) {
+ mDecoder->NotifyBytesDownloaded();
+ }
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyDataEnded(nsresult aStatus)
+{
+ RefPtr<ResourceCallback> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ if (!self->mDecoder) {
+ return;
+ }
+ self->mDecoder->NotifyDownloadEnded(aStatus);
+ if (NS_SUCCEEDED(aStatus)) {
+ HTMLMediaElement* element = self->GetMediaOwner()->GetMediaElement();
+ if (element) {
+ element->DownloadSuspended();
+ }
+ // NotifySuspendedStatusChanged will tell the element that download
+ // has been suspended "by the cache", which is true since we never
+ // download anything. The element can then transition to HAVE_ENOUGH_DATA.
+ self->mDecoder->NotifySuspendedStatusChanged();
+ }
+ });
+ AbstractThread::MainThread()->Dispatch(r.forget());
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyPrincipalChanged()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoder) {
+ mDecoder->NotifyPrincipalChanged();
+ }
+}
+
+void
+MediaDecoder::ResourceCallback::NotifySuspendedStatusChanged()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoder) {
+ mDecoder->NotifySuspendedStatusChanged();
+ }
+}
+
+void
+MediaDecoder::ResourceCallback::NotifyBytesConsumed(int64_t aBytes,
+ int64_t aOffset)
+{
+ RefPtr<ResourceCallback> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ if (self->mDecoder) {
+ self->mDecoder->NotifyBytesConsumed(aBytes, aOffset);
+ }
+ });
+ AbstractThread::MainThread()->Dispatch(r.forget());
+}
+
+void
+MediaDecoder::NotifyOwnerActivityChanged(bool aIsVisible)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ SetElementVisibility(aIsVisible);
+
+ MediaDecoderOwner* owner = GetOwner();
+ NS_ENSURE_TRUE_VOID(owner);
+
+ dom::HTMLMediaElement* element = owner->GetMediaElement();
+ NS_ENSURE_TRUE_VOID(element);
+
+ RefPtr<LayerManager> layerManager =
+ nsContentUtils::LayerManagerForDocument(element->OwnerDoc());
+ NS_ENSURE_TRUE_VOID(layerManager);
+
+ RefPtr<KnowsCompositor> knowsCompositor = layerManager->AsShadowForwarder();
+ mCompositorUpdatedEvent.Notify(knowsCompositor);
+}
+
+void
+MediaDecoder::Pause()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ if (mPlayState == PLAY_STATE_LOADING || IsEnded()) {
+ mNextState = PLAY_STATE_PAUSED;
+ return;
+ }
+ ChangeState(PLAY_STATE_PAUSED);
+}
+
+void
+MediaDecoder::SetVolume(double aVolume)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mVolume = aVolume;
+}
+
+void
+MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
+ bool aFinishWhenEnded)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
+ mDecoderStateMachine->AddOutputStream(aStream, aFinishWhenEnded);
+}
+
+void
+MediaDecoder::RemoveOutputStream(MediaStream* aStream)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
+ mDecoderStateMachine->RemoveOutputStream(aStream);
+}
+
+double
+MediaDecoder::GetDuration()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDuration;
+}
+
+AbstractCanonical<media::NullableTimeUnit>*
+MediaDecoder::CanonicalDurationOrNull()
+{
+ MOZ_ASSERT(mDecoderStateMachine);
+ return mDecoderStateMachine->CanonicalDuration();
+}
+
+void
+MediaDecoder::SetInfinite(bool aInfinite)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mInfiniteStream = aInfinite;
+ DurationChanged();
+}
+
+bool
+MediaDecoder::IsInfinite() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mInfiniteStream;
+}
+
+#define INIT_MIRROR(name, val) \
+ name(AbstractThread::MainThread(), val, "MediaDecoder::" #name " (Mirror)")
+#define INIT_CANONICAL(name, val) \
+ name(AbstractThread::MainThread(), val, "MediaDecoder::" #name " (Canonical)")
+
+MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner)
+ : mWatchManager(this, AbstractThread::MainThread())
+ , mLogicalPosition(0.0)
+ , mDuration(std::numeric_limits<double>::quiet_NaN())
+ , mResourceCallback(new ResourceCallback())
+ , mCDMProxyPromise(mCDMProxyPromiseHolder.Ensure(__func__))
+ , mIgnoreProgressData(false)
+ , mInfiniteStream(false)
+ , mOwner(aOwner)
+ , mFrameStats(new FrameStatistics())
+ , mVideoFrameContainer(aOwner->GetVideoFrameContainer())
+ , mPlaybackStatistics(new MediaChannelStatistics())
+ , mPinnedForSeek(false)
+ , mMinimizePreroll(false)
+ , mMediaTracksConstructed(false)
+ , mFiredMetadataLoaded(false)
+ , mElementVisible(!aOwner->IsHidden())
+ , mForcedHidden(false)
+ , INIT_MIRROR(mStateMachineIsShutdown, true)
+ , INIT_MIRROR(mBuffered, TimeIntervals())
+ , INIT_MIRROR(mNextFrameStatus, MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE)
+ , INIT_MIRROR(mCurrentPosition, 0)
+ , INIT_MIRROR(mStateMachineDuration, NullableTimeUnit())
+ , INIT_MIRROR(mPlaybackPosition, 0)
+ , INIT_MIRROR(mIsAudioDataAudible, false)
+ , INIT_CANONICAL(mVolume, 0.0)
+ , INIT_CANONICAL(mPreservesPitch, true)
+ , INIT_CANONICAL(mEstimatedDuration, NullableTimeUnit())
+ , INIT_CANONICAL(mExplicitDuration, Maybe<double>())
+ , INIT_CANONICAL(mPlayState, PLAY_STATE_LOADING)
+ , INIT_CANONICAL(mNextState, PLAY_STATE_PAUSED)
+ , INIT_CANONICAL(mLogicallySeeking, false)
+ , INIT_CANONICAL(mSameOriginMedia, false)
+ , INIT_CANONICAL(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE)
+ , INIT_CANONICAL(mPlaybackBytesPerSecond, 0.0)
+ , INIT_CANONICAL(mPlaybackRateReliable, true)
+ , INIT_CANONICAL(mDecoderPosition, 0)
+ , INIT_CANONICAL(mIsVisible, !aOwner->IsHidden())
+ , mTelemetryReported(false)
+{
+ MOZ_COUNT_CTOR(MediaDecoder);
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaMemoryTracker::AddMediaDecoder(this);
+
+ mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
+ mResourceCallback->Connect(this);
+
+ //
+ // Initialize watchers.
+ //
+
+ // mDuration
+ mWatchManager.Watch(mStateMachineDuration, &MediaDecoder::DurationChanged);
+
+ // mStateMachineIsShutdown
+ mWatchManager.Watch(mStateMachineIsShutdown, &MediaDecoder::ShutdownBitChanged);
+
+ // readyState
+ mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateReadyState);
+ mWatchManager.Watch(mNextFrameStatus, &MediaDecoder::UpdateReadyState);
+ // ReadyState computation depends on MediaDecoder::CanPlayThrough, which
+ // depends on the download rate.
+ mWatchManager.Watch(mBuffered, &MediaDecoder::UpdateReadyState);
+
+ // mLogicalPosition
+ mWatchManager.Watch(mCurrentPosition, &MediaDecoder::UpdateLogicalPosition);
+ mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateLogicalPosition);
+ mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::UpdateLogicalPosition);
+
+ // mIgnoreProgressData
+ mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::SeekingChanged);
+
+ mWatchManager.Watch(mIsAudioDataAudible, &MediaDecoder::NotifyAudibleStateChanged);
+
+ MediaShutdownManager::InitStatics();
+}
+
+#undef INIT_MIRROR
+#undef INIT_CANONICAL
+
+void
+MediaDecoder::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ // Unwatch all watch targets to prevent further notifications.
+ mWatchManager.Shutdown();
+
+ mResourceCallback->Disconnect();
+
+ mCDMProxyPromiseHolder.RejectIfExists(true, __func__);
+
+ DiscardOngoingSeekIfExists();
+
+ // 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) {
+ mTimedMetadataListener.Disconnect();
+ mMetadataLoadedListener.Disconnect();
+ mFirstFrameLoadedListener.Disconnect();
+ mOnPlaybackEvent.Disconnect();
+ mOnPlaybackErrorEvent.Disconnect();
+ mOnDecoderDoctorEvent.Disconnect();
+ mOnMediaNotSeekable.Disconnect();
+
+ mDecoderStateMachine->BeginShutdown()
+ ->Then(AbstractThread::MainThread(), __func__, this,
+ &MediaDecoder::FinishShutdown,
+ &MediaDecoder::FinishShutdown);
+ } else {
+ // Ensure we always unregister asynchronously in order not to disrupt
+ // the hashtable iterating in MediaShutdownManager::Shutdown().
+ RefPtr<MediaDecoder> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () {
+ self->mVideoFrameContainer = nullptr;
+ MediaShutdownManager::Instance().Unregister(self);
+ });
+ AbstractThread::MainThread()->Dispatch(r.forget());
+ }
+
+ // Force any outstanding seek and byterange requests to complete
+ // to prevent shutdown from deadlocking.
+ if (mResource) {
+ mResource->Close();
+ }
+
+ ChangeState(PLAY_STATE_SHUTDOWN);
+ mOwner = nullptr;
+}
+
+void
+MediaDecoder::NotifyXPCOMShutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (auto owner = GetOwner()) {
+ owner->NotifyXPCOMShutdown();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(IsShutdown());
+
+ // Don't cause grief to release builds by ensuring Shutdown()
+ // is always called during shutdown phase.
+ if (!IsShutdown()) {
+ Shutdown();
+ }
+}
+
+MediaDecoder::~MediaDecoder()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaMemoryTracker::RemoveMediaDecoder(this);
+ UnpinForSeek();
+ MOZ_COUNT_DTOR(MediaDecoder);
+}
+
+void
+MediaDecoder::OnPlaybackEvent(MediaEventType aEvent)
+{
+ switch (aEvent) {
+ case MediaEventType::PlaybackStarted:
+ mPlaybackStatistics->Start();
+ break;
+ case MediaEventType::PlaybackStopped:
+ mPlaybackStatistics->Stop();
+ ComputePlaybackRate();
+ break;
+ case MediaEventType::PlaybackEnded:
+ PlaybackEnded();
+ break;
+ case MediaEventType::SeekStarted:
+ SeekingStarted();
+ break;
+ case MediaEventType::Invalidate:
+ Invalidate();
+ break;
+ case MediaEventType::EnterVideoSuspend:
+ mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("mozentervideosuspend"));
+ break;
+ case MediaEventType::ExitVideoSuspend:
+ mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("mozexitvideosuspend"));
+ break;
+ }
+}
+
+void
+MediaDecoder::OnPlaybackErrorEvent(const MediaResult& aError)
+{
+ DecodeError(aError);
+}
+
+void
+MediaDecoder::OnDecoderDoctorEvent(DecoderDoctorEvent aEvent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // OnDecoderDoctorEvent is disconnected at shutdown time.
+ MOZ_ASSERT(!IsShutdown());
+ HTMLMediaElement* element = mOwner->GetMediaElement();
+ if (!element) {
+ return;
+ }
+ nsIDocument* doc = element->OwnerDoc();
+ if (!doc) {
+ return;
+ }
+ DecoderDoctorDiagnostics diags;
+ diags.StoreEvent(doc, aEvent, __func__);
+}
+
+void
+MediaDecoder::FinishShutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mDecoderStateMachine->BreakCycles();
+ SetStateMachine(nullptr);
+ mVideoFrameContainer = nullptr;
+ MediaShutdownManager::Instance().Unregister(this);
+}
+
+MediaResourceCallback*
+MediaDecoder::GetResourceCallback() const
+{
+ return mResourceCallback;
+}
+
+nsresult
+MediaDecoder::OpenResource(nsIStreamListener** aStreamListener)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aStreamListener) {
+ *aStreamListener = nullptr;
+ }
+ return mResource->Open(aStreamListener);
+}
+
+nsresult
+MediaDecoder::Load(nsIStreamListener** aStreamListener)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mResource, "Can't load without a MediaResource");
+
+ nsresult rv = MediaShutdownManager::Instance().Register(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = OpenResource(aStreamListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetStateMachine(CreateStateMachine());
+ NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE);
+
+ return InitializeStateMachine();
+}
+
+nsresult
+MediaDecoder::InitializeStateMachine()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ASSERTION(mDecoderStateMachine, "Cannot initialize null state machine!");
+
+ nsresult rv = mDecoderStateMachine->Init(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If some parameters got set before the state machine got created,
+ // set them now
+ SetStateMachineParameters();
+
+ return NS_OK;
+}
+
+void
+MediaDecoder::SetStateMachineParameters()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mMinimizePreroll) {
+ mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
+ }
+ if (mPlaybackRate != 1 && mPlaybackRate != 0) {
+ mDecoderStateMachine->DispatchSetPlaybackRate(mPlaybackRate);
+ }
+ mTimedMetadataListener = mDecoderStateMachine->TimedMetadataEvent().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::OnMetadataUpdate);
+ mMetadataLoadedListener = mDecoderStateMachine->MetadataLoadedEvent().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::MetadataLoaded);
+ mFirstFrameLoadedListener = mDecoderStateMachine->FirstFrameLoadedEvent().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::FirstFrameLoaded);
+
+ mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::OnPlaybackEvent);
+ mOnPlaybackErrorEvent = mDecoderStateMachine->OnPlaybackErrorEvent().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::OnPlaybackErrorEvent);
+ mOnDecoderDoctorEvent = mDecoderStateMachine->OnDecoderDoctorEvent().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::OnDecoderDoctorEvent);
+ mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect(
+ AbstractThread::MainThread(), this, &MediaDecoder::OnMediaNotSeekable);
+}
+
+void
+MediaDecoder::SetMinimizePrerollUntilPlaybackStarts()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()");
+ mMinimizePreroll = true;
+
+ // This needs to be called before we init the state machine, otherwise it will
+ // have no effect.
+ MOZ_DIAGNOSTIC_ASSERT(!mDecoderStateMachine);
+}
+
+nsresult
+MediaDecoder::Play()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
+ if (mPlaybackRate == 0) {
+ return NS_OK;
+ }
+
+ if (IsEnded()) {
+ return Seek(0, SeekTarget::PrevSyncPoint);
+ } else if (mPlayState == PLAY_STATE_LOADING) {
+ mNextState = PLAY_STATE_PLAYING;
+ return NS_OK;
+ }
+
+ ChangeState(PLAY_STATE_PLAYING);
+ return NS_OK;
+}
+
+nsresult
+MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType, dom::Promise* aPromise /*=nullptr*/)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ MOZ_ASSERT(aTime >= 0.0, "Cannot seek to a negative value.");
+
+ int64_t timeUsecs = TimeUnit::FromSeconds(aTime).ToMicroseconds();
+
+ mLogicalPosition = aTime;
+
+ mLogicallySeeking = true;
+ SeekTarget target = SeekTarget(timeUsecs, aSeekType);
+ CallSeek(target, aPromise);
+
+ if (mPlayState == PLAY_STATE_ENDED) {
+ PinForSeek();
+ ChangeState(mOwner->GetPaused() ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING);
+ }
+ return NS_OK;
+}
+
+void
+MediaDecoder::AsyncResolveSeekDOMPromiseIfExists()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mSeekDOMPromise) {
+ RefPtr<dom::Promise> promise = mSeekDOMPromise;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ promise->MaybeResolveWithUndefined();
+ });
+ AbstractThread::MainThread()->Dispatch(r.forget());
+ mSeekDOMPromise = nullptr;
+ }
+}
+
+void
+MediaDecoder::AsyncRejectSeekDOMPromiseIfExists()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mSeekDOMPromise) {
+ RefPtr<dom::Promise> promise = mSeekDOMPromise;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ });
+ AbstractThread::MainThread()->Dispatch(r.forget());
+ mSeekDOMPromise = nullptr;
+ }
+}
+
+void
+MediaDecoder::DiscardOngoingSeekIfExists()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mSeekRequest.DisconnectIfExists();
+ AsyncRejectSeekDOMPromiseIfExists();
+}
+
+void
+MediaDecoder::CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ DiscardOngoingSeekIfExists();
+
+ mSeekDOMPromise = aPromise;
+ mSeekRequest.Begin(
+ mDecoderStateMachine->InvokeSeek(aTarget)
+ ->Then(AbstractThread::MainThread(), __func__, this,
+ &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
+}
+
+double
+MediaDecoder::GetCurrentTime()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mLogicalPosition;
+}
+
+already_AddRefed<nsIPrincipal>
+MediaDecoder::GetCurrentPrincipal()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mResource ? mResource->GetCurrentPrincipal() : nullptr;
+}
+
+void
+MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RemoveMediaTracks();
+ MetadataLoaded(nsAutoPtr<MediaInfo>(new MediaInfo(*aMetadata.mInfo)),
+ Move(aMetadata.mTags),
+ MediaDecoderEventVisibility::Observable);
+ FirstFrameLoaded(Move(aMetadata.mInfo),
+ MediaDecoderEventVisibility::Observable);
+}
+
+void
+MediaDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
+ nsAutoPtr<MetadataTags> aTags,
+ MediaDecoderEventVisibility aEventVisibility)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
+ aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
+ aInfo->HasAudio(), aInfo->HasVideo());
+
+ mMediaSeekable = aInfo->mMediaSeekable;
+ mMediaSeekableOnlyInBufferedRanges = aInfo->mMediaSeekableOnlyInBufferedRanges;
+ mInfo = aInfo.forget();
+ ConstructMediaTracks();
+
+ // Make sure the element and the frame (if any) are told about
+ // our new size.
+ if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+ mFiredMetadataLoaded = true;
+ mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
+ }
+ // Invalidate() will end up calling mOwner->UpdateMediaSize with the last
+ // dimensions retrieved from the video frame container. The video frame
+ // container contains more up to date dimensions than aInfo.
+ // So we call Invalidate() after calling mOwner->MetadataLoaded to ensure
+ // the media element has the latest dimensions.
+ Invalidate();
+
+ EnsureTelemetryReported();
+}
+
+void
+MediaDecoder::EnsureTelemetryReported()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mTelemetryReported || !mInfo) {
+ // Note: sometimes we get multiple MetadataLoaded calls (for example
+ // for chained ogg). So we ensure we don't report duplicate results for
+ // these resources.
+ return;
+ }
+
+ nsTArray<nsCString> codecs;
+ if (mInfo->HasAudio() && !mInfo->mAudio.GetAsAudioInfo()->mMimeType.IsEmpty()) {
+ codecs.AppendElement(mInfo->mAudio.GetAsAudioInfo()->mMimeType);
+ }
+ if (mInfo->HasVideo() && !mInfo->mVideo.GetAsVideoInfo()->mMimeType.IsEmpty()) {
+ codecs.AppendElement(mInfo->mVideo.GetAsVideoInfo()->mMimeType);
+ }
+ if (codecs.IsEmpty()) {
+ if (mResource->GetContentType().IsEmpty()) {
+ NS_WARNING("Somehow the resource's content type is empty");
+ return;
+ }
+ codecs.AppendElement(nsPrintfCString("resource; %s", mResource->GetContentType().get()));
+ }
+ for (const nsCString& codec : codecs) {
+ DECODER_LOG("Telemetry MEDIA_CODEC_USED= '%s'", codec.get());
+ Telemetry::Accumulate(Telemetry::ID::MEDIA_CODEC_USED, codec);
+ }
+
+ mTelemetryReported = true;
+}
+
+const char*
+MediaDecoder::PlayStateStr()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return ToPlayStateStr(mPlayState);
+}
+
+void
+MediaDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
+ MediaDecoderEventVisibility aEventVisibility)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ DECODER_LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d mPlayState=%s",
+ aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
+ aInfo->HasAudio(), aInfo->HasVideo(), PlayStateStr());
+
+ mInfo = aInfo.forget();
+
+ Invalidate();
+
+ // 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) {
+ ChangeState(mNextState);
+ }
+
+ // Run NotifySuspendedStatusChanged now to give us a chance to notice
+ // that autoplay should run.
+ NotifySuspendedStatusChanged();
+
+ // mOwner->FirstFrameLoaded() might call us back. Put it at the bottom of
+ // this function to avoid unexpected shutdown from reentrant calls.
+ if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+ mOwner->FirstFrameLoaded();
+ }
+}
+
+void
+MediaDecoder::NetworkError()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mOwner->NetworkError();
+}
+
+void
+MediaDecoder::DecodeError(const MediaResult& aError)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mOwner->DecodeError(aError);
+}
+
+void
+MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mSameOriginMedia = aSameOrigin;
+}
+
+bool
+MediaDecoder::IsSeeking() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mLogicallySeeking;
+}
+
+bool
+MediaDecoder::OwnerHasError() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ return mOwner->HasError();
+}
+
+class MediaElementGMPCrashHelper : public GMPCrashHelper
+{
+public:
+ explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
+ : mElement(aElement)
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ }
+ already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
+ {
+ MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
+ if (!mElement) {
+ return nullptr;
+ }
+ return do_AddRef(mElement->OwnerDoc()->GetInnerWindow());
+ }
+private:
+ WeakPtr<HTMLMediaElement> mElement;
+};
+
+already_AddRefed<GMPCrashHelper>
+MediaDecoder::GetCrashHelper()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mOwner->GetMediaElement() ?
+ MakeAndAddRef<MediaElementGMPCrashHelper>(mOwner->GetMediaElement()) : nullptr;
+}
+
+bool
+MediaDecoder::IsEnded() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mPlayState == PLAY_STATE_ENDED;
+}
+
+bool
+MediaDecoder::IsShutdown() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return mPlayState == PLAY_STATE_SHUTDOWN;
+}
+
+void
+MediaDecoder::PlaybackEnded()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ if (mLogicallySeeking || mPlayState == PLAY_STATE_LOADING) {
+ return;
+ }
+
+ ChangeState(PLAY_STATE_ENDED);
+ InvalidateWithFlags(VideoFrameContainer::INVALIDATE_FORCE);
+ mOwner->PlaybackEnded();
+
+ // This must be called after |mOwner->PlaybackEnded()| call above, in order
+ // to fire the required durationchange.
+ if (IsInfinite()) {
+ SetInfinite(false);
+ }
+}
+
+MediaStatistics
+MediaDecoder::GetStatistics()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mResource);
+
+ MediaStatistics result;
+ result.mDownloadRate = mResource->GetDownloadRate(&result.mDownloadRateReliable);
+ result.mDownloadPosition = mResource->GetCachedDataEnd(mDecoderPosition);
+ result.mTotalBytes = mResource->GetLength();
+ result.mPlaybackRate = mPlaybackBytesPerSecond;
+ result.mPlaybackRateReliable = mPlaybackRateReliable;
+ result.mDecoderPosition = mDecoderPosition;
+ result.mPlaybackPosition = mPlaybackPosition;
+ return result;
+}
+
+void
+MediaDecoder::ComputePlaybackRate()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mResource);
+
+ int64_t length = mResource->GetLength();
+ if (!IsNaN(mDuration) && !mozilla::IsInfinite<double>(mDuration) && length >= 0) {
+ mPlaybackRateReliable = true;
+ mPlaybackBytesPerSecond = length / mDuration;
+ return;
+ }
+
+ bool reliable = false;
+ mPlaybackBytesPerSecond = mPlaybackStatistics->GetRateAtLastStop(&reliable);
+ mPlaybackRateReliable = reliable;
+}
+
+void
+MediaDecoder::UpdatePlaybackRate()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mResource);
+
+ ComputePlaybackRate();
+ uint32_t rate = mPlaybackBytesPerSecond;
+
+ if (mPlaybackRateReliable) {
+ // 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());
+ MOZ_ASSERT(!IsShutdown());
+ if (mResource) {
+ bool suspended = mResource->IsSuspendedByCache();
+ mOwner->NotifySuspendedByCache(suspended);
+ }
+}
+
+void
+MediaDecoder::NotifyBytesDownloaded()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ UpdatePlaybackRate();
+ mOwner->DownloadProgressed();
+}
+
+void
+MediaDecoder::NotifyDownloadEnded(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ DECODER_LOG("NotifyDownloadEnded, status=%x", aStatus);
+
+ if (aStatus == NS_BINDING_ABORTED) {
+ // Download has been cancelled by user.
+ mOwner->LoadAborted();
+ return;
+ }
+
+ UpdatePlaybackRate();
+
+ if (NS_SUCCEEDED(aStatus)) {
+ // A final progress event will be fired by the MediaResource calling
+ // DownloadSuspended on the element.
+ // Also NotifySuspendedStatusChanged() will be called to update readyState
+ // if download ended with success.
+ } else if (aStatus != NS_BASE_STREAM_CLOSED) {
+ NetworkError();
+ }
+}
+
+void
+MediaDecoder::NotifyPrincipalChanged()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ nsCOMPtr<nsIPrincipal> newPrincipal = GetCurrentPrincipal();
+ mMediaPrincipalHandle = MakePrincipalHandle(newPrincipal);
+ mOwner->NotifyDecoderPrincipalChanged();
+}
+
+void
+MediaDecoder::NotifyBytesConsumed(int64_t aBytes, int64_t aOffset)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ if (mIgnoreProgressData) {
+ return;
+ }
+
+ MOZ_ASSERT(mDecoderStateMachine);
+ if (aOffset >= mDecoderPosition) {
+ mPlaybackStatistics->AddBytes(aBytes);
+ }
+ mDecoderPosition = aOffset + aBytes;
+}
+
+void
+MediaDecoder::OnSeekResolved(SeekResolveValue aVal)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mSeekRequest.Complete();
+
+ bool fireEnded = false;
+ {
+ // An additional seek was requested while the current seek was
+ // in operation.
+ UnpinForSeek();
+ fireEnded = aVal.mAtEnd;
+ if (aVal.mAtEnd) {
+ ChangeState(PLAY_STATE_ENDED);
+ }
+ mLogicallySeeking = false;
+ }
+
+ // Ensure logical position is updated after seek.
+ UpdateLogicalPositionInternal();
+
+ mOwner->SeekCompleted();
+ AsyncResolveSeekDOMPromiseIfExists();
+ if (fireEnded) {
+ mOwner->PlaybackEnded();
+ }
+}
+
+void
+MediaDecoder::OnSeekRejected()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mSeekRequest.Complete();
+ mLogicallySeeking = false;
+ AsyncRejectSeekDOMPromiseIfExists();
+}
+
+void
+MediaDecoder::SeekingStarted()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mOwner->SeekStarted();
+}
+
+void
+MediaDecoder::ChangeState(PlayState aState)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown(), "SHUTDOWN is the final state.");
+
+ if (mNextState == aState) {
+ mNextState = PLAY_STATE_PAUSED;
+ }
+
+ DECODER_LOG("ChangeState %s => %s", PlayStateStr(), ToPlayStateStr(aState));
+ mPlayState = aState;
+
+ if (mPlayState == PLAY_STATE_PLAYING) {
+ ConstructMediaTracks();
+ } else if (IsEnded()) {
+ RemoveMediaTracks();
+ }
+}
+
+void
+MediaDecoder::UpdateLogicalPositionInternal()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ double currentPosition = static_cast<double>(CurrentPosition()) / static_cast<double>(USECS_PER_S);
+ if (mPlayState == PLAY_STATE_ENDED) {
+ currentPosition = std::max(currentPosition, mDuration);
+ }
+ bool logicalPositionChanged = mLogicalPosition != currentPosition;
+ mLogicalPosition = currentPosition;
+
+ // 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 (logicalPositionChanged) {
+ FireTimeUpdate();
+ }
+}
+
+void
+MediaDecoder::DurationChanged()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ double oldDuration = mDuration;
+ if (IsInfinite()) {
+ mDuration = std::numeric_limits<double>::infinity();
+ } else if (mExplicitDuration.Ref().isSome()) {
+ mDuration = mExplicitDuration.Ref().ref();
+ } else if (mStateMachineDuration.Ref().isSome()) {
+ mDuration = mStateMachineDuration.Ref().ref().ToSeconds();
+ }
+
+ if (mDuration == oldDuration || IsNaN(mDuration)) {
+ return;
+ }
+
+ DECODER_LOG("Duration changed to %f", mDuration);
+
+ // Duration has changed so we should recompute playback rate
+ UpdatePlaybackRate();
+
+ // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=28822 for a discussion
+ // of whether we should fire durationchange on explicit infinity.
+ if (mFiredMetadataLoaded &&
+ (!mozilla::IsInfinite<double>(mDuration) || mExplicitDuration.Ref().isSome())) {
+ mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
+ }
+
+ if (CurrentPosition() > TimeUnit::FromSeconds(mDuration).ToMicroseconds()) {
+ Seek(mDuration, SeekTarget::Accurate);
+ }
+}
+
+void
+MediaDecoder::SetElementVisibility(bool aIsVisible)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mElementVisible = aIsVisible;
+ mIsVisible = !mForcedHidden && mElementVisible;
+}
+
+void
+MediaDecoder::SetForcedHidden(bool aForcedHidden)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mForcedHidden = aForcedHidden;
+ SetElementVisibility(mElementVisible);
+}
+
+void
+MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mPlayState <= PLAY_STATE_LOADING) {
+ return;
+ }
+
+ // The duration is only changed if its significantly different than the
+ // the current estimate, as the incoming duration is an estimate and so
+ // often is unstable as more data is read and the estimate is updated.
+ // Can result in a durationchangeevent. aDuration is in microseconds.
+ if (mEstimatedDuration.Ref().isSome() &&
+ mozilla::Abs(mEstimatedDuration.Ref().ref().ToMicroseconds() - aDuration) < ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
+ return;
+ }
+
+ mEstimatedDuration = Some(TimeUnit::FromMicroseconds(aDuration));
+}
+
+bool
+MediaDecoder::IsTransportSeekable()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return GetResource()->IsTransportSeekable();
+}
+
+bool
+MediaDecoder::IsMediaSeekable()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(GetStateMachine(), false);
+ return mMediaSeekable;
+}
+
+media::TimeIntervals
+MediaDecoder::GetSeekable()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IsNaN(GetDuration())) {
+ // We do not have a duration yet, we can't determine the seekable range.
+ return TimeIntervals();
+ }
+
+ // 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.) or in cue-less WebMs
+ if (mMediaSeekableOnlyInBufferedRanges) {
+ return GetBuffered();
+ } else if (!IsMediaSeekable()) {
+ return media::TimeIntervals();
+ } else if (!IsTransportSeekable()) {
+ return GetBuffered();
+ } else {
+ return media::TimeIntervals(
+ media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
+ IsInfinite() ?
+ media::TimeUnit::FromInfinity() :
+ media::TimeUnit::FromSeconds(GetDuration())));
+ }
+}
+
+void
+MediaDecoder::SetFragmentEndTime(double aTime)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoderStateMachine) {
+ mDecoderStateMachine->DispatchSetFragmentEndTime(static_cast<int64_t>(aTime * USECS_PER_S));
+ }
+}
+
+void
+MediaDecoder::Suspend()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mResource) {
+ mResource->Suspend(true);
+ }
+}
+
+void
+MediaDecoder::Resume()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mResource) {
+ mResource->Resume();
+ }
+}
+
+void
+MediaDecoder::SetLoadInBackground(bool aLoadInBackground)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mResource) {
+ mResource->SetLoadInBackground(aLoadInBackground);
+ }
+}
+
+void
+MediaDecoder::SetPlaybackRate(double aPlaybackRate)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ double oldRate = mPlaybackRate;
+ mPlaybackRate = aPlaybackRate;
+ if (aPlaybackRate == 0) {
+ Pause();
+ return;
+ }
+
+
+ if (oldRate == 0 && !mOwner->GetPaused()) {
+ // PlaybackRate is no longer null.
+ // Restart the playback if the media was playing.
+ Play();
+ }
+
+ if (mDecoderStateMachine) {
+ mDecoderStateMachine->DispatchSetPlaybackRate(aPlaybackRate);
+ }
+}
+
+void
+MediaDecoder::SetPreservesPitch(bool aPreservesPitch)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mPreservesPitch = aPreservesPitch;
+}
+
+void
+MediaDecoder::ConnectMirrors(MediaDecoderStateMachine* aObject)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aObject);
+ mStateMachineDuration.Connect(aObject->CanonicalDuration());
+ mBuffered.Connect(aObject->CanonicalBuffered());
+ mStateMachineIsShutdown.Connect(aObject->CanonicalIsShutdown());
+ mNextFrameStatus.Connect(aObject->CanonicalNextFrameStatus());
+ mCurrentPosition.Connect(aObject->CanonicalCurrentPosition());
+ mPlaybackPosition.Connect(aObject->CanonicalPlaybackOffset());
+ mIsAudioDataAudible.Connect(aObject->CanonicalIsAudioDataAudible());
+}
+
+void
+MediaDecoder::DisconnectMirrors()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mStateMachineDuration.DisconnectIfConnected();
+ mBuffered.DisconnectIfConnected();
+ mStateMachineIsShutdown.DisconnectIfConnected();
+ mNextFrameStatus.DisconnectIfConnected();
+ mCurrentPosition.DisconnectIfConnected();
+ mPlaybackPosition.DisconnectIfConnected();
+ mIsAudioDataAudible.DisconnectIfConnected();
+}
+
+void
+MediaDecoder::SetStateMachine(MediaDecoderStateMachine* aStateMachine)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT_IF(aStateMachine, !mDecoderStateMachine);
+ mDecoderStateMachine = aStateMachine;
+ if (aStateMachine) {
+ ConnectMirrors(aStateMachine);
+ } else {
+ DisconnectMirrors();
+ }
+}
+
+ImageContainer*
+MediaDecoder::GetImageContainer()
+{
+ return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer() : nullptr;
+}
+
+void
+MediaDecoder::InvalidateWithFlags(uint32_t aFlags)
+{
+ if (mVideoFrameContainer) {
+ mVideoFrameContainer->InvalidateWithFlags(aFlags);
+ }
+}
+
+void
+MediaDecoder::Invalidate()
+{
+ if (mVideoFrameContainer) {
+ mVideoFrameContainer->Invalidate();
+ }
+}
+
+// Constructs the time ranges representing what segments of the media
+// are buffered and playable.
+media::TimeIntervals
+MediaDecoder::GetBuffered() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mBuffered.Ref();
+}
+
+size_t
+MediaDecoder::SizeOfVideoQueue() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoderStateMachine) {
+ return mDecoderStateMachine->SizeOfVideoQueue();
+ }
+ return 0;
+}
+
+size_t
+MediaDecoder::SizeOfAudioQueue() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDecoderStateMachine) {
+ return mDecoderStateMachine->SizeOfAudioQueue();
+ }
+ return 0;
+}
+
+void MediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (GetResource()) {
+ aSizes->mByteSize += GetResource()->SizeOfIncludingThis(aSizes->mMallocSizeOf);
+ }
+}
+
+void
+MediaDecoder::NotifyDataArrived() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mDataArrivedEvent.Notify();
+}
+
+// Provide access to the state machine object
+MediaDecoderStateMachine*
+MediaDecoder::GetStateMachine() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDecoderStateMachine;
+}
+
+void
+MediaDecoder::FireTimeUpdate()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+ mOwner->FireTimeUpdate(true);
+}
+
+void
+MediaDecoder::PinForSeek()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaResource* resource = GetResource();
+ if (!resource || mPinnedForSeek) {
+ return;
+ }
+ mPinnedForSeek = true;
+ resource->Pin();
+}
+
+void
+MediaDecoder::UnpinForSeek()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaResource* resource = GetResource();
+ if (!resource || !mPinnedForSeek) {
+ return;
+ }
+ mPinnedForSeek = false;
+ resource->Unpin();
+}
+
+bool
+MediaDecoder::CanPlayThrough()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mDecoderStateMachine, false);
+ return GetStatistics().CanPlayThrough();
+}
+
+RefPtr<MediaDecoder::CDMProxyPromise>
+MediaDecoder::RequestCDMProxy() const
+{
+ return mCDMProxyPromise;
+}
+
+void
+MediaDecoder::SetCDMProxy(CDMProxy* aProxy)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProxy);
+
+ mCDMProxyPromiseHolder.ResolveIfExists(aProxy, __func__);
+}
+
+bool
+MediaDecoder::IsOpusEnabled()
+{
+ return Preferences::GetBool("media.opus.enabled");
+}
+
+bool
+MediaDecoder::IsOggEnabled()
+{
+ return Preferences::GetBool("media.ogg.enabled");
+}
+
+bool
+MediaDecoder::IsWaveEnabled()
+{
+ return Preferences::GetBool("media.wave.enabled");
+}
+
+bool
+MediaDecoder::IsWebMEnabled()
+{
+ return Preferences::GetBool("media.webm.enabled");
+}
+
+#ifdef MOZ_ANDROID_OMX
+bool
+MediaDecoder::IsAndroidMediaPluginEnabled()
+{
+ return AndroidBridge::Bridge() &&
+ AndroidBridge::Bridge()->GetAPIVersion() < 16 &&
+ Preferences::GetBool("media.plugins.enabled");
+}
+#endif
+
+NS_IMETHODIMP
+MediaMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ int64_t video = 0, audio = 0;
+
+ // NB: When resourceSizes' ref count goes to 0 the promise will report the
+ // resources memory and finish the asynchronous memory report.
+ RefPtr<MediaDecoder::ResourceSizes> resourceSizes =
+ new MediaDecoder::ResourceSizes(MediaMemoryTracker::MallocSizeOf);
+
+ nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
+ nsCOMPtr<nsISupports> data = aData;
+
+ resourceSizes->Promise()->Then(
+ AbstractThread::MainThread(), __func__,
+ [handleReport, data] (size_t size) {
+ handleReport->Callback(
+ EmptyCString(), NS_LITERAL_CSTRING("explicit/media/resources"),
+ KIND_HEAP, UNITS_BYTES, size,
+ NS_LITERAL_CSTRING("Memory used by media resources including "
+ "streaming buffers, caches, etc."),
+ data);
+
+ nsCOMPtr<nsIMemoryReporterManager> imgr =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+
+ if (imgr) {
+ imgr->EndReport();
+ }
+ },
+ [] (size_t) { /* unused reject function */ });
+
+ DecodersArray& decoders = Decoders();
+ for (size_t i = 0; i < decoders.Length(); ++i) {
+ MediaDecoder* decoder = decoders[i];
+ video += decoder->SizeOfVideoQueue();
+ audio += decoder->SizeOfAudioQueue();
+ decoder->AddSizeOfResources(resourceSizes);
+ }
+
+ MOZ_COLLECT_REPORT(
+ "explicit/media/decoded/video", KIND_HEAP, UNITS_BYTES, video,
+ "Memory used by decoded video frames.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/media/decoded/audio", KIND_HEAP, UNITS_BYTES, audio,
+ "Memory used by decoded audio chunks.");
+
+ return NS_OK;
+}
+
+MediaDecoderOwner*
+MediaDecoder::GetOwner() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // mOwner is valid until shutdown.
+ return mOwner;
+}
+
+void
+MediaDecoder::ConstructMediaTracks()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ if (mMediaTracksConstructed || !mInfo) {
+ return;
+ }
+
+ HTMLMediaElement* element = mOwner->GetMediaElement();
+ if (!element) {
+ return;
+ }
+
+ mMediaTracksConstructed = true;
+
+ AudioTrackList* audioList = element->AudioTracks();
+ if (audioList && mInfo->HasAudio()) {
+ const TrackInfo& info = mInfo->mAudio;
+ RefPtr<AudioTrack> track = MediaTrackList::CreateAudioTrack(
+ info.mId, info.mKind, info.mLabel, info.mLanguage, info.mEnabled);
+
+ audioList->AddTrack(track);
+ }
+
+ VideoTrackList* videoList = element->VideoTracks();
+ if (videoList && mInfo->HasVideo()) {
+ const TrackInfo& info = mInfo->mVideo;
+ RefPtr<VideoTrack> track = MediaTrackList::CreateVideoTrack(
+ info.mId, info.mKind, info.mLabel, info.mLanguage);
+
+ videoList->AddTrack(track);
+ track->SetEnabledInternal(info.mEnabled, MediaTrack::FIRE_NO_EVENTS);
+ }
+}
+
+void
+MediaDecoder::RemoveMediaTracks()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsShutdown());
+
+ HTMLMediaElement* element = mOwner->GetMediaElement();
+ if (!element) {
+ return;
+ }
+
+ AudioTrackList* audioList = element->AudioTracks();
+ if (audioList) {
+ audioList->RemoveTracks();
+ }
+
+ VideoTrackList* videoList = element->VideoTracks();
+ if (videoList) {
+ videoList->RemoveTracks();
+ }
+
+ mMediaTracksConstructed = false;
+}
+
+MediaDecoderOwner::NextFrameStatus
+MediaDecoder::NextFrameBufferedStatus()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // Next frame hasn't been decoded yet.
+ // Use the buffered range to consider if we have the next frame available.
+ media::TimeUnit currentPosition =
+ media::TimeUnit::FromMicroseconds(CurrentPosition());
+ media::TimeInterval interval(currentPosition,
+ currentPosition + media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
+ return GetBuffered().Contains(interval)
+ ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
+ : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
+}
+
+void
+MediaDecoder::DumpDebugInfo()
+{
+ MOZ_ASSERT(!IsShutdown());
+ DUMP_LOG("metadata: channels=%u rate=%u hasAudio=%d hasVideo=%d, "
+ "state: mPlayState=%s mdsm=%p",
+ mInfo ? mInfo->mAudio.mChannels : 0, mInfo ? mInfo->mAudio.mRate : 0,
+ mInfo ? mInfo->HasAudio() : 0, mInfo ? mInfo->HasVideo() : 0,
+ PlayStateStr(), GetStateMachine());
+
+ nsString str;
+ GetMozDebugReaderData(str);
+ if (!str.IsEmpty()) {
+ DUMP_LOG("reader data:\n%s", NS_ConvertUTF16toUTF8(str).get());
+ }
+
+ if (GetStateMachine()) {
+ GetStateMachine()->DumpDebugInfo();
+ }
+}
+
+void
+MediaDecoder::NotifyAudibleStateChanged()
+{
+ MOZ_ASSERT(!IsShutdown());
+ mOwner->SetAudibleState(mIsAudioDataAudible);
+}
+
+MediaMemoryTracker::MediaMemoryTracker()
+{
+}
+
+void
+MediaMemoryTracker::InitMemoryReporter()
+{
+ RegisterWeakAsyncMemoryReporter(this);
+}
+
+MediaMemoryTracker::~MediaMemoryTracker()
+{
+ UnregisterWeakMemoryReporter(this);
+}
+
+} // namespace mozilla
+
+// avoid redefined macro in unified build
+#undef DECODER_LOG