Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/html/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 269 kB image not shown  

Quelle  HTMLMediaElement.cpp   Sprache: C

 
/* -*- 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]
    // 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;
    }

    OnInactive();
  }

 protected:
  virtual ~MediaStreamTrackListener() = default;
  RefPtr<HTMLMediaElement> mElement;
};

NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::MediaStreamTrackListener,
                                   DOMMediaStream::TrackListener, mElement)
NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaStreamTrackListener,
                         DOMMediaStream::TrackListener)
NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaStreamTrackListener,
                          DOMMediaStream::TrackListener)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
    HTMLMediaElement::MediaStreamTrackListener)
NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream::TrackListener)

/**
 * 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)

  MediaStreamRenderer(AbstractThread* aMainThread,
                      VideoFrameContainer* aVideoContainer,
                      FirstFrameVideoOutput* aFirstFrameVideoOutput,
                      void* aAudioOutputKey)
      : mVideoContainer(aVideoContainer),
        mAudioOutputKey(aAudioOutputKey),
        mWatchManager(this, aMainThread),
        mFirstFrameVideoOutput(aFirstFrameVideoOutput) {
    if (mFirstFrameVideoOutput) {
      mWatchManager.Watch(mFirstFrameVideoOutput->mFirstFrameRendered,
                          &MediaStreamRenderer::SetFirstFrameRendered);
    }
  }

  void Shutdown() {
    for (const auto& t : mAudioTracks.Clone()) {
      if (t) {
        RemoveTrack(t->AsAudioStreamTrack());
      }
    }
    if (mVideoTrack) {
      RemoveTrack(mVideoTrack->AsVideoStreamTrack());
    }
    mWatchManager.Shutdown();
    mFirstFrameVideoOutput = nullptr;
  }

  void UpdateGraphTime() {
    mGraphTime =
        mGraphTimeDummy->mTrack->Graph()->CurrentTime() - *mGraphTimeOffset;
  }

  void SetFirstFrameRendered() {
    if (!mFirstFrameVideoOutput) {
      return;
    }
    if (mVideoTrack) {
      mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(
          mFirstFrameVideoOutput);
    }
    mWatchManager.Unwatch(mFirstFrameVideoOutput->mFirstFrameRendered,
                          &MediaStreamRenderer::SetFirstFrameRendered);
    mFirstFrameVideoOutput = nullptr;
  }

  void SetProgressingCurrentTime(bool aProgress) {
    if (aProgress == mProgressingCurrentTime) {
      return;
    }

    MOZ_DIAGNOSTIC_ASSERT(mGraphTimeDummy);
    mProgressingCurrentTime = aProgress;
    MediaTrackGraph* graph = mGraphTimeDummy->mTrack->Graph();
    if (mProgressingCurrentTime) {
      mGraphTimeOffset = Some(graph->CurrentTime().Ref() - mGraphTime);
      mWatchManager.Watch(graph->CurrentTime(),
                          &MediaStreamRenderer::UpdateGraphTime);
    } else {
      mWatchManager.Unwatch(graph->CurrentTime(),
                            &MediaStreamRenderer::UpdateGraphTime);
    }
  }

  void Start() {
    if (mRendering) {
      return;
    }

    LOG(LogLevel::Info, ("MediaStreamRenderer=%p Start"this));
    mRendering = true;

    if (!mGraphTimeDummy) {
      return;
    }

    for (const auto& t : mAudioTracks) {
      if (t) {
        t->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey,
                                                mAudioOutputSink);
        t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
                                                      mAudioOutputVolume);
      }
    }

    if (mVideoTrack) {
      mVideoTrack->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer);
    }
  }

  void Stop() {
    if (!mRendering) {
      return;
    }

    LOG(LogLevel::Info, ("MediaStreamRenderer=%p Stop"this));
    mRendering = false;

    if (!mGraphTimeDummy) {
      return;
    }

    for (const auto& 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 (const auto& t : mAudioTracks) {
      if (t) {
        t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
                                                      mAudioOutputVolume);
      }
    }
  }

  RefPtr<GenericPromise> SetAudioOutputDevice(AudioDeviceInfo* aSink) {
    MOZ_ASSERT(aSink);
    MOZ_ASSERT(mAudioOutputSink != aSink);
    LOG(LogLevel::Info,
        ("MediaStreamRenderer=%p SetAudioOutputDevice name=%s\n"this,
         NS_ConvertUTF16toUTF8(aSink->Name()).get()));

    mAudioOutputSink = aSink;

    if (!mRendering) {
      MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty());
      return GenericPromise::CreateAndResolve(true, __func__);
    }

    nsTArray<RefPtr<GenericPromise>> promises;
    for (const auto& 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);
    }
  }

  void RemoveTrack(AudioStreamTrack* aTrack) {
    MOZ_DIAGNOSTIC_ASSERT(mAudioTracks.Contains(aTrack));
    if (mRendering) {
      aTrack->RemoveAudioOutput(mAudioOutputKey);
    }
    mAudioTracks.RemoveElement(aTrack);

    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;
    }

    return mGraphTimeDummy->mTrack->GraphImpl()->MediaTimeToSeconds(mGraphTime);
  }

  Watchable<GraphTime>& CurrentGraphTime() { return mGraphTime; }

  // Set if we're rendering video.
  const RefPtr<VideoFrameContainer> mVideoContainer;

  // Set if we're rendering audio, nullptr otherwise.
  voidconst mAudioOutputKey;

 private:
  ~MediaStreamRenderer() { Shutdown(); }

  void EnsureGraphTimeDummy() {
    if (mGraphTimeDummy) {
      return;
    }

    MediaTrackGraph* graph = nullptr;
    for (const auto& t : mAudioTracks) {
      if (t && !t->Ended()) {
        graph = t->Graph();
        break;
      }
    }

    if (!graph && mVideoTrack && !mVideoTrack->Ended()) {
      graph = mVideoTrack->Graph();
    }

    if (!graph) {
      return;
    }

    // This dummy keeps `graph` alive and ensures access to it.
    mGraphTimeDummy = MakeRefPtr<SharedDummyTrack>(
        graph->CreateSourceTrack(MediaSegment::AUDIO));
  }

  void ResolveAudioDevicePromiseIfExists(StaticString aMethodName) {
    if (mSetAudioDevicePromise.IsEmpty()) {
      return;
    }
    LOG(LogLevel::Info,
        ("MediaStreamRenderer=%p resolve audio device promise"this));
    mSetAudioDevicePromise.Resolve(true, aMethodName);
    mDeviceStartedRequest.Disconnect();
  }

  // True when all tracks are being rendered, i.e., when the media element is
  // playing.
  bool mRendering = false;

  // True while we're progressing mGraphTime. False otherwise.
  bool mProgressingCurrentTime = false;

  // 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)

  /* MediaDecoder track source */
  MediaElementTrackSource(ProcessedMediaTrack* aTrack, nsIPrincipal* aPrincipal,
                          OutputMuteState aMuteState, bool aHasAlpha)
      : MediaStreamTrackSource(
            aPrincipal, nsString(),
            TrackingId(TrackingId::Source::MediaElementDecoder,
                       sDecoderCaptureSourceId++,
                       TrackingId::TrackAcrossProcesses::Yes)),
        mTrack(aTrack),
        mIntendedElementMuteState(aMuteState),
        mElementMuteState(aMuteState),
        mMediaDecoderHasAlpha(Some(aHasAlpha)) {
    MOZ_ASSERT(mTrack);
  }

  /* MediaStream track source */
  MediaElementTrackSource(MediaStreamTrack* aCapturedTrack,
                          MediaStreamTrackSource* aCapturedTrackSource,
                          ProcessedMediaTrack* aTrack, MediaInputPort* aPort,
                          OutputMuteState aMuteState)
      : MediaStreamTrackSource(
            aCapturedTrackSource->GetPrincipal(), nsString(),
            TrackingId(TrackingId::Source::MediaElementStream,
                       sStreamCaptureSourceId++,
                       TrackingId::TrackAcrossProcesses::Yes)),
        mCapturedTrack(aCapturedTrack),
        mCapturedTrackSource(aCapturedTrackSource),
        mTrack(aTrack),
        mPort(aPort),
        mIntendedElementMuteState(aMuteState),
        mElementMuteState(aMuteState) {
    MOZ_ASSERT(mTrack);
    MOZ_ASSERT(mCapturedTrack);
    MOZ_ASSERT(mCapturedTrackSource);
    MOZ_ASSERT(mPort);

    mCapturedTrack->AddConsumer(this);
    mCapturedTrackSource->RegisterSink(this);
  }

  void SetEnabled(bool aEnabled) {
    if (!mTrack) {
      return;
    }
    mTrack->SetDisabledTrackMode(aEnabled ? DisabledTrackMode::ENABLED
                                          : DisabledTrackMode::SILENCE_FREEZE);
  }

  void SetPrincipal(RefPtr<nsIPrincipal> aPrincipal) {
    mPrincipal = std::move(aPrincipal);
    MediaStreamTrackSource::PrincipalChanged();
  }

  void SetMutedByElement(OutputMuteState aMuteState) {
    if (mIntendedElementMuteState == aMuteState) {
      return;
    }
    mIntendedElementMuteState = aMuteState;
    GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
        "MediaElementTrackSource::SetMutedByElement",
        [self = RefPtr<MediaElementTrackSource>(this), this, aMuteState] {
          mElementMuteState = aMuteState;
          MediaStreamTrackSource::MutedChanged(Muted());
        }));
  }

  void Destroy() override {
    if (mCapturedTrack) {
      mCapturedTrack->RemoveConsumer(this);
      mCapturedTrack = nullptr;
    }
    if (mCapturedTrackSource) {
      mCapturedTrackSource->UnregisterSink(this);
      mCapturedTrackSource = nullptr;
    }
    if (mTrack && !mTrack->IsDestroyed()) {
      mTrack->Destroy();
    }
    if (mPort) {
      mPort->Destroy();
      mPort = nullptr;
    }
  }

  MediaSourceEnum GetMediaSource() const override {
    return MediaSourceEnum::Other;
  }

  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 { return false; }

  /**
   * Do not keep the track source on. It is controlled by its associated tracks.
   */

  bool Enabled() const override { return false; }

  void Disable() override {}

  void Enable() override {}

  void PrincipalChanged() override {
    if (!mCapturedTrackSource) {
      // This could happen during shutdown.
      return;
    }

    SetPrincipal(mCapturedTrackSource->GetPrincipal());
  }

  void MutedChanged(bool aNewState) override {
    MediaStreamTrackSource::MutedChanged(Muted());
  }

  void OverrideEnded() override {
    Destroy();
    MediaStreamTrackSource::OverrideEnded();
  }

  void NotifyEnabledChanged(MediaStreamTrack* aTrack, bool aEnabled) override {
    MediaStreamTrackSource::MutedChanged(Muted());
  }

  bool Muted() const {
    return mElementMuteState == OutputMuteState::Muted ||
           (mCapturedTrack &&
            (mCapturedTrack->Muted() || !mCapturedTrack->Enabled()));
  }

  bool HasAlpha() const override {
    if (mCapturedTrack) {
      return mCapturedTrack->AsVideoStreamTrack()
                 ? mCapturedTrack->AsVideoStreamTrack()->HasAlpha()
                 : false;
    }
    return mMediaDecoderHasAlpha.valueOr(false);
  }

  ProcessedMediaTrack* Track() const { return mTrack; }

 private:
  virtual ~MediaElementTrackSource() { Destroy(); };

  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;
};

