Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "mozilla/dom/HTMLMediaElement.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <type_traits>
#include <unordered_map>
#include "AudioDeviceInfo.h"
#include "AudioStreamTrack.h"
#include "AutoplayPolicy.h"
#include "ChannelMediaDecoder.h"
#include "CrossGraphPort.h"
#include "DOMMediaStream.h"
#include "DecoderDoctorDiagnostics.h"
#include "DecoderDoctorLogger.h"
#include "DecoderTraits.h"
#include "FrameStatistics.h"
#include "GMPCrashHelper.h"
#include "GVAutoplayPermissionRequest.h"
#include "nsString.h"
#ifdef MOZ_ANDROID_HLS_SUPPORT
# include "HLSDecoder.h"
#endif
#include "HTMLMediaElement.h"
#include "ImageContainer.h"
#include "MP4Decoder.h"
#include "MediaContainerType.h"
#include "MediaError.h"
#include "MediaManager.h"
#include "MediaMetadataManager.h"
#include "MediaProfilerMarkers.h"
#include "MediaResource.h"
#include "MediaShutdownManager.h"
#include "MediaSourceDecoder.h"
#include "MediaStreamError.h"
#include "MediaStreamWindowCapturer.h"
#include "MediaTrack.h"
#include "MediaTrackGraphImpl.h"
#include "MediaTrackList.h"
#include "MediaTrackListener.h"
#include "Navigator.h"
#include "ReferrerInfo.h"
#include "TimeRanges.h"
#include "TimeUnits.h"
#include "VideoFrameContainer.h"
#include "VideoOutput.h"
#include "VideoStreamTrack.h"
#include "base/basictypes.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "jsapi.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EMEUtils.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/NotNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/SVGObserverUtils.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/AudioTrackList.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/ContentMediaController.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/HTMLAudioElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLMediaElementBinding.h"
#include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/MediaControlUtils.h"
#include "mozilla/dom/MediaDevices.h"
#include "mozilla/dom/MediaEncryptedEvent.h"
#include "mozilla/dom/MediaErrorBinding.h"
#include "mozilla/dom/MediaSource.h"
#include "mozilla/dom/PlayPromise.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TextTrack.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/VideoPlaybackQuality.h"
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/glean/DomMediaMetrics.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsAttrValueInlines.h"
#include "nsContentPolicyUtils.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDisplayList.h"
#include "nsDocShell.h"
#include "nsError.h"
#include "nsGenericHTMLElement.h"
#include "nsGkAtoms.h"
#include "nsGlobalWindowInner.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsICachingChannel.h"
#include "nsIClassOfService.h"
#include "nsIContentPolicy.h"
#include "nsIDocShell.h"
#include "nsIFrame.h"
#include "nsIHttpChannel.h"
#include "nsIObserverService.h"
#include "nsIRequest.h"
#include "nsIScriptError.h"
#include "nsISupportsPrimitives.h"
#include "nsIThreadRetargetableStreamListener.h"
#include "nsITimer.h"
#include "nsJSUtils.h"
#include "nsLayoutUtils.h"
#include "nsMediaFragmentURIParser.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
#include "nsNodeInfoManager.h"
#include "nsPresContext.h"
#include "nsQueryObject.h"
#include "nsRange.h"
#include "nsSize.h"
#include "nsThreadUtils.h"
#include "nsURIHashKey.h"
#include "nsURLHelper.h"
#include "nsVideoFrame.h"
#ifdef XP_WIN
# include "objbase.h"
#endif
#include "xpcpublic.h"
mozilla::LazyLogModule gMediaElementLog("HTMLMediaElement");
mozilla::LazyLogModule gMediaElementEventsLog("HTMLMediaElementEvents");
extern mozilla::LazyLogModule gAutoplayPermissionLog;
#define AUTOPLAY_LOG(msg, ...) \
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
// avoid redefined macro in unified build
#undef MEDIACONTROL_LOG
#define MEDIACONTROL_LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("HTMLMediaElement=%p, " msg, this, ##__VA_ARGS__))
#undef CONTROLLER_TIMER_LOG
#define CONTROLLER_TIMER_LOG(element, msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("HTMLMediaElement=%p, " msg, element, ##__VA_ARGS__))
#define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
#define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
using namespace mozilla::layers;
using mozilla::net::nsMediaFragmentURIParser;
using namespace mozilla::dom::HTMLMediaElement_Binding;
namespace mozilla::dom {
using AudibleState = AudioChannelService::AudibleState;
using SinkInfoPromise = MediaDevices::SinkInfoPromise;
// Number of milliseconds between progress events as defined by spec
static const uint32_t PROGRESS_MS = 350;
// Number of milliseconds of no data before a stall event is fired as defined by
// spec
static const uint32_t STALL_MS = 3000;
// Used by AudioChannel for suppresssing the volume to this ratio.
#define FADED_VOLUME_RATIO 0.25
// These constants are arbitrary
// Minimum playbackRate for a media
static const double MIN_PLAYBACKRATE = 1.0 / 16;
// Maximum playbackRate for a media
static const double MAX_PLAYBACKRATE = 16.0;
static double ClampPlaybackRate(double aPlaybackRate) {
MOZ_ASSERT(aPlaybackRate >= 0.0);
if (aPlaybackRate == 0.0) {
return aPlaybackRate;
}
if (aPlaybackRate < MIN_PLAYBACKRATE) {
return MIN_PLAYBACKRATE;
}
if (aPlaybackRate > MAX_PLAYBACKRATE) {
return MAX_PLAYBACKRATE;
}
return aPlaybackRate;
}
// Media error values. These need to match the ones in MediaError.webidl.
static const unsigned short MEDIA_ERR_ABORTED = 1;
static const unsigned short MEDIA_ERR_NETWORK = 2;
static const unsigned short MEDIA_ERR_DECODE = 3;
static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
/**
* EventBlocker helps media element to postpone the event delivery by storing
* the event runner, and execute them once media element decides not to postpone
* the event delivery. If media element never resumes the event delivery, then
* those runner would be cancelled.
* For example, we postpone the event delivery when media element entering to
* the bf-cache.
*/
class HTMLMediaElement::EventBlocker final : public nsISupports {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
NS_DECL_CYCLE_COLLECTION_CLASS(EventBlocker)
explicit EventBlocker(HTMLMediaElement* aElement) : mElement(aElement) {}
void SetBlockEventDelivery(bool aShouldBlock) {
MOZ_ASSERT(NS_IsMainThread());
if (mShouldBlockEventDelivery == aShouldBlock) {
return;
}
LOG_EVENT(LogLevel::Debug,
("%p %s event delivery", mElement.get(),
mShouldBlockEventDelivery ? "block" : "unblock"));
mShouldBlockEventDelivery = aShouldBlock;
if (!mShouldBlockEventDelivery) {
DispatchPendingMediaEvents();
}
}
void PostponeEvent(nsMediaEventRunner* aRunner) {
MOZ_ASSERT(NS_IsMainThread());
// Element has been CCed, which would break the weak pointer.
if (!mElement) {
return;
}
MOZ_ASSERT(mShouldBlockEventDelivery);
MOZ_ASSERT(mElement);
LOG_EVENT(LogLevel::Debug,
("%p postpone runner %s for %s", mElement.get(),
NS_ConvertUTF16toUTF8(aRunner->Name()).get(),
NS_ConvertUTF16toUTF8(aRunner->EventName()).get()));
mPendingEventRunners.AppendElement(aRunner);
}
void Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
for (auto& runner : mPendingEventRunners) {
runner->Cancel();
}
mPendingEventRunners.Clear();
}
bool ShouldBlockEventDelivery() const {
MOZ_ASSERT(NS_IsMainThread());
return mShouldBlockEventDelivery;
}
size_t SizeOfExcludingThis(MallocSizeOf& aMallocSizeOf) const {
MOZ_ASSERT(NS_IsMainThread());
size_t total = 0;
for (const auto& runner : mPendingEventRunners) {
total += aMallocSizeOf(runner);
}
return total;
}
private:
~EventBlocker() = default;
void DispatchPendingMediaEvents() {
MOZ_ASSERT(mElement);
for (auto& runner : mPendingEventRunners) {
LOG_EVENT(LogLevel::Debug,
("%p execute runner %s for %s", mElement.get(),
NS_ConvertUTF16toUTF8(runner->Name()).get(),
NS_ConvertUTF16toUTF8(runner->EventName()).get()));
GetMainThreadSerialEventTarget()->Dispatch(runner.forget());
}
mPendingEventRunners.Clear();
}
WeakPtr<HTMLMediaElement> mElement;
bool mShouldBlockEventDelivery = false;
// Contains event runners which should not be run for now because we want
// to block all events delivery. They would be dispatched once media element
// decides unblocking them.
nsTArray<RefPtr<nsMediaEventRunner>> mPendingEventRunners;
};
NS_IMPL_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker, mPendingEventRunners)
NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::EventBlocker)
NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::EventBlocker)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/**
* We use MediaControlKeyListener to listen to media control key in order to
* play and pause media element when user press media control keys and update
* media's playback and audible state to the media controller.
*
* Use `Start()` to start listening event and use `Stop()` to stop listening
* event. In addition, notifying any change to media controller MUST be done
* after successfully calling `Start()`.
*/
class HTMLMediaElement::MediaControlKeyListener final
: public ContentMediaControlKeyReceiver {
public:
NS_INLINE_DECL_REFCOUNTING(MediaControlKeyListener, override)
MOZ_INIT_OUTSIDE_CTOR explicit MediaControlKeyListener(
HTMLMediaElement* aElement)
: mElement(aElement), mElementId(nsID::GenerateUUID()) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aElement);
}
/**
* Start listening to the media control keys which would make media being able
* to be controlled via pressing media control keys.
*/
void Start() {
MOZ_ASSERT(NS_IsMainThread());
if (IsStarted()) {
// We have already been started, do not notify start twice.
return;
}
// Fail to init media agent, we are not able to notify the media controller
// any update and also are not able to receive media control key events.
if (!InitMediaAgent()) {
MEDIACONTROL_LOG("Failed to start due to not able to init media agent!");
return;
}
NotifyPlaybackStateChanged(MediaPlaybackState::eStarted);
// If owner has started playing before the listener starts, we should update
// the playing state as well. Eg. media starts inaudily and becomes audible
// later.
if (!Owner()->Paused()) {
NotifyMediaStartedPlaying();
}
if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
Owner(), u"MozStartMediaControl"_ns, CanBubble::eYes,
ChromeOnlyDispatch::eYes);
dispatcher->PostDOMEvent();
}
}
/**
* Stop listening to the media control keys which would make media not be able
* to be controlled via pressing media control keys. If we haven't started
* listening to the media control keys, then nothing would happen.
*/
void StopIfNeeded() {
MOZ_ASSERT(NS_IsMainThread());
if (!IsStarted()) {
// We have already been stopped, do not notify stop twice.
return;
}
NotifyMediaStoppedPlaying();
NotifyPlaybackStateChanged(MediaPlaybackState::eStopped);
// Remove ourselves from media agent, which would stop receiving event.
mControlAgent->RemoveReceiver(this);
mControlAgent = nullptr;
}
bool IsStarted() const { return mState != MediaPlaybackState::eStopped; }
bool IsPlaying() const override {
return Owner() ? !Owner()->Paused() : false;
}
/**
* Following methods should only be used after starting listener.
*/
void NotifyMediaStartedPlaying() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
if (mState == MediaPlaybackState::eStarted ||
mState == MediaPlaybackState::ePaused) {
NotifyPlaybackStateChanged(MediaPlaybackState::ePlayed);
// If media is `inaudible` in the beginning, then we don't need to notify
// the state, because notifying `inaudible` should always come after
// notifying `audible`.
if (mIsOwnerAudible) {
NotifyAudibleStateChanged(MediaAudibleState::eAudible);
}
}
}
void NotifyMediaStoppedPlaying() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
if (mState == MediaPlaybackState::ePlayed) {
NotifyPlaybackStateChanged(MediaPlaybackState::ePaused);
// As media are going to be paused, so no sound is possible to be heard.
if (mIsOwnerAudible) {
NotifyAudibleStateChanged(MediaAudibleState::eInaudible);
}
}
}
void NotifyMediaPositionState() {
if (!IsStarted()) {
return;
}
MOZ_ASSERT(mControlAgent);
auto* owner = Owner();
PositionState state(owner->Duration(), owner->PlaybackRate(),
owner->CurrentTime(), TimeStamp::Now());
MEDIACONTROL_LOG(
"Notify media position state (duration=%f, playbackRate=%f, "
"position=%f)",
state.mDuration, state.mPlaybackRate,
state.mLastReportedPlaybackPosition);
mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId,
mElementId, Some(state));
}
void Shutdown() {
StopIfNeeded();
if (!mControlAgent) {
return;
}
mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId,
mElementId, Nothing());
}
// This method can be called before the listener starts, which would cache
// the audible state and update after the listener starts.
void UpdateMediaAudibleState(bool aIsOwnerAudible) {
MOZ_ASSERT(NS_IsMainThread());
if (mIsOwnerAudible == aIsOwnerAudible) {
return;
}
mIsOwnerAudible = aIsOwnerAudible;
MEDIACONTROL_LOG("Media becomes %s",
mIsOwnerAudible ? "audible" : "inaudible");
// If media hasn't started playing, it doesn't make sense to update media
// audible state. Therefore, in that case we would noitfy the audible state
// when media starts playing.
if (mState == MediaPlaybackState::ePlayed) {
NotifyAudibleStateChanged(mIsOwnerAudible
? MediaAudibleState::eAudible
: MediaAudibleState::eInaudible);
}
}
void SetPictureInPictureModeEnabled(bool aIsEnabled) {
MOZ_ASSERT(NS_IsMainThread());
if (mIsPictureInPictureEnabled == aIsEnabled) {
return;
}
// PIP state changes might happen before the listener starts or stops where
// we haven't call `InitMediaAgent()` yet. Eg. Reset the PIP video's src,
// then cancel the PIP. In addition, not like playback and audible state
// which should be restricted to update via the same agent in order to keep
// those states correct in each `ContextMediaInfo`, PIP state can be updated
// through any browsing context, so we would use `ContentMediaAgent::Get()`
// directly to update PIP state.
mIsPictureInPictureEnabled = aIsEnabled;
if (RefPtr<IMediaInfoUpdater> updater =
ContentMediaAgent::Get(GetCurrentBrowsingContext())) {
updater->SetIsInPictureInPictureMode(mOwnerBrowsingContextId,
mIsPictureInPictureEnabled);
}
}
void HandleMediaKey(MediaControlKey aKey,
Maybe<SeekDetails> aDetails) override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
MEDIACONTROL_LOG("HandleEvent '%s'", GetEnumString(aKey).get());
switch (aKey) {
case MediaControlKey::Play:
Owner()->Play();
break;
case MediaControlKey::Pause:
Owner()->Pause();
break;
case MediaControlKey::Stop:
Owner()->Pause();
StopIfNeeded();
break;
case MediaControlKey::Seekto:
MOZ_ASSERT(aDetails->mAbsolute);
if (aDetails->mAbsolute->mFastSeek) {
Owner()->FastSeek(aDetails->mAbsolute->mSeekTime, IgnoreErrors());
} else {
Owner()->SetCurrentTime(aDetails->mAbsolute->mSeekTime);
}
break;
case MediaControlKey::Seekforward:
MOZ_ASSERT(aDetails->mRelativeSeekOffset);
Owner()->SetCurrentTime(Owner()->CurrentTime() +
aDetails->mRelativeSeekOffset.value());
break;
case MediaControlKey::Seekbackward:
MOZ_ASSERT(aDetails->mRelativeSeekOffset);
Owner()->SetCurrentTime(Owner()->CurrentTime() -
aDetails->mRelativeSeekOffset.value());
break;
default:
MOZ_ASSERT_UNREACHABLE(
"Unsupported media control key for media element!");
}
}
void UpdateOwnerBrowsingContextIfNeeded() {
// Has not notified any information about the owner context yet.
if (!IsStarted()) {
return;
}
BrowsingContext* currentBC = GetCurrentBrowsingContext();
MOZ_ASSERT(currentBC);
// Still in the same browsing context, no need to update.
if (currentBC->Id() == mOwnerBrowsingContextId) {
return;
}
MEDIACONTROL_LOG("Change browsing context from %" PRIu64 " to %" PRIu64,
mOwnerBrowsingContextId, currentBC->Id());
// This situation would happen when we start a media in an original browsing
// context, then we move it to another browsing context, such as an iframe,
// so its owner browsing context would be changed. Therefore, we should
// reset the media status for the previous browsing context by calling
// `Stop()`, in which the listener would notify `ePaused` (if it's playing)
// and `eStop`. Then calls `Start()`, in which the listener would notify
// `eStart` to the new browsing context. If the media was playing before,
// we would also notify `ePlayed`.
bool wasInPlayingState = mState == MediaPlaybackState::ePlayed;
StopIfNeeded();
Start();
if (wasInPlayingState) {
NotifyMediaStartedPlaying();
}
}
private:
~MediaControlKeyListener() = default;
// The media can be moved around different browsing contexts, so this context
// might be different from the one that we used to initialize
// `ContentMediaAgent`.
BrowsingContext* GetCurrentBrowsingContext() const {
// Owner has been CCed, which would break the link of the weaker pointer.
if (!Owner()) {
return nullptr;
}
nsPIDOMWindowInner* window = Owner()->OwnerDoc()->GetInnerWindow();
return window ? window->GetBrowsingContext() : nullptr;
}
bool InitMediaAgent() {
MOZ_ASSERT(NS_IsMainThread());
BrowsingContext* currentBC = GetCurrentBrowsingContext();
mControlAgent = ContentMediaAgent::Get(currentBC);
if (!mControlAgent) {
return false;
}
MOZ_ASSERT(currentBC);
mOwnerBrowsingContextId = currentBC->Id();
MEDIACONTROL_LOG("Init agent in browsing context %" PRIu64,
mOwnerBrowsingContextId);
mControlAgent->AddReceiver(this);
return true;
}
HTMLMediaElement* Owner() const {
// `mElement` would be clear during CC unlinked, but it would only happen
// after stopping the listener.
MOZ_ASSERT(mElement || !IsStarted());
return mElement.get();
}
void NotifyPlaybackStateChanged(MediaPlaybackState aState) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mControlAgent);
MEDIACONTROL_LOG("NotifyMediaState from state='%s' to state='%s'",
dom::EnumValueToString(mState),
dom::EnumValueToString(aState));
MOZ_ASSERT(mState != aState, "Should not notify same state again!");
mState = aState;
mControlAgent->NotifyMediaPlaybackChanged(mOwnerBrowsingContextId, mState);
if (aState == MediaPlaybackState::ePlayed) {
NotifyMediaPositionState();
}
}
void NotifyAudibleStateChanged(MediaAudibleState aState) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
mControlAgent->NotifyMediaAudibleChanged(mOwnerBrowsingContextId, aState);
}
MediaPlaybackState mState = MediaPlaybackState::eStopped;
WeakPtr<HTMLMediaElement> mElement;
RefPtr<ContentMediaAgent> mControlAgent;
bool mIsPictureInPictureEnabled = false;
bool mIsOwnerAudible = false;
MOZ_INIT_OUTSIDE_CTOR uint64_t mOwnerBrowsingContextId;
const nsID mElementId;
};
class HTMLMediaElement::MediaStreamTrackListener
: public DOMMediaStream::TrackListener {
public:
explicit MediaStreamTrackListener(HTMLMediaElement* aElement)
: mElement(aElement) {}
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrackListener,
DOMMediaStream::TrackListener)
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
if (!mElement) {
return;
}
mElement->NotifyMediaStreamTrackAdded(aTrack);
}
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
if (!mElement) {
return;
}
mElement->NotifyMediaStreamTrackRemoved(aTrack);
}
void OnActive() {
MOZ_ASSERT(mElement);
// mediacapture-main says:
// Note that once ended equals true the HTMLVideoElement will not play media
// even if new MediaStreamTracks are added to the MediaStream (causing it to
// return to the active state) unless autoplay is true or the web
// application restarts the element, e.g., by calling play().
//
// This is vague on exactly how to go from becoming active to playing, when
// autoplaying. However, per the media element spec, to play an autoplaying
// media element, we must load the source and reach readyState
// HAVE_ENOUGH_DATA [1]. Hence, a MediaStream being assigned to a media
// element and becoming active runs the load algorithm, so that it can
// eventually be played.
//
// [1]
LOG(LogLevel::Debug, ("%p, mSrcStream %p became active, checking if we "
"need to run the load algorithm",
mElement.get(), mElement->mSrcStream.get()));
if (!mElement->IsPlaybackEnded()) {
return;
}
if (!mElement->Autoplay()) {
return;
}
LOG(LogLevel::Info, ("%p, mSrcStream %p became active on autoplaying, "
"ended element. Reloading.",
mElement.get(), mElement->mSrcStream.get()));
mElement->DoLoad();
}
void NotifyActive() override {
if (!mElement) {
return;
}
if (!mElement->IsVideo()) {
// Audio elements use NotifyAudible().
return;
}
OnActive();
}
void NotifyAudible() override {
if (!mElement) {
return;
}
if (mElement->IsVideo()) {
// Video elements use NotifyActive().
return;
}
OnActive();
}
void OnInactive() {
MOZ_ASSERT(mElement);
if (mElement->IsPlaybackEnded()) {
return;
}
LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", mElement.get(),
mElement->mSrcStream.get()));
mElement->PlaybackEnded();
}
void NotifyInactive() override {
if (!mElement) {
return;
}
if (!mElement->IsVideo()) {
// Audio elements use NotifyInaudible().
return;
}
OnInactive();
}
void NotifyInaudible() override {