Decoders uses 64-bit values to count frames, so we should use the same type in FrameStatistics. Because VideoPlaybackQuality can only use 32 bits (as defined in W3C specs), we need to ensure that imported FrameStatistics numbers can fit in 32 bits, while keeping their ratios the same. MozReview-Commit-ID: 3pUTGK0ekGv
324 lines
8.8 KiB
C++
324 lines
8.8 KiB
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 "nsIDOMHTMLSourceElement.h"
|
|
#include "mozilla/dom/HTMLVideoElement.h"
|
|
#include "mozilla/dom/HTMLVideoElementBinding.h"
|
|
#include "nsGenericHTMLElement.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsSize.h"
|
|
#include "nsError.h"
|
|
#include "nsNodeInfoManager.h"
|
|
#include "plbase64.h"
|
|
#include "nsXPCOMStrings.h"
|
|
#include "prlock.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "ImageContainer.h"
|
|
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsIXPConnect.h"
|
|
|
|
#include "nsITimer.h"
|
|
|
|
#include "MediaError.h"
|
|
#include "MediaDecoder.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/dom/WakeLock.h"
|
|
#include "mozilla/dom/power/PowerManagerService.h"
|
|
#include "mozilla/dom/Performance.h"
|
|
#include "mozilla/dom/VideoPlaybackQuality.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
NS_IMPL_NS_NEW_HTML_ELEMENT(Video)
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
static bool sVideoStatsEnabled;
|
|
|
|
NS_IMPL_ELEMENT_CLONE(HTMLVideoElement)
|
|
|
|
HTMLVideoElement::HTMLVideoElement(already_AddRefed<NodeInfo>& aNodeInfo)
|
|
: HTMLMediaElement(aNodeInfo)
|
|
, mUseScreenWakeLock(true)
|
|
{
|
|
}
|
|
|
|
HTMLVideoElement::~HTMLVideoElement()
|
|
{
|
|
}
|
|
|
|
nsresult HTMLVideoElement::GetVideoSize(nsIntSize* size)
|
|
{
|
|
if (!mMediaInfo.HasVideo()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (mDisableVideo) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
switch (mMediaInfo.mVideo.mRotation) {
|
|
case VideoInfo::Rotation::kDegree_90:
|
|
case VideoInfo::Rotation::kDegree_270: {
|
|
size->width = mMediaInfo.mVideo.mDisplay.height;
|
|
size->height = mMediaInfo.mVideo.mDisplay.width;
|
|
break;
|
|
}
|
|
case VideoInfo::Rotation::kDegree_0:
|
|
case VideoInfo::Rotation::kDegree_180:
|
|
default: {
|
|
size->height = mMediaInfo.mVideo.mDisplay.height;
|
|
size->width = mMediaInfo.mVideo.mDisplay.width;
|
|
break;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
HTMLVideoElement::ParseAttribute(int32_t aNamespaceID,
|
|
nsIAtom* aAttribute,
|
|
const nsAString& aValue,
|
|
nsAttrValue& aResult)
|
|
{
|
|
if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
|
|
return aResult.ParseSpecialIntValue(aValue);
|
|
}
|
|
|
|
return HTMLMediaElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
|
|
aResult);
|
|
}
|
|
|
|
void
|
|
HTMLVideoElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
|
|
nsRuleData* aData)
|
|
{
|
|
nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
|
|
nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
|
|
}
|
|
|
|
NS_IMETHODIMP_(bool)
|
|
HTMLVideoElement::IsAttributeMapped(const nsIAtom* 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;
|
|
}
|
|
|
|
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(NS_LITERAL_CSTRING("Accept"),
|
|
value,
|
|
false);
|
|
}
|
|
|
|
bool
|
|
HTMLVideoElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
|
|
{
|
|
return HasAttr(kNameSpaceID_None, nsGkAtoms::controls) ||
|
|
HTMLMediaElement::IsInteractiveHTMLContent(aIgnoreTabindex);
|
|
}
|
|
|
|
uint32_t HTMLVideoElement::MozParsedFrames() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
if (!sVideoStatsEnabled) {
|
|
return 0;
|
|
}
|
|
return mDecoder ? mDecoder->GetFrameStatistics().GetParsedFrames() : 0;
|
|
}
|
|
|
|
uint32_t HTMLVideoElement::MozDecodedFrames() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
if (!sVideoStatsEnabled) {
|
|
return 0;
|
|
}
|
|
return mDecoder ? mDecoder->GetFrameStatistics().GetDecodedFrames() : 0;
|
|
}
|
|
|
|
uint32_t HTMLVideoElement::MozPresentedFrames() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
if (!sVideoStatsEnabled) {
|
|
return 0;
|
|
}
|
|
return mDecoder ? mDecoder->GetFrameStatistics().GetPresentedFrames() : 0;
|
|
}
|
|
|
|
uint32_t HTMLVideoElement::MozPaintedFrames()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
if (!sVideoStatsEnabled) {
|
|
return 0;
|
|
}
|
|
layers::ImageContainer* container = GetImageContainer();
|
|
return container ? container->GetPaintCount() : 0;
|
|
}
|
|
|
|
double HTMLVideoElement::MozFrameDelay()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
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();
|
|
}
|
|
|
|
bool HTMLVideoElement::MozUseScreenWakeLock() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
return mUseScreenWakeLock;
|
|
}
|
|
|
|
void HTMLVideoElement::SetMozUseScreenWakeLock(bool aValue)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
|
|
mUseScreenWakeLock = aValue;
|
|
UpdateScreenWakeLock();
|
|
}
|
|
|
|
JSObject*
|
|
HTMLVideoElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return HTMLVideoElementBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
bool
|
|
HTMLVideoElement::NotifyOwnerDocumentActivityChangedInternal()
|
|
{
|
|
bool pauseElement = HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal();
|
|
UpdateScreenWakeLock();
|
|
return pauseElement;
|
|
}
|
|
|
|
already_AddRefed<VideoPlaybackQuality>
|
|
HTMLVideoElement::GetVideoPlaybackQuality()
|
|
{
|
|
DOMHighResTimeStamp creationTime = 0;
|
|
uint32_t totalFrames = 0;
|
|
uint32_t droppedFrames = 0;
|
|
uint32_t corruptedFrames = 0;
|
|
|
|
if (sVideoStatsEnabled) {
|
|
if (nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow()) {
|
|
Performance* perf = window->GetPerformance();
|
|
if (perf) {
|
|
creationTime = perf->Now();
|
|
}
|
|
}
|
|
|
|
if (mDecoder) {
|
|
FrameStatisticsData stats =
|
|
mDecoder->GetFrameStatistics().GetFrameStatisticsData();
|
|
if (sizeof(totalFrames) >= sizeof(stats.mParsedFrames)) {
|
|
totalFrames = stats.mParsedFrames;
|
|
droppedFrames = stats.mDroppedFrames;
|
|
} else {
|
|
auto maxStat = std::max(stats.mParsedFrames, stats.mDroppedFrames);
|
|
const auto maxNumber = std::numeric_limits<uint32_t>::max();
|
|
if (maxStat <= maxNumber) {
|
|
totalFrames = static_cast<uint32_t>(stats.mParsedFrames);
|
|
droppedFrames = static_cast<uint32_t>(stats.mDroppedFrames);
|
|
} else {
|
|
// Too big number(s) -> Resize everything to fit in 32 bits.
|
|
double ratio = double(maxNumber) / double(maxStat);
|
|
totalFrames = double(stats.mParsedFrames) * ratio;
|
|
droppedFrames = double(stats.mDroppedFrames) * ratio;
|
|
}
|
|
}
|
|
corruptedFrames = 0;
|
|
}
|
|
}
|
|
|
|
RefPtr<VideoPlaybackQuality> playbackQuality =
|
|
new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames,
|
|
corruptedFrames);
|
|
return playbackQuality.forget();
|
|
}
|
|
|
|
void
|
|
HTMLVideoElement::WakeLockCreate()
|
|
{
|
|
HTMLMediaElement::WakeLockCreate();
|
|
UpdateScreenWakeLock();
|
|
}
|
|
|
|
void
|
|
HTMLVideoElement::WakeLockRelease()
|
|
{
|
|
UpdateScreenWakeLock();
|
|
HTMLMediaElement::WakeLockRelease();
|
|
}
|
|
|
|
void
|
|
HTMLVideoElement::UpdateScreenWakeLock()
|
|
{
|
|
bool hidden = OwnerDoc()->Hidden();
|
|
|
|
if (mScreenWakeLock && (mPaused || hidden || !mUseScreenWakeLock)) {
|
|
ErrorResult rv;
|
|
mScreenWakeLock->Unlock(rv);
|
|
rv.SuppressException();
|
|
mScreenWakeLock = nullptr;
|
|
return;
|
|
}
|
|
|
|
if (!mScreenWakeLock && !mPaused && !hidden &&
|
|
mUseScreenWakeLock && HasVideo()) {
|
|
RefPtr<power::PowerManagerService> pmService =
|
|
power::PowerManagerService::GetInstance();
|
|
NS_ENSURE_TRUE_VOID(pmService);
|
|
|
|
ErrorResult rv;
|
|
mScreenWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("screen"),
|
|
OwnerDoc()->GetInnerWindow(),
|
|
rv);
|
|
}
|
|
}
|
|
|
|
void
|
|
HTMLVideoElement::Init()
|
|
{
|
|
Preferences::AddBoolVarCache(&sVideoStatsEnabled, "media.video_stats.enabled");
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|