Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  HTMLVideoElement.cpp

  Sprache: C
 

/* 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/HTMLVideoElement.h"

#include "mozilla/AppShutdown.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/HTMLVideoElementBinding.h"
#ifdef MOZ_WEBRTC
#  include "mozilla/dom/RTCStatsReport.h"
#endif
#include <algorithm>
#include <limits>

#include "FrameStatistics.h"
#include "ImageContainer.h"
#include "MediaDecoder.h"
#include "MediaDecoderStateMachine.h"
#include "MediaError.h"
#include "VideoFrameContainer.h"
#include "VideoOutput.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PictureInPictureEvent.h"
#include "mozilla/dom/PictureInPictureEventBinding.h"
#include "mozilla/dom/PictureInPictureService.h"
#include "mozilla/dom/PictureInPictureWindow.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TimeRanges.h"
#include "mozilla/dom/VideoPlaybackQuality.h"
#include "mozilla/dom/VideoStreamTrack.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "nsError.h"
#include "nsGenericHTMLElement.h"
#include "nsGkAtoms.h"
#include "nsIHttpChannel.h"
#include "nsNodeInfoManager.h"
#include "nsRFPService.h"
#include "nsSize.h"
#include "nsThreadUtils.h"
#include "plbase64.h"
#include "prlock.h"

extern mozilla::LazyLogModule gMediaElementLog;
#define LOG(msg, ...)                                                         \
  MOZ_LOG_FMT(gMediaElementLog, LogLevel::Debug, "HTMLVideoElement={}, " msg, \
              fmt::ptr(this), ##__VA_ARGS__)

nsGenericHTMLElement* NS_NewHTMLVideoElement(
    already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo,
    mozilla::dom::FromParser aFromParser) {
  RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
  auto* nim = nodeInfo->NodeInfoManager();
  mozilla::dom::HTMLVideoElement* element =
      new (nim) mozilla::dom::HTMLVideoElement(nodeInfo.forget());
  element->Init();
  return element;
}

namespace mozilla::dom {

nsresult HTMLVideoElement::Clone(mozilla::dom::NodeInfo* aNodeInfo,
                                 nsINode** aResult) const {
  *aResult = nullptr;
  RefPtr<mozilla::dom::NodeInfo> ni(aNodeInfo);
  auto* nim = ni->NodeInfoManager();
  HTMLVideoElement* it = new (nim) HTMLVideoElement(ni.forget());
  it->Init();
  nsCOMPtr<nsINode> kungFuDeathGrip = it;
  nsresult rv = const_cast<HTMLVideoElement*>(this)->CopyInnerTo(it);
  if (NS_SUCCEEDED(rv)) {
    kungFuDeathGrip.swap(*aResult);
  }
  return rv;
}

nsresult HTMLVideoElement::CopyInnerTo(Element* aDest) {
  nsresult rv = HTMLMediaElement::CopyInnerTo(aDest);
  NS_ENSURE_SUCCESS(rv, rv);
  HTMLVideoElement* dest = static_cast<HTMLVideoElement*>(aDest);

  // Cloning into a static document indicates we are creating a clone for
  // printing purposes only.
  //
  // If we are making a clone for printing, also clone a frame of video.
  // Avoid using GetVideoFrameContainer on this object, because that may
  // create a new video container for no reason.
  if (aDest->OwnerDoc()->IsStaticDocument() && mVideoFrameContainer) {
    // We can expect the source video to have frames, unless the decoder
    // has been suspended. When that happens, all frames are cleared.
    // Otherwise, even if the media is not seekable, once a frame is
    // decoded there should always be images available.
    //
    // When the decoder is suspended, we will only get fake frames.
    // This situation is similar to when JS needs a frame to use in,
    // eg., nsLayoutUtils::SurfaceFromElement() via drawImage().
    //
    // TODO: As an alternative, we could asynchronously resume decoding
    // and dispatch an event to copy an image when that succeeds.
    //
    // See bug 1295921 for synchronous decoding support:
    // https://bugzilla.mozilla.org/show_bug.cgi?id=1295921#c208
    AutoTArray<ImageContainer::OwningImage, 10> images;
    mVideoFrameContainer->GetImageContainer()->GetCurrentImages(&images);
    if (images.IsEmpty()) {
      LOG("no video images, printing with a suspended video decoder?");
      return rv;
    }

    // Ask for the video frame container only after we know we have a current
    // image from the source.
    // GetVideoFrameContainer might create a new video frame container,
    // which would be pointless if we won't be able to create any video data.
    VideoFrameContainer* const dstVideo = dest->GetVideoFrameContainer();
    NS_ENSURE_TRUE(dstVideo, rv);

    // Make a copy of the first image. This ensures we don't hold onto any
    // output buffers from a decoder while print preview is open.
    RefPtr<gfx::DataSourceSurface> dstSurface(CopyImage(images[0].mImage));
    if (!dstSurface) {
      MOZ_LOG_FMT(gMediaElementLog, LogLevel::Error,
                  "failed to copy video image");
      return rv;
    }
    RefPtr<layers::SourceSurfaceImage> dstImage =
        MakeAndAddRef<layers::SourceSurfaceImage>(dstSurface.get());

    dstVideo->SetCurrentFrame(dstImage->GetSize(), dstImage, TimeStamp(),
                              media::TimeUnit::Invalid(),
                              media::TimeUnit::Invalid());
  }
  return rv;
}

NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLVideoElement,
                                               HTMLMediaElement)

NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLVideoElement)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLVideoElement)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoFrameRequestManager)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTarget)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTargetPromise)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneSource)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPictureInPictureWindow)
  tmp->mSecondaryVideoOutput = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(HTMLMediaElement)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLVideoElement,
                                                  HTMLMediaElement)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoFrameRequestManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTarget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTargetPromise)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneSource)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPictureInPictureWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

HTMLVideoElement::HTMLVideoElement(already_AddRefed<NodeInfo> aNodeInfo)
    : HTMLMediaElement(std::move(aNodeInfo)),
      mVideoWatchManager(this, AbstractThread::MainThread()) {
  DecoderDoctorLogger::LogConstruction(this);
}

HTMLVideoElement::~HTMLVideoElement() {
  mVideoWatchManager.Shutdown();
  DecoderDoctorLogger::LogDestruction(this);
}

void HTMLVideoElement::UpdateMediaSize(const nsIntSize& aSize) {
  HTMLMediaElement::UpdateMediaSize(aSize);
  // If we have a clone target, we should update its size as well.
  if (mVisualCloneTarget) {
    Maybe<nsIntSize> newSize = Some(aSize);
    mVisualCloneTarget->Invalidate(ImageSizeChanged::Yes, newSize,
                                   ForceInvalidate::Yes);
  }
}

Maybe<CSSIntSize> HTMLVideoElement::GetVideoSize() const {
  if (!mMediaInfo.HasVideo()) {
    return Nothing();
  }

  if (mDisableVideo) {
    return Nothing();
  }

  CSSIntSize size;
  switch (mMediaInfo.mVideo.mRotation) {
    case VideoRotation::kDegree_90:
    case VideoRotation::kDegree_270: {
      size.width = mMediaInfo.mVideo.mDisplay.height;
      size.height = mMediaInfo.mVideo.mDisplay.width;
      break;
    }
    case VideoRotation::kDegree_0:
    case VideoRotation::kDegree_180:
    default: {
      size.height = mMediaInfo.mVideo.mDisplay.height;
      size.width = mMediaInfo.mVideo.mDisplay.width;
      break;
    }
  }
  return Some(size);
}

void HTMLVideoElement::Invalidate(ImageSizeChanged aImageSizeChanged,
                                  const Maybe<nsIntSize>& aNewIntrinsicSize,
                                  ForceInvalidate aForceInvalidate) {
  HTMLMediaElement::Invalidate(aImageSizeChanged, aNewIntrinsicSize,
                               aForceInvalidate);
  if (mVisualCloneTarget) {
    VideoFrameContainer* container =
        mVisualCloneTarget->GetVideoFrameContainer();
    if (container) {
      container->Invalidate();
    }
  }

  if (mVideoFrameRequestManager.IsEmpty()) {
    return;
  }

  if (RefPtr<ImageContainer> imageContainer = GetImageContainer()) {
    if (imageContainer->HasCurrentImage()) {
      OwnerDoc()->ScheduleVideoFrameCallbacks(this);
    }
  }
}

bool HTMLVideoElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
                                      const nsAString& aValue,
                                      nsIPrincipal* aMaybeScriptedPrincipal,
                                      nsAttrValue& aResult) {
  if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
    return aResult.ParseHTMLDimension(aValue);
  }

  return HTMLMediaElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                          aMaybeScriptedPrincipal, aResult);
}

void HTMLVideoElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
                                    const nsAttrValue* aValue,
                                    const nsAttrValue* aOldValue,
                                    nsIPrincipal* aMaybeScriptedPrincipal,
                                    bool aNotify) {
  if (aNameSpaceID == kNameSpaceID_None &&
      aName == nsGkAtoms::disablepictureinpicture && aValue) {
    if (OwnerDoc()->GetPictureInPictureElementInternal() == this) {
      PictureInPictureService::DispatchExitPictureInPictureRunnable(
          /* aPromise */ nullptr, this);
    }
  }

  HTMLMediaElement::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue,
                                 aMaybeScriptedPrincipal, aNotify);
}