HTMLMediaElement::OutputMediaStream::OutputMediaStream(
    RefPtr<DOMMediaStream> aStream, bool aCapturingAudioOnly,
    bool aFinishWhenEnded)
    : mStream(std::move(aStream)),
      mCapturingAudioOnly(aCapturingAudioOnly),
      mFinishWhenEnded(aFinishWhenEnded) {}
HTMLMediaElement::OutputMediaStream::~OutputMediaStream() = default;

void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
                                 HTMLMediaElement::OutputMediaStream& aField,
                                 const char* aName, uint32_t aFlags) {
  ImplCycleCollectionTraverse(aCallback, aField.mStream, "mStream", aFlags);
  ImplCycleCollectionTraverse(aCallback, aField.mLiveTracks, "mLiveTracks",
                              aFlags);
  ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedLoadingSrc,
                              "mFinishWhenEndedLoadingSrc", aFlags);
  ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedAttrStream,
                              "mFinishWhenEndedAttrStream", aFlags);
  ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedMediaSource,
                              "mFinishWhenEndedMediaSource", aFlags);
}

void ImplCycleCollectionUnlink(HTMLMediaElement::OutputMediaStream& aField) {
  ImplCycleCollectionUnlink(aField.mStream);
  ImplCycleCollectionUnlink(aField.mLiveTracks);
  ImplCycleCollectionUnlink(aField.mFinishWhenEndedLoadingSrc);
  ImplCycleCollectionUnlink(aField.mFinishWhenEndedAttrStream);
  ImplCycleCollectionUnlink(aField.mFinishWhenEndedMediaSource);
}

NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaElementTrackSource,
                         MediaStreamTrackSource)
NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaElementTrackSource,
                          MediaStreamTrackSource)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
    HTMLMediaElement::MediaElementTrackSource)
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::MediaElementTrackSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
    HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
  tmp->Destroy();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrack)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrackSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
    HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrack)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrackSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

/**
 * 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;

  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSICHANNELEVENTSINK
  NS_DECL_NSIOBSERVER
  NS_DECL_NSIINTERFACEREQUESTOR
  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER

 public:
  explicit MediaLoadListener(HTMLMediaElement* aElement)
      : mElement(aElement), mLoadID(aElement->GetCurrentLoadID()) {
    MOZ_ASSERT(mElement, "Must pass an element to call back");
  }

 private:
  RefPtr<HTMLMediaElement> mElement;
  nsCOMPtr<nsIStreamListener> mNextListener;
  const uint32_t mLoadID;
};

NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener, nsIRequestObserver,
                  nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
                  nsIObserver, nsIThreadRetargetableStreamListener)

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
                                             const char* aTopic,
                                             const char16_t* aData) {
  nsContentUtils::UnregisterShutdownObserver(this);

  // Clear mElement to break cycle so we don't leak on shutdown
  mElement = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest) {
  nsContentUtils::UnregisterShutdownObserver(this);

  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()));

    nsAutoString code;
    code.AppendInt(responseStatus);
    nsAutoString src;
    element->GetCurrentSrc(src);
    AutoTArray<nsString, 2> params = {code, src};
    element->ReportLoadError("MediaLoadHttpError", params);
    return NS_BINDING_ABORTED;
  }

  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;
  }

  return rv;
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest,
                                                   nsresult aStatus) {
  if (mNextListener) {
    return mNextListener->OnStopRequest(aRequest, aStatus);
  }
  return NS_OK;
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest,
                                                     nsIInputStream* aStream,
                                                     uint64_t aOffset,
                                                     uint32_t aCount) {
  if (!mNextListener) {
    NS_ERROR(
        "Must have a chained listener; OnStartRequest should have "
        "canceled this request");
    return NS_BINDING_ABORTED;
  }
  return mNextListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::OnDataFinished(nsresult aStatus) {
  if (!mNextListener) {
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
      do_QueryInterface(mNextListener);
  if (retargetable) {
    return retargetable->OnDataFinished(aStatus);
  }

  return NS_OK;
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
    nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
    nsIAsyncVerifyRedirectCallback* cb) {
  // TODO is this really correct?? See bug #579329.
  if (mElement) {
    mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
  }
  nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
  if (sink) {
    return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
  }
  cb->OnRedirectVerifyCallback(NS_OK);
  return NS_OK;
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::CheckListenerChain() {
  MOZ_ASSERT(mNextListener);
  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
      do_QueryInterface(mNextListener);
  if (retargetable) {
    return retargetable->CheckListenerChain();
  }
  return NS_ERROR_NO_INTERFACE;
}

NS_IMETHODIMP
HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID,
                                                  void** aResult) {
  return QueryInterface(aIID, aResult);
}

void HTMLMediaElement::ReportLoadError(const char* aMsg,
                                       const nsTArray<nsString>& aParams) {
  ReportToConsole(nsIScriptError::warningFlag, aMsg, aParams);
}

void HTMLMediaElement::ReportToConsole(
    uint32_t aErrorFlags, const char* aMsg,
    const nsTArray<nsString>& aParams) const {
  nsContentUtils::ReportToConsole(aErrorFlags, "Media"_ns, OwnerDoc(),
                                  nsContentUtils::eDOM_PROPERTIES, aMsg,
                                  aParams);
}

class HTMLMediaElement::AudioChannelAgentCallback final
    : public nsIAudioChannelAgentCallback {
 public:
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)

  explicit AudioChannelAgentCallback(HTMLMediaElement* aOwner)
      : mOwner(aOwner),
        mAudioChannelVolume(1.0),
        mPlayingThroughTheAudioChannel(false),
        mIsOwnerAudible(IsOwnerAudible()),
        mIsShutDown(false) {
    MOZ_ASSERT(mOwner);
    MaybeCreateAudioChannelAgent();
  }

  void UpdateAudioChannelPlayingState() {
    MOZ_ASSERT(!mIsShutDown);
    bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();

    if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
      if (!MaybeCreateAudioChannelAgent()) {
        return;
      }

      mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
      if (mPlayingThroughTheAudioChannel) {
        StartAudioChannelAgent();
      } else {
        StopAudioChanelAgent();
      }
    }
  }

  void NotifyPlayStateChanged() {
    MOZ_ASSERT(!mIsShutDown);
    UpdateAudioChannelPlayingState();
  }

  NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override {
    MOZ_ASSERT(mAudioChannelAgent);

    MOZ_LOG(
        AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
        ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
         "this = %p, aVolume = %f, aMuted = %s\n",
         this, aVolume, aMuted ? "true" : "false"));

    if (mAudioChannelVolume != aVolume) {
      mAudioChannelVolume = aVolume;
      mOwner->SetVolumeInternal();
    }

    const uint32_t muted = mOwner->mMuted;
    if (aMuted && !mOwner->ComputedMuted()) {
      mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL);
    } else if (!aMuted && mOwner->ComputedMuted()) {
      mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL);
    }

    return NS_OK;
  }

  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;
  }

  NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override {
    MOZ_ASSERT(mAudioChannelAgent);
    AudioCaptureTrackChangeIfNeeded();
    return NS_OK;
  }

  void AudioCaptureTrackChangeIfNeeded() {
    MOZ_ASSERT(!mIsShutDown);
    if (!IsPlayingStarted()) {
      return;
    }

    MOZ_ASSERT(mAudioChannelAgent);
    bool isCapturing = mAudioChannelAgent->IsWindowAudioCapturingEnabled();
    mOwner->AudioCaptureTrackChange(isCapturing);
  }

  void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason) {
    MOZ_ASSERT(!mIsShutDown);
    AudibleState newAudibleState = IsOwnerAudible();
    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
            ("HTMLMediaElement::AudioChannelAgentCallback, "
             "NotifyAudioPlaybackChanged, this=%p, current=%s, new=%s",
             this, AudioChannelService::EnumValueToString(mIsOwnerAudible),
             AudioChannelService::EnumValueToString(newAudibleState)));
    if (mIsOwnerAudible == newAudibleState) {
      return;
    }

    mIsOwnerAudible = newAudibleState;
    if (IsPlayingStarted()) {
      mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
    }
  }

  void Shutdown() {
    MOZ_ASSERT(!mIsShutDown);
    if (mAudioChannelAgent && mAudioChannelAgent->IsPlayingStarted()) {
      StopAudioChanelAgent();
    }
    mAudioChannelAgent = nullptr;
    mIsShutDown = true;
  }

  float GetEffectiveVolume() const {
    MOZ_ASSERT(!mIsShutDown);
    return static_cast<float>(mOwner->Volume()) * mAudioChannelVolume;
  }

 private:
  ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown); };

  bool MaybeCreateAudioChannelAgent() {
    if (mAudioChannelAgent) {
      return true;
    }

    mAudioChannelAgent = new AudioChannelAgent();
    nsresult rv =
        mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(), this);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      mAudioChannelAgent = nullptr;
      MOZ_LOG(
          AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
          ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
           "the audio channel agent, this = %p\n",
           this));
      return false;
    }

    return true;
  }

  void StartAudioChannelAgent() {
    MOZ_ASSERT(mAudioChannelAgent);
    MOZ_ASSERT(!mAudioChannelAgent->IsPlayingStarted());
    if (NS_WARN_IF(NS_FAILED(
            mAudioChannelAgent->NotifyStartedPlaying(IsOwnerAudible())))) {
      return;
    }
    mAudioChannelAgent->PullInitialUpdate();
  }

  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);
  }

  bool IsPlayingStarted() {
    if (MaybeCreateAudioChannelAgent()) {
      return mAudioChannelAgent->IsPlayingStarted();
    }
    return false;
  }

  AudibleState IsOwnerAudible() const {
    // paused media doesn't produce any sound.
    if (mOwner->mPaused) {
      return AudibleState::eNotAudible;
    }
    return mOwner->IsAudible() ? AudibleState::eAudible
                               : AudibleState::eNotAudible;
  }

  bool IsPlayingThroughTheAudioChannel() const {
    // If we have an error, we are not playing.
    if (mOwner->GetError()) {
      return false;
    }

    // We should consider any bfcached page or inactive document as non-playing.
    if (!mOwner->OwnerDoc()->IsActive()) {
      return false;
    }

    // Media is suspended by the docshell.
    if (mOwner->ShouldBeSuspendedByInactiveDocShell()) {
      return false;
    }

    // Are we paused
    if (mOwner->mPaused) {
      return false;
    }

    // No audio track
    if (!mOwner->HasAudio()) {
      return false;
    }

    // A loop always is playing
    if (mOwner->HasAttr(nsGkAtoms::loop)) {
      return true;
    }

    // If we are actually playing...
    if (mOwner->IsCurrentlyPlaying()) {
      return true;
    }

    // If we are playing an external stream.
    if (mOwner->mSrcAttrStream) {
      return true;
    }

    return false;
  }

  RefPtr<AudioChannelAgent> mAudioChannelAgent;
  HTMLMediaElement* mOwner;

  // 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;
};

NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
    HTMLMediaElement::AudioChannelAgentCallback)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
    HTMLMediaElement::AudioChannelAgentCallback)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
    HTMLMediaElement::AudioChannelAgentCallback)
  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback)

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;
    }

    securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;

    MOZ_ASSERT(
        aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
    nsContentPolicyType contentPolicyType =
        aElement->IsHTMLElement(nsGkAtoms::audio)
            ? nsIContentPolicy::TYPE_INTERNAL_AUDIO
            : nsIContentPolicy::TYPE_INTERNAL_VIDEO;

    // 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));

    nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup();
    nsCOMPtr<nsIChannel> channel;
    nsresult rv = NS_NewChannelWithTriggeringPrincipal(
        getter_AddRefs(channel), aElement->mLoadingSrc,
        static_cast<Element*>(aElement), triggeringPrincipal, securityFlags,
        contentPolicyType,
        nullptr,  // aPerformanceStorage
        loadGroup,
        nullptr,  // aCallbacks
        nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
            nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
            nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);

    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);

    if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) {
      nsString initiatorType =
          aElement->IsHTMLElement(nsGkAtoms::audio) ? u"audio"_ns : u"video"_ns;
      timedChannel->SetInitiatorType(initiatorType);
    }

    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

--> --------------------

97%


¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.