/* -*- 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/. */
usingnamespace mozilla::layers; using mozilla::net::nsMediaFragmentURIParser; usingnamespace 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 staticconst uint32_t PROGRESS_MS = 350;
// Number of milliseconds of no data before a stall event is fired as defined by // spec staticconst 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 staticconstdouble MIN_PLAYBACKRATE = 1.0 / 16; // Maximum playbackRate for a media staticconstdouble MAX_PLAYBACKRATE = 16.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. staticconstunsignedshort MEDIA_ERR_ABORTED = 1; staticconstunsignedshort MEDIA_ERR_NETWORK = 2; staticconstunsignedshort MEDIA_ERR_DECODE = 3; staticconstunsignedshort 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)
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);
}
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;
};
/** * 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)
/** * 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;
}
/** * 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));
}
// 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;
}
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();
}
}
// 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] // https://html.spec.whatwg.org/multipage/media.html#ready-states:event-media-play
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 { if (!mElement) { return;
}
if (mElement->IsVideo()) { // Video elements use NotifyInactive(). return;
}
/** * Helper class that manages audio and video outputs for all enabled tracks in a * media element. It also manages calculating the current time when playing a * MediaStream.
*/ class HTMLMediaElement::MediaStreamRenderer { public:
NS_INLINE_DECL_REFCOUNTING(MediaStreamRenderer)
for (constauto& t : mAudioTracks) { if (t) {
t->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey,
mAudioOutputSink);
t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
mAudioOutputVolume);
}
}
if (mVideoTrack) {
mVideoTrack->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer);
}
}
for (constauto& t : mAudioTracks) { if (t) {
t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
}
} // There is no longer an audio output that needs the device so the // device may not start. Ensure the promise is resolved.
ResolveAudioDevicePromiseIfExists(__func__);
if (mVideoTrack) {
mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(mVideoContainer);
}
}
void SetAudioOutputVolume(float aVolume) { if (mAudioOutputVolume == aVolume) { return;
}
mAudioOutputVolume = aVolume; if (!mRendering) { return;
} for (constauto& t : mAudioTracks) { if (t) {
t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
mAudioOutputVolume);
}
}
}
if (!mRendering) {
MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty()); return GenericPromise::CreateAndResolve(true, __func__);
}
nsTArray<RefPtr<GenericPromise>> promises; for (constauto& t : mAudioTracks) {
t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
promises.AppendElement(t->AsAudioStreamTrack()->AddAudioOutput(
mAudioOutputKey, mAudioOutputSink));
t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
mAudioOutputVolume);
} if (!promises.Length()) { // Not active track, save it for later
MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty()); return GenericPromise::CreateAndResolve(true, __func__);
}
// Resolve any existing promise for a previous device so that promises // resolve in order of setSinkId() invocation.
ResolveAudioDevicePromiseIfExists(__func__);
RefPtr promise = mSetAudioDevicePromise.Ensure(__func__);
GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
->Then(GetMainThreadSerialEventTarget(), __func__,
[self = RefPtr{this}, this](const GenericPromise::AllSettledPromiseType::
ResolveOrRejectValue& aValue) { // This handler should have been disconnected if // mSetAudioDevicePromise has been settled.
MOZ_ASSERT(!mSetAudioDevicePromise.IsEmpty());
mDeviceStartedRequest.Complete(); // The AudioStreamTrack::AddAudioOutput() promise is rejected // either when the graph no longer needs the device, in which // case this handler would have already been disconnected, or // the graph is force shutdown. // mSetAudioDevicePromise is resolved regardless of whether // the AddAudioOutput() promises resolve or reject because // the underlying device has been changed.
LOG(LogLevel::Info,
("MediaStreamRenderer=%p SetAudioOutputDevice settled", this));
mSetAudioDevicePromise.Resolve(true, __func__);
})
->Track(mDeviceStartedRequest);
return promise;
}
void AddTrack(AudioStreamTrack* aTrack) {
MOZ_DIAGNOSTIC_ASSERT(!mAudioTracks.Contains(aTrack));
mAudioTracks.AppendElement(aTrack);
EnsureGraphTimeDummy(); if (mRendering) {
aTrack->AddAudioOutput(mAudioOutputKey, mAudioOutputSink);
aTrack->SetAudioOutputVolume(mAudioOutputKey, mAudioOutputVolume);
}
} void AddTrack(VideoStreamTrack* aTrack) {
MOZ_DIAGNOSTIC_ASSERT(!mVideoTrack); if (!mVideoContainer) { return;
}
mVideoTrack = aTrack;
EnsureGraphTimeDummy(); if (mFirstFrameVideoOutput) { // Add the first frame output even if we are rendering. It will only // accept one frame. If we are rendering, then the main output will // overwrite that with the same frame (and possibly more frames).
aTrack->AddVideoOutput(mFirstFrameVideoOutput);
} if (mRendering) {
aTrack->AddVideoOutput(mVideoContainer);
}
}
if (mAudioTracks.IsEmpty()) { // There is no longer an audio output that needs the device so the // device may not start. Ensure the promise is resolved.
ResolveAudioDevicePromiseIfExists(__func__);
}
} void RemoveTrack(VideoStreamTrack* aTrack) {
MOZ_DIAGNOSTIC_ASSERT(mVideoTrack == aTrack); if (!mVideoContainer) { return;
} if (mFirstFrameVideoOutput) {
aTrack->RemoveVideoOutput(mFirstFrameVideoOutput);
} if (mRendering) {
aTrack->RemoveVideoOutput(mVideoContainer);
}
mVideoTrack = nullptr;
}
double CurrentTime() const { if (!mGraphTimeDummy) { return 0.0;
}
// This dummy keeps `graph` alive and ensures access to it.
mGraphTimeDummy = MakeRefPtr<SharedDummyTrack>(
graph->CreateSourceTrack(MediaSegment::AUDIO));
}
// The audio output volume for all audio tracks. float mAudioOutputVolume = 1.0f;
// The sink device for all audio tracks.
RefPtr<AudioDeviceInfo> mAudioOutputSink; // The promise returned from SetAudioOutputDevice() when an output is // active.
MozPromiseHolder<GenericPromise> mSetAudioDevicePromise; // Request tracking the promise to indicate when the device passed to // SetAudioOutputDevice() is running.
MozPromiseRequestHolder<GenericPromise::AllSettledPromiseType>
mDeviceStartedRequest;
// WatchManager for mGraphTime.
WatchManager<MediaStreamRenderer> mWatchManager;
// A dummy MediaTrack to guarantee a MediaTrackGraph is kept alive while // we're actively rendering, so we can track the graph's current time. Set // when the first track is added, never unset.
RefPtr<SharedDummyTrack> mGraphTimeDummy;
// Watchable that relays the graph's currentTime updates to the media element // only while we're rendering. This is the current time of the rendering in // GraphTime units.
Watchable<GraphTime> mGraphTime = {0, "MediaStreamRenderer::mGraphTime"};
// Nothing until a track has been added. Then, the current GraphTime at the // time when we were last Start()ed.
Maybe<GraphTime> mGraphTimeOffset;
// Currently enabled (and rendered) audio tracks.
nsTArray<WeakPtr<MediaStreamTrack>> mAudioTracks;
// Currently selected (and rendered) video track.
WeakPtr<MediaStreamTrack> mVideoTrack;
// Holds a reference to the first-frame-getting video output attached to // mVideoTrack. Set by the constructor, unset when the media element tells us // it has rendered the first frame.
RefPtr<FirstFrameVideoOutput> mFirstFrameVideoOutput;
};
static uint32_t sDecoderCaptureSourceId = 0; static uint32_t sStreamCaptureSourceId = 0; class HTMLMediaElement::MediaElementTrackSource
: public MediaStreamTrackSource, public MediaStreamTrackSource::Sink, public MediaStreamTrackConsumer { public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaElementTrackSource,
MediaStreamTrackSource)
void Stop() override { // Do nothing. There may appear new output streams // that need tracks sourced from this source, so we // cannot destroy things yet.
}
/** * Do not keep the track source alive. The source lifetime is controlled by * its associated tracks.
*/ bool KeepsSourceAlive() const override { returnfalse; }
/** * Do not keep the track source on. It is controlled by its associated tracks.
*/ bool Enabled() const override { returnfalse; }
void Disable() override {}
void Enable() override {}
void PrincipalChanged() override { if (!mCapturedTrackSource) { // This could happen during shutdown. return;
}
RefPtr<MediaStreamTrack> mCapturedTrack;
RefPtr<MediaStreamTrackSource> mCapturedTrackSource; const RefPtr<ProcessedMediaTrack> mTrack;
RefPtr<MediaInputPort> mPort; // The mute state as intended by the media element.
OutputMuteState mIntendedElementMuteState; // The mute state as applied to this track source. It is applied async, so // needs to be tracked separately from the intended state.
OutputMuteState mElementMuteState; // Some<bool> if this is a MediaDecoder track source. const Maybe<bool> mMediaDecoderHasAlpha;
};
/** * There is a reference cycle involving this class: MediaLoadListener * holds a reference to the HTMLMediaElement, which holds a reference * to an nsIChannel, which holds a reference to this listener. * We break the reference cycle in OnStartRequest by clearing mElement.
*/ class HTMLMediaElement::MediaLoadListener final
: public nsIChannelEventSink, public nsIInterfaceRequestor, public nsIObserver, public nsIThreadRetargetableStreamListener {
~MediaLoadListener() = default;
if (!mElement) { // We've been notified by the shutdown observer, and are shutting down. return NS_BINDING_ABORTED;
}
// The element is only needed until we've had a chance to call // InitializeDecoderForChannel. So make sure mElement is cleared here.
RefPtr<HTMLMediaElement> element;
element.swap(mElement);
if (mLoadID != element->GetCurrentLoadID()) { // The channel has been cancelled before we had a chance to create // a decoder. Abort, don't dispatch an "error" event, as the new load // may not be in an error state. return NS_BINDING_ABORTED;
}
// Don't continue to load if the request failed or has been canceled.
nsresult status;
nsresult rv = aRequest->GetStatus(&status);
NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(status)) { if (element) { // Handle media not loading error because source was a tracking URL (or // fingerprinting, cryptomining, etc). // We make a note of this media node by including it in a dedicated // array of blocked tracking nodes under its parent document. if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
status)) {
element->OwnerDoc()->AddBlockedNodeByClassifier(element);
}
element->NotifyLoadError(
nsPrintfCString("%u: %s", uint32_t(status), "Request failed"));
} return status;
}
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest); bool succeeded; if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
uint32_t responseStatus = 0;
Unused << hc->GetResponseStatus(&responseStatus);
nsAutoCString statusText;
Unused << hc->GetResponseStatusText(statusText); // we need status text for resist fingerprinting mode's message allowlist if (statusText.IsEmpty()) {
net_GetDefaultStatusTextForCode(responseStatus, statusText);
}
element->NotifyLoadError(
nsPrintfCString("%u: %s", responseStatus, statusText.get()));
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); if (channel &&
NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(
channel, getter_AddRefs(mNextListener))) &&
mNextListener) {
rv = mNextListener->OnStartRequest(aRequest);
} else { // If InitializeDecoderForChannel() returned an error, fire a network error. if (NS_FAILED(rv) && !mNextListener) { // Load failed, attempt to load the next candidate resource. If there // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
element->NotifyLoadError("Failed to init decoder"_ns);
} // If InitializeDecoderForChannel did not return a listener (but may // have otherwise succeeded), we abort the connection since we aren't // interested in keeping the channel alive ourselves.
rv = NS_BINDING_ABORTED;
}
class HTMLMediaElement::AudioChannelAgentCallback final
: public nsIAudioChannelAgentCallback { public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)
NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override { // Currently this method is only be used for delaying autoplay, and we've // separated related codes to `MediaPlaybackDelayPolicy`. return NS_OK;
}
void StopAudioChanelAgent() {
MOZ_ASSERT(mAudioChannelAgent);
MOZ_ASSERT(mAudioChannelAgent->IsPlayingStarted());
mAudioChannelAgent->NotifyStoppedPlaying(); // If we have started audio capturing before, we have to tell media element // to clear the output capturing track.
mOwner->AudioCaptureTrackChange(false);
}
// The audio channel volume float mAudioChannelVolume; // Is this media element playing? bool mPlayingThroughTheAudioChannel; // Indicate whether media element is audible for users.
AudibleState mIsOwnerAudible; bool mIsShutDown;
};
class HTMLMediaElement::ChannelLoader final { public:
NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
void LoadInternal(HTMLMediaElement* aElement) { if (mCancelled) { return;
}
// determine what security checks need to be performed in AsyncOpen().
nsSecurityFlags securityFlags =
aElement->ShouldCheckAllowOrigin()
? nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
// If aElement has 'triggeringprincipal' attribute, we will use the value as // triggeringPrincipal for the channel, otherwise it will default to use // aElement->NodePrincipal(). // This function returns true when aElement has 'triggeringprincipal', so if // setAttrs is true we will override the origin attributes on the channel // later.
nsCOMPtr<nsIPrincipal> triggeringPrincipal; bool setAttrs = nsContentUtils::QueryTriggeringPrincipal(
aElement, aElement->mLoadingSrcTriggeringPrincipal,
getter_AddRefs(triggeringPrincipal));
if (NS_FAILED(rv)) { // Notify load error so the element will try next resource candidate.
aElement->NotifyLoadError("Fail to create channel"_ns); return;
}
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); if (setAttrs) { // The function simply returns NS_OK, so we ignore the return value.
Unused << loadInfo->SetOriginAttributes(
triggeringPrincipal->OriginAttributesRef());
}
loadInfo->SetIsMediaRequest(true);
loadInfo->SetIsMediaInitialRequest(true);
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel)); if (cos) { if (aElement->mUseUrgentStartForChannel) {
cos->AddClassFlags(nsIClassOfService::UrgentStart);
// Reset the flag to avoid loading again without initiated by user // interaction.
aElement->mUseUrgentStartForChannel = false;
}
// Unconditionally disable throttling since we want the media to fluently // play even when we switch the tab to background.
cos->AddClassFlags(nsIClassOfService::DontThrottle);
}
// The listener holds a strong reference to us. This creates a // reference cycle, once we've set mChannel, which is manually broken // in the listener's OnStartRequest method after it is finished with // the element. The cycle will also be broken if we get a shutdown // notification before OnStartRequest fires. Necko guarantees that // OnStartRequest will eventually fire if we don't shut down first.
RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
channel->SetNotificationCallbacks(loadListener);
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel); if (hc) { // Use a byte range request from the start of the resource. // This enables us to detect if the stream supports byte range // requests, and therefore seeking, early.
rv = hc->SetRequestHeader("Range"_ns, "bytes=0-"_ns, false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
aElement->SetRequestHeaders(hc);
}
rv = channel->AsyncOpen(loadListener); if (NS_FAILED(rv)) { // Notify load error so the element will try next resource candidate.
aElement->NotifyLoadError("Failed to open channel"_ns); return;
}
// Else the channel must be open and starting to download. If it encounters
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.31 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.