void HTMLVideoElement::MapAttributesIntoRule(
    MappedDeclarationsBuilder& aBuilder) {
  MapImageSizeAttributesInto(aBuilder, MapAspectRatio::Yes);
  MapCommonAttributesInto(aBuilder);
}

NS_IMETHODIMP_(bool)
HTMLVideoElement::IsAttributeMapped(const nsAtom* aAttribute) const {
  static const MappedAttributeEntry attributes[] = {
      {nsGkAtoms::width}, {nsGkAtoms::height}, {nullptr}};

  static const MappedAttributeEntry* const map[] = {attributes,
                                                    sCommonAttributeMap};

  return FindAttributeDependence(aAttribute, map);
}

nsMapRuleToAttributesFunc HTMLVideoElement::GetAttributeMappingFunction()
    const {
  return &MapAttributesIntoRule;
}

void HTMLVideoElement::UnbindFromTree(UnbindContext& aContext) {
  if (mVisualCloneSource) {
    mVisualCloneSource->EndCloningVisually();
  } else if (mVisualCloneTarget) {
    AsyncEventDispatcher::RunDOMEventWhenSafe(
        *this, u"MozStopPictureInPicture"_ns, CanBubble::eNo,
        ChromeOnlyDispatch::eYes);
    EndCloningVisually();
  }

  HTMLMediaElement::UnbindFromTree(aContext);
}

