зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1453176 - Add telemetry to report fulfilment of HTMLMediaElement.play(). r=bryce
We'd like to know the proportion of HTMLMediaElement.play() calls that are rejected due to autoplay being blocked. There are also other conditions that cause us to reject the promise returned by HTMLMediaElement.play(), so add telemetry to report all the identifyable conditions under which play() succeeds or fails. MozReview-Commit-ID: AZ67WWXaowN --HG-- extra : rebase_source : 4a164cb0b4fb7fb6944cd371c6e90dde021a4dc0
This commit is contained in:
Родитель
f1d278f735
Коммит
5328fa5cb9
|
@ -10,6 +10,7 @@
|
|||
#include "mozilla/dom/HTMLAudioElement.h"
|
||||
#include "mozilla/dom/HTMLVideoElement.h"
|
||||
#include "mozilla/dom/ElementInlines.h"
|
||||
#include "mozilla/dom/PlayPromise.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
|
@ -124,7 +125,7 @@
|
|||
#undef GetCurrentTime
|
||||
#endif
|
||||
|
||||
static mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
|
||||
mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
|
||||
static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
|
||||
|
||||
#define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
|
||||
|
@ -182,7 +183,7 @@ static const unsigned short MEDIA_ERR_DECODE = 3;
|
|||
static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
|
||||
|
||||
static void
|
||||
ResolvePromisesWithUndefined(const nsTArray<RefPtr<Promise>>& aPromises)
|
||||
ResolvePromisesWithUndefined(const nsTArray<RefPtr<PlayPromise>>& aPromises)
|
||||
{
|
||||
for (auto& promise : aPromises) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
|
@ -190,7 +191,7 @@ ResolvePromisesWithUndefined(const nsTArray<RefPtr<Promise>>& aPromises)
|
|||
}
|
||||
|
||||
static void
|
||||
RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError)
|
||||
RejectPromises(const nsTArray<RefPtr<PlayPromise>>& aPromises, nsresult aError)
|
||||
{
|
||||
for (auto& promise : aPromises) {
|
||||
promise->MaybeReject(aError);
|
||||
|
@ -301,16 +302,19 @@ public:
|
|||
*/
|
||||
class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEvent
|
||||
{
|
||||
nsTArray<RefPtr<Promise>> mPromises;
|
||||
nsTArray<RefPtr<PlayPromise>> mPromises;
|
||||
nsresult mError;
|
||||
|
||||
public:
|
||||
nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement,
|
||||
nsTArray<RefPtr<Promise>>&& aPromises,
|
||||
nsresult aError = NS_OK)
|
||||
: nsMediaEvent("HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner", aElement)
|
||||
, mPromises(Move(aPromises))
|
||||
, mError(aError)
|
||||
nsResolveOrRejectPendingPlayPromisesRunner(
|
||||
HTMLMediaElement* aElement,
|
||||
nsTArray<RefPtr<PlayPromise>>&& aPromises,
|
||||
nsresult aError = NS_OK)
|
||||
: nsMediaEvent(
|
||||
"HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner",
|
||||
aElement)
|
||||
, mPromises(Move(aPromises))
|
||||
, mError(aError)
|
||||
{
|
||||
mElement->mPendingPlayPromisesRunners.AppendElement(this);
|
||||
}
|
||||
|
@ -338,10 +342,11 @@ public:
|
|||
class HTMLMediaElement::nsNotifyAboutPlayingRunner : public nsResolveOrRejectPendingPlayPromisesRunner
|
||||
{
|
||||
public:
|
||||
nsNotifyAboutPlayingRunner(HTMLMediaElement* aElement,
|
||||
nsTArray<RefPtr<Promise>>&& aPendingPlayPromises)
|
||||
: nsResolveOrRejectPendingPlayPromisesRunner(aElement,
|
||||
Move(aPendingPlayPromises))
|
||||
nsNotifyAboutPlayingRunner(
|
||||
HTMLMediaElement* aElement,
|
||||
nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
|
||||
: nsResolveOrRejectPendingPlayPromisesRunner(aElement,
|
||||
Move(aPendingPlayPromises))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -3965,7 +3970,7 @@ HTMLMediaElement::Play(ErrorResult& aRv)
|
|||
// A blocked media element will be resumed later, so we return a pending
|
||||
// promise which might be resolved/rejected depends on the result of
|
||||
// resuming the blocked media element.
|
||||
RefPtr<Promise> promise = CreateDOMPromise(aRv);
|
||||
RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
|
||||
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
|
@ -3989,6 +3994,12 @@ HTMLMediaElement::PlayInternal(ErrorResult& aRv)
|
|||
{
|
||||
MOZ_ASSERT(!aRv.Failed());
|
||||
|
||||
RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
|
||||
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 4.8.12.8
|
||||
// When the play() method on a media element is invoked, the user agent must
|
||||
// run the following steps.
|
||||
|
@ -4005,8 +4016,8 @@ HTMLMediaElement::PlayInternal(ErrorResult& aRv)
|
|||
// NOTE: for promise-based-play, will return a rejected promise here.
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Play() promise rejected because not allowed to play.", this));
|
||||
aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
|
||||
return nullptr;
|
||||
promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// 4.8.12.8 - Step 2:
|
||||
|
@ -4016,17 +4027,13 @@ HTMLMediaElement::PlayInternal(ErrorResult& aRv)
|
|||
if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Play() promise rejected because source not supported.", this));
|
||||
aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
|
||||
return nullptr;
|
||||
promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// 4.8.12.8 - Step 3:
|
||||
// Let promise be a new promise and append promise to the list of pending
|
||||
// play promises.
|
||||
RefPtr<Promise> promise = CreateDOMPromise(aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
mPendingPlayPromises.AppendElement(promise);
|
||||
|
||||
if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
|
||||
|
@ -4076,8 +4083,8 @@ HTMLMediaElement::PlayInternal(ErrorResult& aRv)
|
|||
LOG(LogLevel::Debug,
|
||||
("%p Play() promise rejected because failed to play MediaDecoder.",
|
||||
this));
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7593,7 +7600,7 @@ HTMLMediaElement::AbstractMainThread() const
|
|||
return mAbstractMainThread;
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<Promise>>
|
||||
nsTArray<RefPtr<PlayPromise>>
|
||||
HTMLMediaElement::TakePendingPlayPromises()
|
||||
{
|
||||
return Move(mPendingPlayPromises);
|
||||
|
@ -7607,6 +7614,22 @@ HTMLMediaElement::NotifyAboutPlaying()
|
|||
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
|
||||
}
|
||||
|
||||
already_AddRefed<PlayPromise>
|
||||
HTMLMediaElement::CreatePlayPromise(ErrorResult& aRv) const
|
||||
{
|
||||
nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
|
||||
|
||||
if (!win) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv);
|
||||
LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get()));
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
HTMLMediaElement::CreateDOMPromise(ErrorResult& aRv) const
|
||||
{
|
||||
|
|
|
@ -82,6 +82,7 @@ namespace dom {
|
|||
|
||||
class MediaError;
|
||||
class MediaSource;
|
||||
class PlayPromise;
|
||||
class Promise;
|
||||
class TextTrackList;
|
||||
class AudioTrackList;
|
||||
|
@ -1326,7 +1327,7 @@ protected:
|
|||
|
||||
// This method moves the mPendingPlayPromises into a temperate object. So the
|
||||
// mPendingPlayPromises is cleared after this method call.
|
||||
nsTArray<RefPtr<Promise>> TakePendingPlayPromises();
|
||||
nsTArray<RefPtr<PlayPromise>> TakePendingPlayPromises();
|
||||
|
||||
// This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
|
||||
// and queues a task to resolve them.
|
||||
|
@ -1780,6 +1781,9 @@ public:
|
|||
uint32_t mCount;
|
||||
};
|
||||
private:
|
||||
|
||||
already_AddRefed<PlayPromise> CreatePlayPromise(ErrorResult& aRv) const;
|
||||
|
||||
/**
|
||||
* This function is called by AfterSetAttr and OnAttrSetButNotChanged.
|
||||
* It will not be called if the value is being unset.
|
||||
|
@ -1841,7 +1845,7 @@ private:
|
|||
|
||||
// A list of pending play promises. The elements are pushed during the play()
|
||||
// method call and are resolved/rejected during further playback steps.
|
||||
nsTArray<RefPtr<Promise>> mPendingPlayPromises;
|
||||
nsTArray<RefPtr<PlayPromise>> mPendingPlayPromises;
|
||||
|
||||
// A list of already-dispatched but not yet run
|
||||
// nsResolveOrRejectPendingPlayPromisesRunners.
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/* -*- 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/PlayPromise.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
extern mozilla::LazyLogModule gMediaElementLog;
|
||||
|
||||
#define PLAY_PROMISE_LOG(msg, ...) \
|
||||
MOZ_LOG(gMediaElementLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
PlayPromise::PlayPromise(nsIGlobalObject* aGlobal)
|
||||
: Promise(aGlobal)
|
||||
{
|
||||
}
|
||||
|
||||
PlayPromise::~PlayPromise()
|
||||
{
|
||||
if (!mFulfilled && PromiseObj()) {
|
||||
MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<PlayPromise>
|
||||
PlayPromise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
|
||||
{
|
||||
RefPtr<PlayPromise> promise = new PlayPromise(aGlobal);
|
||||
promise->CreateWrapper(nullptr, aRv);
|
||||
return aRv.Failed() ? nullptr : promise.forget();
|
||||
}
|
||||
|
||||
void
|
||||
PlayPromise::MaybeResolveWithUndefined()
|
||||
{
|
||||
if (mFulfilled) {
|
||||
return;
|
||||
}
|
||||
mFulfilled = true;
|
||||
PLAY_PROMISE_LOG("PlayPromise %p resolved with undefined", this);
|
||||
auto reason = Telemetry::LABELS_MEDIA_PLAY_PROMISE_RESOLUTION::Resolved;
|
||||
Telemetry::AccumulateCategorical(reason);
|
||||
Promise::MaybeResolveWithUndefined();
|
||||
}
|
||||
|
||||
using PlayLabel = Telemetry::LABELS_MEDIA_PLAY_PROMISE_RESOLUTION;
|
||||
|
||||
struct PlayPromiseTelemetryResult
|
||||
{
|
||||
nsresult mValue;
|
||||
PlayLabel mLabel;
|
||||
const char* mName;
|
||||
};
|
||||
|
||||
static const PlayPromiseTelemetryResult sPlayPromiseTelemetryResults[] = {
|
||||
{
|
||||
NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR,
|
||||
PlayLabel::NotAllowedErr,
|
||||
"NotAllowedErr",
|
||||
},
|
||||
{
|
||||
NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
|
||||
PlayLabel::SrcNotSupportedErr,
|
||||
"SrcNotSupportedErr",
|
||||
},
|
||||
{
|
||||
NS_ERROR_DOM_MEDIA_ABORT_ERR,
|
||||
PlayLabel::PauseAbortErr,
|
||||
"PauseAbortErr",
|
||||
},
|
||||
{
|
||||
NS_ERROR_DOM_ABORT_ERR,
|
||||
PlayLabel::AbortErr,
|
||||
"AbortErr",
|
||||
},
|
||||
};
|
||||
|
||||
static const PlayPromiseTelemetryResult*
|
||||
FindPlayPromiseTelemetryResult(nsresult aReason)
|
||||
{
|
||||
for (const auto& p : sPlayPromiseTelemetryResults) {
|
||||
if (p.mValue == aReason) {
|
||||
return &p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static PlayLabel
|
||||
ToPlayResultLabel(nsresult aReason)
|
||||
{
|
||||
auto p = FindPlayPromiseTelemetryResult(aReason);
|
||||
return p ? p->mLabel : PlayLabel::UnknownErr;
|
||||
}
|
||||
|
||||
static const char*
|
||||
ToPlayResultStr(nsresult aReason)
|
||||
{
|
||||
auto p = FindPlayPromiseTelemetryResult(aReason);
|
||||
return p ? p->mName : "UnknownErr";
|
||||
}
|
||||
|
||||
void
|
||||
PlayPromise::MaybeReject(nsresult aReason)
|
||||
{
|
||||
if (mFulfilled) {
|
||||
return;
|
||||
}
|
||||
mFulfilled = true;
|
||||
PLAY_PROMISE_LOG("PlayPromise %p rejected with 0x%x (%s)",
|
||||
this,
|
||||
static_cast<uint32_t>(aReason),
|
||||
ToPlayResultStr(aReason));
|
||||
Telemetry::AccumulateCategorical(ToPlayResultLabel(aReason));
|
||||
Promise::MaybeReject(aReason);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 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/. */
|
||||
|
||||
#ifndef __PlayPromise_h__
|
||||
#define __PlayPromise_h__
|
||||
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// Decorates a DOM Promise to report telemetry as to whether it was resolved
|
||||
// or rejected and why.
|
||||
class PlayPromise : public Promise
|
||||
{
|
||||
public:
|
||||
static already_AddRefed<PlayPromise> Create(nsIGlobalObject* aGlobal,
|
||||
ErrorResult& aRv);
|
||||
~PlayPromise();
|
||||
void MaybeResolveWithUndefined();
|
||||
void MaybeReject(nsresult aReason);
|
||||
|
||||
private:
|
||||
explicit PlayPromise(nsIGlobalObject* aGlobal);
|
||||
bool mFulfilled = false;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // __PlayPromise_h__
|
|
@ -122,6 +122,7 @@ EXPORTS.mozilla.dom += [
|
|||
'ImageDocument.h',
|
||||
'MediaError.h',
|
||||
'nsBrowserElement.h',
|
||||
'PlayPromise.h',
|
||||
'RadioNodeList.h',
|
||||
'TextTrackManager.h',
|
||||
'TimeRanges.h',
|
||||
|
@ -211,6 +212,7 @@ UNIFIED_SOURCES += [
|
|||
'nsIConstraintValidation.cpp',
|
||||
'nsRadioVisitor.cpp',
|
||||
'nsTextEditorState.cpp',
|
||||
'PlayPromise.cpp',
|
||||
'RadioNodeList.cpp',
|
||||
'TextTrackManager.cpp',
|
||||
'TimeRanges.cpp',
|
||||
|
|
|
@ -11317,6 +11317,15 @@
|
|||
"description": "720p VP9 decode benchmark measurement in frames per second",
|
||||
"releaseChannelCollection": "opt-out"
|
||||
},
|
||||
"MEDIA_PLAY_PROMISE_RESOLUTION": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"bug_numbers": [1453176],
|
||||
"alert_emails": ["cpearce@mozilla.com", "drno@ohlmeier.org"],
|
||||
"expires_in_version": "72",
|
||||
"kind": "categorical",
|
||||
"labels": ["Resolved", "NotAllowedErr", "SrcNotSupportedErr", "PauseAbortErr", "AbortErr", "UnknownErr"],
|
||||
"description": "Records whether promise returned by HTMLMediaElement.play() successfully resolved, or the error code which it was rejected with."
|
||||
},
|
||||
"MEDIA_CODEC_USED": {
|
||||
"record_in_processes": ["main", "content"],
|
||||
"alert_emails": ["cpearce@mozilla.com"],
|
||||
|
|
Загрузка…
Ссылка в новой задаче