nsresult HTMLVideoElement::SetAcceptHeader(nsIHttpChannel* aChannel) {
  nsAutoCString value(
      "video/webm,"
      "video/ogg,"
      "video/*;q=0.9,"
      "application/ogg;q=0.7,"
      "audio/*;q=0.6,*/*;q=0.5");

  return aChannel->SetRequestHeader("Accept"_ns, value, false);
}

bool HTMLVideoElement::IsInteractiveHTMLContent() const {
  return HasAttr(nsGkAtoms::controls) ||
         HTMLMediaElement::IsInteractiveHTMLContent();
}

gfx::IntSize HTMLVideoElement::GetVideoIntrinsicDimensions() {
  const auto& sz = mMediaInfo.mVideo.mDisplay;

  // Prefer the size of the container as it's more up to date.
  return ToMaybeRef(mVideoFrameContainer.get())
      .map([&](auto& aVFC) { return aVFC.CurrentIntrinsicSize().valueOr(sz); })
      .valueOr(sz);
}

uint32_t HTMLVideoElement::VideoWidth() {
  if (!HasVideo()) {
    return 0;
  }
  gfx::IntSize size = GetVideoIntrinsicDimensions();
  if (mMediaInfo.mVideo.mRotation == VideoRotation::kDegree_90 ||
      mMediaInfo.mVideo.mRotation == VideoRotation::kDegree_270) {
    return size.height;
  }
  return size.width;
}

uint32_t HTMLVideoElement::VideoHeight() {
  if (!HasVideo()) {
    return 0;
  }
  gfx::IntSize size = GetVideoIntrinsicDimensions();
  if (mMediaInfo.mVideo.mRotation == VideoRotation::kDegree_90 ||
      mMediaInfo.mVideo.mRotation == VideoRotation::kDegree_270) {
    return size.width;
  }
  return size.height;
}

uint32_t HTMLVideoElement::MozParsedFrames() const {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
  if (!IsVideoStatsEnabled()) {
    return 0;
  }

  if (OwnerDoc()->ShouldResistFingerprinting(
          RFPTarget::VideoElementMozFrames)) {
    return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
  }

  return mDecoder ? mDecoder->GetFrameStatistics().GetParsedFrames() : 0;
}

uint32_t HTMLVideoElement::MozDecodedFrames() const {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
  if (!IsVideoStatsEnabled()) {
    return 0;
  }

  if (OwnerDoc()->ShouldResistFingerprinting(
          RFPTarget::VideoElementMozFrames)) {
    return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
  }

  return mDecoder ? mDecoder->GetFrameStatistics().GetDecodedFrames() : 0;
}

uint32_t HTMLVideoElement::MozPresentedFrames() {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
  if (!IsVideoStatsEnabled()) {
    return 0;
  }

  if (OwnerDoc()->ShouldResistFingerprinting(
          RFPTarget::VideoElementMozFrames)) {
    return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
                                                   VideoWidth(), VideoHeight());
  }

  return mDecoder ? mDecoder->GetFrameStatistics().GetPresentedFrames() : 0;
}

uint32_t HTMLVideoElement::MozPaintedFrames() {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
  if (!IsVideoStatsEnabled()) {
    return 0;
  }

  if (OwnerDoc()->ShouldResistFingerprinting(
          RFPTarget::VideoElementMozFrames)) {
    return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
                                                   VideoWidth(), VideoHeight());
  }

  layers::ImageContainer* container = GetImageContainer();
  return container ? container->GetPaintCount() : 0;
}

double HTMLVideoElement::MozFrameDelay() {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");

  if (!IsVideoStatsEnabled() || OwnerDoc()->ShouldResistFingerprinting(
                                    RFPTarget::VideoElementMozFrameDelay)) {
    return 0.0;
  }

  VideoFrameContainer* container = GetVideoFrameContainer();
  // Hide negative delays. Frame timing tweaks in the compositor (e.g.
  // adding a bias value to prevent multiple dropped/duped frames when
  // frame times are aligned with composition times) may produce apparent
  // negative delay, but we shouldn't report that.
  return container ? std::max(0.0, container->GetFrameDelay()) : 0.0;
}

bool HTMLVideoElement::MozHasAudio() const {
  MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
  return HasAudio();
}

JSObject* HTMLVideoElement::WrapNode(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
  return HTMLVideoElement_Binding::Wrap(aCx, this, aGivenProto);
}

already_AddRefed<VideoPlaybackQuality>
HTMLVideoElement::GetVideoPlaybackQuality() {
  DOMHighResTimeStamp creationTime = 0;
  uint32_t totalFrames = 0;
  uint32_t droppedFrames = 0;

  if (IsVideoStatsEnabled()) {
    if (nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow()) {
      Performance* perf = window->GetPerformance();
      if (perf) {
        creationTime = perf->Now();
      }
    }

    if (mDecoder) {
      if (OwnerDoc()->ShouldResistFingerprinting(
              RFPTarget::VideoElementPlaybackQuality)) {
        totalFrames = nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
        droppedFrames = nsRFPService::GetSpoofedDroppedFrames(
            TotalPlayTime(), VideoWidth(), VideoHeight());
      } else {
        FrameStatistics* stats = &mDecoder->GetFrameStatistics();
        if (sizeof(totalFrames) >= sizeof(stats->GetParsedFrames())) {
          totalFrames = stats->GetTotalFrames();
          droppedFrames = stats->GetDroppedFrames();
        } else {
          uint64_t total = stats->GetTotalFrames();
          const auto maxNumber = std::numeric_limits<uint32_t>::max();
          if (total <= maxNumber) {
            totalFrames = uint32_t(total);
            droppedFrames = uint32_t(stats->GetDroppedFrames());
          } else {
            // Too big number(s) -> Resize everything to fit in 32 bits.
            double ratio = double(maxNumber) / double(total);
            totalFrames = maxNumber;  // === total * ratio
            droppedFrames = uint32_t(double(stats->GetDroppedFrames()) * ratio);
          }
        }
      }
      if (!StaticPrefs::media_video_dropped_frame_stats_enabled()) {
        droppedFrames = 0;
      }
    }
  }

  RefPtr<VideoPlaybackQuality> playbackQuality =
      new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames);
  return playbackQuality.forget();
}

void HTMLVideoElement::WakeLockRelease() {
  HTMLMediaElement::WakeLockRelease();
  ReleaseVideoWakeLockIfExists();
}

void HTMLVideoElement::UpdateWakeLock() {
  HTMLMediaElement::UpdateWakeLock();
  if (!mPaused) {
    CreateVideoWakeLockIfNeeded();
  } else {
    ReleaseVideoWakeLockIfExists();
  }
}

bool HTMLVideoElement::ShouldCreateVideoWakeLock() const {
  if (!StaticPrefs::media_video_wakelock()) {
    return false;
  }
  // Only request wake lock for video with audio or video from media
  // stream, because non-stream video without audio is often used as a
  // background image.
  //
  // Some web conferencing sites route audio outside the video element,
  // and would not be detected unless we check for media stream, so do
  // that below.
  //
  // Media streams generally aren't used as background images, though if
  // they were we'd get false positives. If this is an issue, we could
  // check for media stream AND document has audio playing (but that was
  // tricky to do).
  return HasVideo() && (mSrcStream || HasAudio());
}

void HTMLVideoElement::CreateVideoWakeLockIfNeeded() {
  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
    return;
  }
  if (!mScreenWakeLock && ShouldCreateVideoWakeLock()) {
    RefPtr<power::PowerManagerService> pmService =
        power::PowerManagerService::GetInstance();
    NS_ENSURE_TRUE_VOID(pmService);

    ErrorResult rv;
    mScreenWakeLock = pmService->NewWakeLock(u"video-playing"_ns,
                                             OwnerDoc()->GetInnerWindow(), rv);
  }
}

void HTMLVideoElement::ReleaseVideoWakeLockIfExists() {
  if (mScreenWakeLock) {
    ErrorResult rv;
    mScreenWakeLock->Unlock(rv);
    rv.SuppressException();
    mScreenWakeLock = nullptr;
    return;
  }
}

bool HTMLVideoElement::SetVisualCloneTarget(
    RefPtr<HTMLVideoElement> aVisualCloneTarget,
    RefPtr<Promise> aVisualCloneTargetPromise) {
  MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneSource,
                        "Can't clone a video element that is already a clone.");
  if (!aVisualCloneTarget || !mVisualCloneSource) {
    mVisualCloneTarget = std::move(aVisualCloneTarget);
    mVisualCloneTargetPromise = std::move(aVisualCloneTargetPromise);
    return true;
  }
  return false;
}

bool HTMLVideoElement::SetVisualCloneSource(
    RefPtr<HTMLVideoElement> aVisualCloneSource) {
  MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneTarget,
                        "Can't clone a video element that is already a "
                        "clone.");
  if (!aVisualCloneSource || !mVisualCloneTarget) {
    mVisualCloneSource = std::move(aVisualCloneSource);
    return true;
  }
  return false;
}

/* static */
already_AddRefed<gfx::DataSourceSurface> HTMLVideoElement::CopyImage(
    layers::Image* aImage) {
  RefPtr<gfx::SourceSurface> surface = aImage->GetAsSourceSurface();
  if (!surface) {
    return nullptr;
  }

  RefPtr<gfx::DataSourceSurface> data = surface->GetDataSurface();
  if (!data) {
    return nullptr;
  }

  gfx::DataSourceSurface::ScopedMap read(data, gfx::DataSourceSurface::READ);
  if (!read.IsMapped()) {
    return nullptr;
  }

  RefPtr<gfx::DataSourceSurface> copy = gfx::CreateDataSourceSurfaceFromData(
      data->GetSize(), data->GetFormat(), read.GetData(), read.GetStride());

  MOZ_ASSERT_IF(copy, data->GetSize() == copy->GetSize());
  MOZ_ASSERT_IF(copy, data->GetFormat() == copy->GetFormat());
  return copy.forget();
}

/* static */
bool HTMLVideoElement::IsVideoStatsEnabled() {
  return StaticPrefs::media_video_stats_enabled();
}

double HTMLVideoElement::TotalPlayTime() const {
  double total = 0.0;

  if (mPlayed) {
    uint32_t timeRangeCount = mPlayed->Length();

    for (uint32_t i = 0; i < timeRangeCount; i++) {
      double begin = mPlayed->Start(i);
      double end = mPlayed->End(i);
      total += end - begin;
    }

    if (mCurrentPlayRangeStart != -1.0) {
      double now = CurrentTime();
      if (mCurrentPlayRangeStart != now) {
        total += now - mCurrentPlayRangeStart;
      }
    }
  }

  return total;
}

already_AddRefed<Promise> HTMLVideoElement::CloneElementVisually(
    HTMLVideoElement& aTargetVideo, ErrorResult& aRv) {
  MOZ_ASSERT(aTargetVideo.IsInComposedDoc(),
             "Can't clone to a video that's not bound to a DOM tree.");
  if (!aTargetVideo.IsInComposedDoc()) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
  if (!win) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(win->AsGlobal(), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  // Do we already have a visual clone target? If so, shut it down.
  if (mVisualCloneTarget) {
    EndCloningVisually();
  }

  // If there's a poster set on the target video, clear it, otherwise
  // it'll display over top of the cloned frames.
  aTargetVideo.UnsetHTMLAttr(nsGkAtoms::poster, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  if (!SetVisualCloneTarget(&aTargetVideo, promise)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  if (!aTargetVideo.SetVisualCloneSource(this)) {
    mVisualCloneTarget = nullptr;
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  aTargetVideo.SetMediaInfo(mMediaInfo);

  if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
    NotifyUAWidgetSetupOrChange();
  }

  MaybeBeginCloningVisually();

  return promise.forget();
}

void HTMLVideoElement::StopCloningElementVisually() {
  if (mVisualCloneTarget) {
    EndCloningVisually();
  }
}

void HTMLVideoElement::MaybeBeginCloningVisually() {
  if (!mVisualCloneTarget) {
    return;
  }

  if (mDecoder) {
    mDecoder->SetSecondaryVideoContainer(
        mVisualCloneTarget->GetVideoFrameContainer());
    NotifyDecoderActivityChanges();
    UpdateMediaControlAfterPictureInPictureModeChanged();
  } else if (mSrcStream) {
    VideoFrameContainer* container =
        mVisualCloneTarget->GetVideoFrameContainer();
    if (container) {
      mSecondaryVideoOutput = MakeRefPtr<FirstFrameVideoOutput>(
          container, AbstractThread::MainThread());
      mVideoWatchManager.Watch(
          mSecondaryVideoOutput->mFirstFrameRendered,
          &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered);
      SetSecondaryMediaStreamRenderer(container, mSecondaryVideoOutput);
    }
    UpdateMediaControlAfterPictureInPictureModeChanged();
  }
}

void HTMLVideoElement::EndCloningVisually() {
  MOZ_ASSERT(mVisualCloneTarget);

  if (mDecoder) {
    mDecoder->SetSecondaryVideoContainer(nullptr);
    NotifyDecoderActivityChanges();
  } else if (mSrcStream) {
    if (mSecondaryVideoOutput) {
      mVideoWatchManager.Unwatch(
          mSecondaryVideoOutput->mFirstFrameRendered,
          &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered);
      mSecondaryVideoOutput = nullptr;
    }
    SetSecondaryMediaStreamRenderer(nullptr);
  }

  (void)mVisualCloneTarget->SetVisualCloneSource(nullptr);
  (void)SetVisualCloneTarget(nullptr);

  UpdateMediaControlAfterPictureInPictureModeChanged();

  if (IsInComposedDoc() && OwnerDoc()->IsCurrentActiveDocument() &&
      !StaticPrefs::media_cloneElementVisually_testing()) {
    NotifyUAWidgetSetupOrChange();
  }

  ClosePictureInPictureWindowAndFireEvent();
}

void HTMLVideoElement::OnSecondaryVideoContainerInstalled(
    const RefPtr<VideoFrameContainer>& aSecondaryContainer) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT_IF(mVisualCloneTargetPromise, mVisualCloneTarget);
  if (!mVisualCloneTargetPromise) {
    // Clone target was unset.
    return;
  }

  VideoFrameContainer* container = mVisualCloneTarget->GetVideoFrameContainer();
  if (NS_WARN_IF(container != aSecondaryContainer)) {
    // Not the right container.
    return;
  }

  NS_DispatchToCurrentThread(NewRunnableMethod(
      "Promise::MaybeResolveWithUndefined", mVisualCloneTargetPromise,
      &Promise::MaybeResolveWithUndefined));
  mVisualCloneTargetPromise = nullptr;
}

void HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered() {
  OnSecondaryVideoContainerInstalled(
      mVisualCloneTarget->GetVideoFrameContainer());
}

void HTMLVideoElement::OnVisibilityChange(Visibility aNewVisibility) {
  HTMLMediaElement::OnVisibilityChange(aNewVisibility);

  // See the alternative part after step 4, but we only pause/resume invisible
  // autoplay for non-audible video, which is different from the spec. This
  // behavior seems aiming to reduce the power consumption without interering
  // users, and Chrome and Safari also chose to do that only for non-audible
  // video, so we want to match them in order to reduce webcompat issue.
  // https://html.spec.whatwg.org/multipage/media.html#ready-states:eligible-for-autoplay-2
  if (!HasAttr(nsGkAtoms::autoplay) || IsAudible()) {
    return;
  }

  if (aNewVisibility == Visibility::ApproximatelyVisible && mPaused &&
      IsEligibleForAutoplay() && AllowedToPlay()) {
    LOG("resume invisible paused autoplay video");
    RunAutoplay();
  }

  // We need to consider the Pip window as well, which won't reflect in the
  // visibility event.
  if ((aNewVisibility == Visibility::ApproximatelyNonVisible &&
       !IsCloningElementVisually()) &&
      mCanAutoplayFlag) {
    // Defer pausing the element to avoid changing the element state during the
    // style refresh.
    NS_DispatchToMainThread(NS_NewRunnableFunction(
        __func__, [self = RefPtr<HTMLMediaElement>(this), this] {
          // https://html.spec.whatwg.org/multipage/media.html#ready-states:intersect-the-viewport-3
          if (mVisibilityState != Visibility::ApproximatelyNonVisible ||
              !mCanAutoplayFlag) {
            return;
          }
          LOG("pause non-audible autoplay video when it's invisible");
          PauseInternal();
          mCanAutoplayFlag = true;
        }));
    return;
  }
}

void HTMLVideoElement::ResetState() {
  HTMLMediaElement::ResetState();
  mLastPresentedFrameID = layers::kContainerFrameID_Invalid;
}

bool HTMLVideoElement::WillFireVideoFrameCallbacks(
    const TimeStamp& aNowTime, const Maybe<TimeStamp>& aNextTickTime,
    VideoFrameCallbackMetadata& aMd) {
  // Attempt to find the next image to be presented on this tick. Note that
  // composited will be accurate only if the element is visible.
  AutoTArray<ImageContainer::OwningImage, 4> images;
  if (RefPtr<layers::ImageContainer> container = GetImageContainer()) {
    container->GetCurrentImages(&images);
  }

  // If we did not find any current images, we must have fired too early, or we
  // are in the process of shutting down. Wait for the next invalidation.
  if (images.IsEmpty()) {
    return false;
  }

  // We are guaranteed that the images are in timestamp order. It is possible we
  // are already behind if the compositor notifications have not been processed
  // yet, so as per the standard, this is a best effort attempt at synchronizing
  // with the state of the GPU process.
  const ImageContainer::OwningImage* selected = nullptr;
  bool composited = false;
  for (const auto& image : images) {
    if (image.mTimeStamp <= aNowTime) {
      // Image should already have been composited. Because we might not be in
      // the display list, we cannot rely upon its mComposited status, and
      // should just assume it has indeed been composited.
      selected = ℑ
      composited = true;
    } else if (!aNextTickTime || image.mTimeStamp <= aNextTickTime.ref()) {
      // Image should be the next to be composited. mComposited will be false
      // if the compositor hasn't rendered the frame yet or notified us of the
      // render yet, but it is in progress. If it is true, then we know the
      // next vsync will display the frame.
      selected = ℑ
      composited = false;
    } else {
      // Image is for a future composition.
      break;
    }
  }

  // If all of the available images are for future compositions, we must have
  // fired too early. Wait for the next invalidation.
  if (!selected || selected->mFrameID == layers::kContainerFrameID_Invalid ||
      selected->mFrameID == mLastPresentedFrameID) {
    return false;
  }

  // If we have got a dummy frame, then we must have suspended decoding and have
  // no actual frame to present. This should only happen if we raced on
  // requesting a callback, and the media state machine advancing.
  gfx::IntSize frameSize = selected->mImage->GetSize();
  if (NS_WARN_IF(frameSize.IsEmpty())) {
    return false;
  }

  // If we have already displayed the expected frame, we need to make the
  // display time match the presentation time to indicate it is already
  // complete.
  if (composited) {
    aMd.mExpectedDisplayTime = aMd.mPresentationTime;
  }

  MOZ_ASSERT(!frameSize.IsEmpty());

  aMd.mWidth = frameSize.width;
  aMd.mHeight = frameSize.height;

  // If we were not provided a valid media time, then we need to estimate based
  // on the CurrentTime from the element.
  aMd.mMediaTime = selected->mMediaTime.IsValid()
                       ? selected->mMediaTime.ToSeconds()
                       : CurrentTime();

  // If we have a processing duration, we need to round it.
  //
  // https://wicg.github.io/video-rvfc/#security-and-privacy
  //
  // 5. Security and Privacy Considerations.
  // ... processingDuration exposes some under-the-hood performance information
  // about the video pipeline ... We therefore propose a resolution of 100μs,
  // which is still useful for automated quality analysis, but doesn’t offer any
  // new sources of high resolution information.
  if (selected->mProcessingDuration.IsValid()) {
    aMd.mProcessingDuration.Construct(
        selected->mProcessingDuration.ToBase(10000).ToSeconds());
  }

#ifdef MOZ_WEBRTC
  // If given, this is the RTP timestamp from the last packet for the frame.
  if (selected->mRtpTimestamp) {
    aMd.mRtpTimestamp.Construct(*selected->mRtpTimestamp);
  }

  // For remote sources, the capture and receive time are represented as WebRTC
  // timestamps relative to an origin that is specific to the WebRTC session.
  const bool hasCaptureTimeNtp = selected->mWebrtcCaptureTime.is<int64_t>();
  const bool hasReceiveTimeReal = selected->mWebrtcReceiveTime.isSome();
  const auto* tsMaker = mSelectedVideoStreamTrack
                            ? mSelectedVideoStreamTrack->GetTimestampMaker()
                            : nullptr;
  auto* win = OwnerDoc()->GetInnerWindow();
  auto* perf = win ? win->GetPerformance() : nullptr;

  // WebRTC timestamps are converted to document-local high resolution
  // timestamps to have a shared time base (see bug 1937776)
  if (tsMaker && perf) {
    if (hasCaptureTimeNtp) {
      const int64_t capt64 = selected->mWebrtcCaptureTime.as<int64_t>();
      webrtc::Timestamp captTs = webrtc::Timestamp::Millis(capt64);
      aMd.mCaptureTime.Construct(
          RTCStatsTimestamp::FromNtp(*tsMaker, captTs).ToDom() -
          perf->TimeOrigin());
    }

    if (hasReceiveTimeReal) {
      const int64_t recvUs = selected->mWebrtcReceiveTime.value();
      webrtc::Timestamp recvTs = webrtc::Timestamp::Micros(recvUs);
      aMd.mReceiveTime.Construct(
          RTCStatsTimestamp::FromRealtime(*tsMaker, recvTs).ToDom() -
          perf->TimeOrigin());
    }
  }

  // The capture time may already be a high resolution timestamp from the
  // camera pipeline indicating when the sample was captured.
  if (perf && selected->mWebrtcCaptureTime.is<TimeStamp>()) {
    aMd.mCaptureTime.Construct(perf->TimeStampToDOMHighResForRendering(
        selected->mWebrtcCaptureTime.as<TimeStamp>()));
  }
#endif

  // Note that if we seek, or restart a video, we may present an earlier frame
  // that we already presented with the same ID. This would cause presented
  // frames to go backwards when it must be monotonically increasing. Presented
  // frames cannot simply increment by 1 each request callback because it is
  // also used by the caller to determine if frames were missed. As such, we
  // will typically use the difference between the current frame and the last
  // presented via the callback, but otherwise assume a single frame due to the
  // seek.
  mPresentedFrames +=
      selected->mFrameID > 1 && selected->mFrameID > mLastPresentedFrameID
          ? selected->mFrameID - mLastPresentedFrameID
          : 1;
  mLastPresentedFrameID = selected->mFrameID;

  // Presented frames is a bit of a misnomer from a rendering perspective,
  // because we still need to advance regardless of composition. Video elements
  // that are outside of the DOM, or are not visible, still advance the video in
  // the background, and presumably the caller still needs some way to know how
  // many frames we have advanced.
  aMd.mPresentedFrames = mPresentedFrames;

  NS_DispatchToMainThread(NewRunnableMethod(
      "HTMLVideoElement::FinishedVideoFrameRequestCallbacks", this,
      &HTMLVideoElement::FinishedVideoFrameRequestCallbacks));

  return true;
}

void HTMLVideoElement::FinishedVideoFrameRequestCallbacks() {
  // After we have executed the rVFC and rAF callbacks, we need to check whether
  // or not we have scheduled more. If we did not, then we need to notify the
  // decoder, because it may be the only thing keeping the decoder fully active.
  if (!HasPendingCallbacks()) {
    NotifyDecoderActivityChanges();
  }
}

uint32_t HTMLVideoElement::RequestVideoFrameCallback(
    VideoFrameRequestCallback& aCallback, ErrorResult& aRv) {
  bool hasPending = HasPendingCallbacks();
  uint32_t handle = 0;
  aRv = mVideoFrameRequestManager.Schedule(aCallback, &handle);
  if (!hasPending && HasPendingCallbacks()) {
    NotifyDecoderActivityChanges();
  }
  return handle;
}

void HTMLVideoElement::CancelVideoFrameCallback(uint32_t aHandle) {
  if (mVideoFrameRequestManager.Cancel(aHandle) && !HasPendingCallbacks()) {
    NotifyDecoderActivityChanges();
  }
}

void HTMLVideoElement::ClosePictureInPictureWindowAndFireEvent() {
  // If this is not GetPictureInPictureElementInternal, then
  // we did cloned this video using our native feature.
  if (OwnerDoc()->GetPictureInPictureElementInternal() != this) {
    return;
  }

  // TODO: address PIP spec. so that "Unset pictureInPictureElement" is done
  // before firing event as this can trigger script to run.
  // https://w3c.github.io/picture-in-picture/#exit-pip
  // 4. Unset pictureInPictureElement.
  OwnerDoc()->SetPictureInPictureElement(nullptr);
  if (RefPtr<PictureInPictureWindow> pipWindow =
          std::move(mPictureInPictureWindow)) {
    pipWindow->Close();
  }
}

// https://w3c.github.io/picture-in-picture/#dom-htmlvideoelement-requestpictureinpicture
already_AddRefed<Promise> HTMLVideoElement::RequestPictureInPicture(
    ErrorResult& aRv) {
  PictureInPictureService::EnsureInit();
  // 9. Let p be a new promise created in this’s relevant realm.
  RefPtr<Promise> p = Promise::Create(GetRelevantGlobal(), aRv);
  if (!p) {
    return nullptr;
  }

  // 1. If Picture-in-Picture support is false, return a promise rejected with
  // NotSupportedError DOMException.
  if (!PictureInPictureWindow::PictureInPictureEnabled()) {
    p->MaybeRejectWithNotSupportedError("Picture-In-Picture is not enabled");
    return p.forget();
  }

  // 2. If this’s node document is not allowed to use the policy-controlled
  // feature named "picture-in-picture", reject p with a SecurityError
  // exception and return p.
  if (!FeaturePolicyUtils::IsFeatureAllowed(OwnerDoc(),
                                            u"picture-in-picture"_ns)) {
    p->MaybeRejectWithSecurityError(
        "Permissions policy: picture-in-picture not allowed");
    return p.forget();
  }

  // 3. If this’s readyState attribute is HAVE_NOTHING, reject p with an
  // InvalidStateError exception and return p.
  if (ReadyState() == HTMLMediaElement_Binding::HAVE_NOTHING) {
    p->MaybeRejectWithInvalidStateError("Video readyState is HAVE_NOTHING");
    return p.forget();
  }

  // 4. If this has no video track, reject p with an InvalidStateError
  // exception and return p.
  if (!HasVideo()) {
    p->MaybeRejectWithInvalidStateError("Video element has no video track");
    return p.forget();
  }

  // 5. If this’s disablePictureInPicture is true, the user agent MAY reject p
  // with an InvalidStateError exception and return p.
  if (DisablePictureInPicture()) {
    p->MaybeRejectWithInvalidStateError(
        "Picture-in-Picture is disabled on this video");
    return p.forget();
  }

  // 6. If pictureInPictureElement is null:
  // 6.1. If this’s relevant global object does not have a transient
  // activation reject p with a NotAllowedError exception and return p.
  // 6.2. Consume user activation given this’s relevant global object.
  Document* doc = OwnerDoc();
  const Element* pictureInPictureElement =
      doc->GetPictureInPictureElementInternal();
  if (!pictureInPictureElement &&
      !doc->ConsumeTransientUserGestureActivation()) {
    p->MaybeRejectWithNotAllowedError(
        "Picture-in-Picture requires user activation");
    return p.forget();
  }

  // 7. If this is pictureInPictureElement:
  if (this == pictureInPictureElement) {
    // 1. Return a promise resolved with with the Picture-in-Picture window
    // associated with pictureInPictureElement.
    auto pipWindow = mPictureInPictureWindow;
    p->MaybeResolve(pipWindow);
    return p.forget();
  }

  // 8. Let global be this’s relevant global object.
  // 9. Return promise, and run the remaining steps in parallel:
  NS_DispatchToMainThread(NS_NewRunnableFunction(
      __func__, [promise = RefPtr{p},
                 video = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
        PictureInPictureService::OpenPictureInPictureWindow(promise, video);
      }));
  return p.forget();
}

// Picture-in-Picture event handlers
EventHandlerNonNull* HTMLVideoElement::GetOnenterpictureinpicture() {
  return EventTarget::GetEventHandler(nsGkAtoms::onenterpictureinpicture);
}

void HTMLVideoElement::SetOnenterpictureinpicture(
    EventHandlerNonNull* aCallback) {
  EventTarget::SetEventHandler(nsGkAtoms::onenterpictureinpicture, aCallback);
}

EventHandlerNonNull* HTMLVideoElement::GetOnleavepictureinpicture() {
  return EventTarget::GetEventHandler(nsGkAtoms::onleavepictureinpicture);
}

void HTMLVideoElement::SetOnleavepictureinpicture(
    EventHandlerNonNull* aCallback) {
  EventTarget::SetEventHandler(nsGkAtoms::onleavepictureinpicture, aCallback);
}

void HTMLVideoElement::SetAssociatedPictureInPictureWindow(
    PictureInPictureWindow* aWindow) {
  mPictureInPictureWindow = aWindow;
}

PictureInPictureWindow* HTMLVideoElement::GetAssociatedPictureInPictureWindow()
    const {
  return mPictureInPictureWindow;
}

}  // namespace mozilla::dom

#undef LOG

Messung V0.5 in Prozent
C=94 H=95 G=94

¤ Dauer der Verarbeitung: 0.12 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

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 und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002