[Blink>Media] Moving autoplay logic to AutoplayPolicy
This CL moves autoplay code from HTMLMediaElement to the AutoplayPolicy class, which helps decoupling the autoplay logic from HTMLMediaElement. BUG=712606 Review-Url: https://codereview.chromium.org/2813303005 Cr-Commit-Position: refs/heads/master@{#466137}
This commit is contained in:
docs/media
third_party/WebKit/Source/core/html
@ -14,6 +14,13 @@ There are two ways of initiating autoplay:
|
|||||||
* Autoplay by `play()` method: Explicitly calling the `play()` method without
|
* Autoplay by `play()` method: Explicitly calling the `play()` method without
|
||||||
user gesture.
|
user gesture.
|
||||||
|
|
||||||
|
All the autoplay logic is handled by the AutoplayPolicy class. When the media
|
||||||
|
element wants to perform some action (like unmute, autoplay by attribute or
|
||||||
|
`play()` method), it will send a request to AutoplayPolicy, and if the request
|
||||||
|
is approved, the element can autoplay, otherwise it should be paused. Also the
|
||||||
|
media element should inform the AutoplayPolicy about relevant changes such as
|
||||||
|
"the element has been moved to a new document".
|
||||||
|
|
||||||
## User gesture lock
|
## User gesture lock
|
||||||
|
|
||||||
Each media element has a user gesture lock. If the element is allowed to
|
Each media element has a user gesture lock. If the element is allowed to
|
||||||
|
2
third_party/WebKit/Source/core/html/BUILD.gn
vendored
2
third_party/WebKit/Source/core/html/BUILD.gn
vendored
@ -389,6 +389,8 @@ blink_core_sources("html") {
|
|||||||
"imports/HTMLImportsController.h",
|
"imports/HTMLImportsController.h",
|
||||||
"imports/LinkImport.cpp",
|
"imports/LinkImport.cpp",
|
||||||
"imports/LinkImport.h",
|
"imports/LinkImport.h",
|
||||||
|
"media/AutoplayPolicy.cpp",
|
||||||
|
"media/AutoplayPolicy.h",
|
||||||
"media/AutoplayUmaHelper.cpp",
|
"media/AutoplayUmaHelper.cpp",
|
||||||
"media/AutoplayUmaHelper.h",
|
"media/AutoplayUmaHelper.h",
|
||||||
"media/HTMLMediaElementControlsList.cpp",
|
"media/HTMLMediaElementControlsList.cpp",
|
||||||
|
@ -36,14 +36,11 @@
|
|||||||
#include "core/css/MediaList.h"
|
#include "core/css/MediaList.h"
|
||||||
#include "core/dom/Attribute.h"
|
#include "core/dom/Attribute.h"
|
||||||
#include "core/dom/DOMException.h"
|
#include "core/dom/DOMException.h"
|
||||||
#include "core/dom/DocumentUserGestureToken.h"
|
|
||||||
#include "core/dom/ElementTraversal.h"
|
#include "core/dom/ElementTraversal.h"
|
||||||
#include "core/dom/ElementVisibilityObserver.h"
|
|
||||||
#include "core/dom/Fullscreen.h"
|
#include "core/dom/Fullscreen.h"
|
||||||
#include "core/dom/TaskRunnerHelper.h"
|
#include "core/dom/TaskRunnerHelper.h"
|
||||||
#include "core/dom/shadow/ShadowRoot.h"
|
#include "core/dom/shadow/ShadowRoot.h"
|
||||||
#include "core/events/Event.h"
|
#include "core/events/Event.h"
|
||||||
#include "core/frame/ContentSettingsClient.h"
|
|
||||||
#include "core/frame/FrameView.h"
|
#include "core/frame/FrameView.h"
|
||||||
#include "core/frame/LocalFrame.h"
|
#include "core/frame/LocalFrame.h"
|
||||||
#include "core/frame/LocalFrameClient.h"
|
#include "core/frame/LocalFrameClient.h"
|
||||||
@ -53,7 +50,7 @@
|
|||||||
#include "core/html/HTMLSourceElement.h"
|
#include "core/html/HTMLSourceElement.h"
|
||||||
#include "core/html/HTMLTrackElement.h"
|
#include "core/html/HTMLTrackElement.h"
|
||||||
#include "core/html/TimeRanges.h"
|
#include "core/html/TimeRanges.h"
|
||||||
#include "core/html/media/AutoplayUmaHelper.h"
|
#include "core/html/media/AutoplayPolicy.h"
|
||||||
#include "core/html/media/HTMLMediaElementControlsList.h"
|
#include "core/html/media/HTMLMediaElementControlsList.h"
|
||||||
#include "core/html/media/HTMLMediaSource.h"
|
#include "core/html/media/HTMLMediaSource.h"
|
||||||
#include "core/html/media/MediaControls.h"
|
#include "core/html/media/MediaControls.h"
|
||||||
@ -78,7 +75,6 @@
|
|||||||
#include "platform/Histogram.h"
|
#include "platform/Histogram.h"
|
||||||
#include "platform/LayoutTestSupport.h"
|
#include "platform/LayoutTestSupport.h"
|
||||||
#include "platform/RuntimeEnabledFeatures.h"
|
#include "platform/RuntimeEnabledFeatures.h"
|
||||||
#include "platform/UserGestureIndicator.h"
|
|
||||||
#include "platform/audio/AudioBus.h"
|
#include "platform/audio/AudioBus.h"
|
||||||
#include "platform/audio/AudioSourceProviderClient.h"
|
#include "platform/audio/AudioSourceProviderClient.h"
|
||||||
#include "platform/graphics/GraphicsLayer.h"
|
#include "platform/graphics/GraphicsLayer.h"
|
||||||
@ -338,36 +334,6 @@ bool IsDocumentCrossOrigin(Document& document) {
|
|||||||
return frame && frame->IsCrossOriginSubframe();
|
return frame && frame->IsCrossOriginSubframe();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsDocumentWhitelisted(Document& document) {
|
|
||||||
DCHECK(document.GetSettings());
|
|
||||||
|
|
||||||
const String& whitelist_scope =
|
|
||||||
document.GetSettings()->GetMediaPlaybackGestureWhitelistScope();
|
|
||||||
if (whitelist_scope.IsNull() || whitelist_scope.IsEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return document.Url().GetString().StartsWith(whitelist_scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if and only if the document settings specifies media playback
|
|
||||||
// requires user gesture.
|
|
||||||
bool ComputeLockedPendingUserGesture(Document& document) {
|
|
||||||
if (!document.GetSettings())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (IsDocumentWhitelisted(document)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.GetSettings()
|
|
||||||
->GetCrossOriginMediaPlaybackRequiresUserGesture() &&
|
|
||||||
IsDocumentCrossOrigin(document)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return document.GetSettings()->GetMediaPlaybackRequiresUserGesture();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<MediaControls::Factory>& MediaControlsFactory() {
|
std::unique_ptr<MediaControls::Factory>& MediaControlsFactory() {
|
||||||
DEFINE_STATIC_LOCAL(std::unique_ptr<MediaControls::Factory>,
|
DEFINE_STATIC_LOCAL(std::unique_ptr<MediaControls::Factory>,
|
||||||
media_controls_factory, ());
|
media_controls_factory, ());
|
||||||
@ -505,8 +471,6 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tag_name,
|
|||||||
official_playback_position_needs_update_(true),
|
official_playback_position_needs_update_(true),
|
||||||
fragment_end_time_(std::numeric_limits<double>::quiet_NaN()),
|
fragment_end_time_(std::numeric_limits<double>::quiet_NaN()),
|
||||||
pending_action_flags_(0),
|
pending_action_flags_(0),
|
||||||
locked_pending_user_gesture_(false),
|
|
||||||
locked_pending_user_gesture_if_cross_origin_experiment_enabled_(true),
|
|
||||||
playing_(false),
|
playing_(false),
|
||||||
should_delay_load_event_(false),
|
should_delay_load_event_(false),
|
||||||
have_fired_loaded_data_(false),
|
have_fired_loaded_data_(false),
|
||||||
@ -527,17 +491,12 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tag_name,
|
|||||||
video_tracks_(this, VideoTrackList::Create(*this)),
|
video_tracks_(this, VideoTrackList::Create(*this)),
|
||||||
text_tracks_(this, nullptr),
|
text_tracks_(this, nullptr),
|
||||||
audio_source_node_(nullptr),
|
audio_source_node_(nullptr),
|
||||||
autoplay_uma_helper_(AutoplayUmaHelper::Create(this)),
|
autoplay_policy_(new AutoplayPolicy(this)),
|
||||||
remote_playback_client_(nullptr),
|
remote_playback_client_(nullptr),
|
||||||
autoplay_visibility_observer_(nullptr),
|
|
||||||
media_controls_(nullptr),
|
media_controls_(nullptr),
|
||||||
controls_list_(HTMLMediaElementControlsList::Create(this)) {
|
controls_list_(HTMLMediaElementControlsList::Create(this)) {
|
||||||
BLINK_MEDIA_LOG << "HTMLMediaElement(" << (void*)this << ")";
|
BLINK_MEDIA_LOG << "HTMLMediaElement(" << (void*)this << ")";
|
||||||
|
|
||||||
locked_pending_user_gesture_ = ComputeLockedPendingUserGesture(document);
|
|
||||||
locked_pending_user_gesture_if_cross_origin_experiment_enabled_ =
|
|
||||||
IsDocumentCrossOrigin(document);
|
|
||||||
|
|
||||||
LocalFrame* frame = document.GetFrame();
|
LocalFrame* frame = document.GetFrame();
|
||||||
if (frame) {
|
if (frame) {
|
||||||
remote_playback_client_ =
|
remote_playback_client_ =
|
||||||
@ -591,15 +550,7 @@ void HTMLMediaElement::DidMoveToNewDocument(Document& old_document) {
|
|||||||
deferred_load_timer_.MoveToNewTaskRunner(
|
deferred_load_timer_.MoveToNewTaskRunner(
|
||||||
TaskRunnerHelper::Get(TaskType::kUnthrottled, &GetDocument()));
|
TaskRunnerHelper::Get(TaskType::kUnthrottled, &GetDocument()));
|
||||||
|
|
||||||
autoplay_uma_helper_->DidMoveToNewDocument(old_document);
|
autoplay_policy_->DidMoveToNewDocument(old_document);
|
||||||
// If any experiment is enabled, then we want to enable a user gesture by
|
|
||||||
// default, otherwise the experiment does nothing.
|
|
||||||
bool old_document_requires_user_gesture =
|
|
||||||
ComputeLockedPendingUserGesture(old_document);
|
|
||||||
bool new_document_requires_user_gesture =
|
|
||||||
ComputeLockedPendingUserGesture(GetDocument());
|
|
||||||
if (new_document_requires_user_gesture && !old_document_requires_user_gesture)
|
|
||||||
locked_pending_user_gesture_ = true;
|
|
||||||
|
|
||||||
if (should_delay_load_event_) {
|
if (should_delay_load_event_) {
|
||||||
GetDocument().IncrementLoadEventDelayCount();
|
GetDocument().IncrementLoadEventDelayCount();
|
||||||
@ -612,10 +563,6 @@ void HTMLMediaElement::DidMoveToNewDocument(Document& old_document) {
|
|||||||
old_document.IncrementLoadEventDelayCount();
|
old_document.IncrementLoadEventDelayCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDocumentCrossOrigin(GetDocument()) &&
|
|
||||||
!IsDocumentCrossOrigin(old_document))
|
|
||||||
locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = true;
|
|
||||||
|
|
||||||
RemoveElementFromDocumentMap(this, &old_document);
|
RemoveElementFromDocumentMap(this, &old_document);
|
||||||
AddElementToDocumentMap(this, &GetDocument());
|
AddElementToDocumentMap(this, &GetDocument());
|
||||||
|
|
||||||
@ -845,10 +792,7 @@ String HTMLMediaElement::canPlayType(const String& mime_type) const {
|
|||||||
void HTMLMediaElement::load() {
|
void HTMLMediaElement::load() {
|
||||||
BLINK_MEDIA_LOG << "load(" << (void*)this << ")";
|
BLINK_MEDIA_LOG << "load(" << (void*)this << ")";
|
||||||
|
|
||||||
if (IsLockedPendingUserGesture() &&
|
autoplay_policy_->TryUnlockingUserGesture();
|
||||||
UserGestureIndicator::UtilizeUserGesture()) {
|
|
||||||
UnlockUserGesture();
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore_preload_none_ = true;
|
ignore_preload_none_ = true;
|
||||||
InvokeLoadAlgorithm();
|
InvokeLoadAlgorithm();
|
||||||
@ -1839,40 +1783,11 @@ void HTMLMediaElement::SetReadyState(ReadyState state) {
|
|||||||
ScheduleNotifyPlaying();
|
ScheduleNotifyPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for autoplay, and record metrics about it if needed.
|
if (autoplay_policy_->RequestAutoplayByAttribute()) {
|
||||||
if (ShouldAutoplay()) {
|
paused_ = false;
|
||||||
autoplay_uma_helper_->OnAutoplayInitiated(AutoplaySource::kAttribute);
|
ScheduleEvent(EventTypeNames::play);
|
||||||
|
ScheduleNotifyPlaying();
|
||||||
if (!IsGestureNeededForPlayback()) {
|
can_autoplay_ = false;
|
||||||
if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) {
|
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
|
||||||
CrossOriginAutoplayResult::kAutoplayBlocked);
|
|
||||||
} else {
|
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
|
||||||
CrossOriginAutoplayResult::kAutoplayAllowed);
|
|
||||||
}
|
|
||||||
if (IsHTMLVideoElement() && muted() &&
|
|
||||||
RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) {
|
|
||||||
// We might end up in a situation where the previous
|
|
||||||
// observer didn't had time to fire yet. We can avoid
|
|
||||||
// creating a new one in this case.
|
|
||||||
if (!autoplay_visibility_observer_) {
|
|
||||||
autoplay_visibility_observer_ = new ElementVisibilityObserver(
|
|
||||||
this,
|
|
||||||
WTF::Bind(&HTMLMediaElement::OnVisibilityChangedForAutoplay,
|
|
||||||
WrapWeakPersistent(this)));
|
|
||||||
autoplay_visibility_observer_->Start();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
paused_ = false;
|
|
||||||
ScheduleEvent(EventTypeNames::play);
|
|
||||||
ScheduleNotifyPlaying();
|
|
||||||
can_autoplay_ = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
|
||||||
CrossOriginAutoplayResult::kAutoplayBlocked);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduleEvent(EventTypeNames::canplaythrough);
|
ScheduleEvent(EventTypeNames::canplaythrough);
|
||||||
@ -2213,12 +2128,6 @@ bool HTMLMediaElement::Autoplay() const {
|
|||||||
return FastHasAttribute(autoplayAttr);
|
return FastHasAttribute(autoplayAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HTMLMediaElement::ShouldAutoplay() {
|
|
||||||
if (GetDocument().IsSandboxed(kSandboxAutomaticFeatures))
|
|
||||||
return false;
|
|
||||||
return can_autoplay_ && paused_ && Autoplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
String HTMLMediaElement::preload() const {
|
String HTMLMediaElement::preload() const {
|
||||||
return PreloadTypeToString(PreloadType());
|
return PreloadTypeToString(PreloadType());
|
||||||
}
|
}
|
||||||
@ -2283,7 +2192,7 @@ String HTMLMediaElement::EffectivePreload() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebMediaPlayer::Preload HTMLMediaElement::EffectivePreloadType() const {
|
WebMediaPlayer::Preload HTMLMediaElement::EffectivePreloadType() const {
|
||||||
if (Autoplay() && !IsGestureNeededForPlayback())
|
if (Autoplay() && !autoplay_policy_->IsGestureNeededForPlayback())
|
||||||
return WebMediaPlayer::kPreloadAuto;
|
return WebMediaPlayer::kPreloadAuto;
|
||||||
|
|
||||||
WebMediaPlayer::Preload preload = PreloadType();
|
WebMediaPlayer::Preload preload = PreloadType();
|
||||||
@ -2329,47 +2238,29 @@ ScriptPromise HTMLMediaElement::playForBindings(ScriptState* script_state) {
|
|||||||
Nullable<ExceptionCode> HTMLMediaElement::Play() {
|
Nullable<ExceptionCode> HTMLMediaElement::Play() {
|
||||||
BLINK_MEDIA_LOG << "play(" << (void*)this << ")";
|
BLINK_MEDIA_LOG << "play(" << (void*)this << ")";
|
||||||
|
|
||||||
if (!UserGestureIndicator::ProcessingUserGesture()) {
|
Nullable<ExceptionCode> exception_code = autoplay_policy_->RequestPlay();
|
||||||
autoplay_uma_helper_->OnAutoplayInitiated(AutoplaySource::kMethod);
|
|
||||||
if (IsGestureNeededForPlayback()) {
|
|
||||||
// If we're already playing, then this play would do nothing anyway.
|
|
||||||
// Call playInternal to handle scheduling the promise resolution.
|
|
||||||
if (!paused_) {
|
|
||||||
PlayInternal();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
if (exception_code == kNotAllowedError) {
|
||||||
CrossOriginAutoplayResult::kAutoplayBlocked);
|
// If we're already playing, then this play would do nothing anyway.
|
||||||
String message = ExceptionMessages::FailedToExecute(
|
// Call playInternal to handle scheduling the promise resolution.
|
||||||
"play", "HTMLMediaElement",
|
if (!paused_) {
|
||||||
"API can only be initiated by a user gesture.");
|
PlayInternal();
|
||||||
GetDocument().AddConsoleMessage(ConsoleMessage::Create(
|
return nullptr;
|
||||||
kJSMessageSource, kWarningMessageLevel, message));
|
|
||||||
return kNotAllowedError;
|
|
||||||
} else {
|
|
||||||
if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) {
|
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
|
||||||
CrossOriginAutoplayResult::kAutoplayBlocked);
|
|
||||||
} else {
|
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
|
||||||
CrossOriginAutoplayResult::kAutoplayAllowed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
String message = ExceptionMessages::FailedToExecute(
|
||||||
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
"play", "HTMLMediaElement",
|
||||||
CrossOriginAutoplayResult::kPlayedWithGesture);
|
"API can only be initiated by a user gesture.");
|
||||||
UserGestureIndicator::UtilizeUserGesture();
|
GetDocument().AddConsoleMessage(ConsoleMessage::Create(
|
||||||
UnlockUserGesture();
|
kJSMessageSource, kWarningMessageLevel, message));
|
||||||
|
return exception_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autoplay_policy_->StopAutoplayMutedWhenVisible();
|
||||||
|
|
||||||
if (error_ && error_->code() == MediaError::kMediaErrSrcNotSupported)
|
if (error_ && error_->code() == MediaError::kMediaErrSrcNotSupported)
|
||||||
return kNotSupportedError;
|
return kNotSupportedError;
|
||||||
|
|
||||||
if (autoplay_visibility_observer_) {
|
DCHECK(exception_code.IsNull());
|
||||||
autoplay_visibility_observer_->Stop();
|
|
||||||
autoplay_visibility_observer_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayInternal();
|
PlayInternal();
|
||||||
|
|
||||||
@ -2410,11 +2301,7 @@ void HTMLMediaElement::PlayInternal() {
|
|||||||
void HTMLMediaElement::pause() {
|
void HTMLMediaElement::pause() {
|
||||||
BLINK_MEDIA_LOG << "pause(" << (void*)this << ")";
|
BLINK_MEDIA_LOG << "pause(" << (void*)this << ")";
|
||||||
|
|
||||||
if (autoplay_visibility_observer_) {
|
autoplay_policy_->StopAutoplayMutedWhenVisible();
|
||||||
autoplay_visibility_observer_->Stop();
|
|
||||||
autoplay_visibility_observer_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PauseInternal();
|
PauseInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2562,41 +2449,21 @@ void HTMLMediaElement::setMuted(bool muted) {
|
|||||||
if (muted_ == muted)
|
if (muted_ == muted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool was_autoplaying_muted = IsAutoplayingMuted();
|
|
||||||
bool was_pending_autoplay_muted = autoplay_visibility_observer_ && paused() &&
|
|
||||||
muted_ && IsLockedPendingUserGesture();
|
|
||||||
|
|
||||||
if (UserGestureIndicator::ProcessingUserGesture())
|
|
||||||
UnlockUserGesture();
|
|
||||||
|
|
||||||
muted_ = muted;
|
muted_ = muted;
|
||||||
|
|
||||||
ScheduleEvent(EventTypeNames::volumechange);
|
ScheduleEvent(EventTypeNames::volumechange);
|
||||||
|
|
||||||
// If an element autoplayed while muted, it needs to be unlocked to unmute,
|
// If it is unmute and AutoplayPolicy doesn't want the playback to continue,
|
||||||
// otherwise, it will be paused.
|
// pause the playback.
|
||||||
if (was_autoplaying_muted) {
|
if (!muted_ && !autoplay_policy_->RequestAutoplayUnmute())
|
||||||
if (IsGestureNeededForPlayback()) {
|
pause();
|
||||||
pause();
|
|
||||||
autoplay_uma_helper_->RecordAutoplayUnmuteStatus(
|
|
||||||
AutoplayUnmuteActionStatus::kFailure);
|
|
||||||
} else {
|
|
||||||
autoplay_uma_helper_->RecordAutoplayUnmuteStatus(
|
|
||||||
AutoplayUnmuteActionStatus::kSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called after the volumechange event to make sure isAutoplayingMuted
|
// This is called after the volumechange event to make sure isAutoplayingMuted
|
||||||
// returns the right value when webMediaPlayer receives the volume update.
|
// returns the right value when webMediaPlayer receives the volume update.
|
||||||
if (GetWebMediaPlayer())
|
if (GetWebMediaPlayer())
|
||||||
GetWebMediaPlayer()->SetVolume(EffectiveMediaVolume());
|
GetWebMediaPlayer()->SetVolume(EffectiveMediaVolume());
|
||||||
|
|
||||||
// If an element was a candidate for autoplay muted but not visible, it will
|
autoplay_policy_->StopAutoplayMutedWhenVisible();
|
||||||
// have a visibility observer ready to start its playback.
|
|
||||||
if (was_pending_autoplay_muted) {
|
|
||||||
autoplay_visibility_observer_->Stop();
|
|
||||||
autoplay_visibility_observer_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double HTMLMediaElement::EffectiveMediaVolume() const {
|
double HTMLMediaElement::EffectiveMediaVolume() const {
|
||||||
@ -3318,15 +3185,6 @@ WebMediaPlayer::TrackId HTMLMediaElement::GetSelectedVideoTrackId() {
|
|||||||
return track->id();
|
return track->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HTMLMediaElement::IsAutoplayingMuted() {
|
|
||||||
if (!IsHTMLVideoElement() ||
|
|
||||||
!RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !paused() && muted() && IsLockedPendingUserGesture();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HTMLMediaElement::RequestReload(const WebURL& new_url) {
|
void HTMLMediaElement::RequestReload(const WebURL& new_url) {
|
||||||
DCHECK(GetWebMediaPlayer());
|
DCHECK(GetWebMediaPlayer());
|
||||||
DCHECK(!src_object_);
|
DCHECK(!src_object_);
|
||||||
@ -3336,6 +3194,10 @@ void HTMLMediaElement::RequestReload(const WebURL& new_url) {
|
|||||||
StartPlayerLoad(new_url);
|
StartPlayerLoad(new_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HTMLMediaElement::IsAutoplayingMuted() {
|
||||||
|
return autoplay_policy_->IsAutoplayingMuted();
|
||||||
|
}
|
||||||
|
|
||||||
// MediaPlayerPresentation methods
|
// MediaPlayerPresentation methods
|
||||||
void HTMLMediaElement::Repaint() {
|
void HTMLMediaElement::Repaint() {
|
||||||
if (web_layer_)
|
if (web_layer_)
|
||||||
@ -3931,9 +3793,8 @@ DEFINE_TRACE(HTMLMediaElement) {
|
|||||||
visitor->Trace(play_promise_resolve_list_);
|
visitor->Trace(play_promise_resolve_list_);
|
||||||
visitor->Trace(play_promise_reject_list_);
|
visitor->Trace(play_promise_reject_list_);
|
||||||
visitor->Trace(audio_source_provider_);
|
visitor->Trace(audio_source_provider_);
|
||||||
visitor->Trace(autoplay_uma_helper_);
|
|
||||||
visitor->Trace(src_object_);
|
visitor->Trace(src_object_);
|
||||||
visitor->Trace(autoplay_visibility_observer_);
|
visitor->Trace(autoplay_policy_);
|
||||||
visitor->Trace(media_controls_);
|
visitor->Trace(media_controls_);
|
||||||
visitor->Trace(controls_list_);
|
visitor->Trace(controls_list_);
|
||||||
visitor->template RegisterWeakMembers<HTMLMediaElement,
|
visitor->template RegisterWeakMembers<HTMLMediaElement,
|
||||||
@ -3983,60 +3844,6 @@ void HTMLMediaElement::SelectInitialTracksIfNecessary() {
|
|||||||
videoTracks().AnonymousIndexedGetter(0)->setSelected(true);
|
videoTracks().AnonymousIndexedGetter(0)->setSelected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HTMLMediaElement::IsLockedPendingUserGesture() const {
|
|
||||||
return locked_pending_user_gesture_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HTMLMediaElement::UnlockUserGesture() {
|
|
||||||
locked_pending_user_gesture_ = false;
|
|
||||||
locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HTMLMediaElement::IsGestureNeededForPlayback() const {
|
|
||||||
if (!locked_pending_user_gesture_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return IsGestureNeededForPlaybackIfPendingUserGestureIsLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HTMLMediaElement::
|
|
||||||
IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled() const {
|
|
||||||
if (!locked_pending_user_gesture_if_cross_origin_experiment_enabled_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return IsGestureNeededForPlaybackIfPendingUserGestureIsLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HTMLMediaElement::IsGestureNeededForPlaybackIfPendingUserGestureIsLocked()
|
|
||||||
const {
|
|
||||||
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// We want to allow muted video to autoplay if:
|
|
||||||
// - the flag is enabled;
|
|
||||||
// - Data Saver is not enabled;
|
|
||||||
// - Preload was not disabled (low end devices);
|
|
||||||
// - Autoplay is enabled in settings;
|
|
||||||
if (IsHTMLVideoElement() && muted() &&
|
|
||||||
RuntimeEnabledFeatures::autoplayMutedVideosEnabled() &&
|
|
||||||
!(GetDocument().GetSettings() &&
|
|
||||||
GetDocument().GetSettings()->GetDataSaverEnabled()) &&
|
|
||||||
!(GetDocument().GetSettings() &&
|
|
||||||
GetDocument().GetSettings()->GetForcePreloadNoneForMediaElements()) &&
|
|
||||||
IsAutoplayAllowedPerSettings()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HTMLMediaElement::IsAutoplayAllowedPerSettings() const {
|
|
||||||
LocalFrame* frame = GetDocument().GetFrame();
|
|
||||||
if (!frame)
|
|
||||||
return false;
|
|
||||||
return frame->GetContentSettingsClient()->AllowAutoplay(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HTMLMediaElement::SetNetworkState(NetworkState state) {
|
void HTMLMediaElement::SetNetworkState(NetworkState state) {
|
||||||
if (network_state_ == state)
|
if (network_state_ == state)
|
||||||
return;
|
return;
|
||||||
@ -4049,9 +3856,7 @@ void HTMLMediaElement::SetNetworkState(NetworkState state) {
|
|||||||
void HTMLMediaElement::VideoWillBeDrawnToCanvas() const {
|
void HTMLMediaElement::VideoWillBeDrawnToCanvas() const {
|
||||||
DCHECK(IsHTMLVideoElement());
|
DCHECK(IsHTMLVideoElement());
|
||||||
UseCounter::Count(GetDocument(), UseCounter::kVideoInCanvas);
|
UseCounter::Count(GetDocument(), UseCounter::kVideoInCanvas);
|
||||||
if (autoplay_uma_helper_->HasSource() && !autoplay_uma_helper_->IsVisible())
|
autoplay_policy_->VideoWillBeDrawnToCanvas();
|
||||||
UseCounter::Count(GetDocument(),
|
|
||||||
UseCounter::kHiddenAutoplayedVideoInCanvas);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLMediaElement::ScheduleResolvePlayPromises() {
|
void HTMLMediaElement::ScheduleResolvePlayPromises() {
|
||||||
@ -4166,24 +3971,6 @@ EnumerationHistogram& HTMLMediaElement::ShowControlsHistogram() const {
|
|||||||
return histogram;
|
return histogram;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLMediaElement::OnVisibilityChangedForAutoplay(bool is_visible) {
|
|
||||||
if (!is_visible) {
|
|
||||||
if (can_autoplay_ && Autoplay()) {
|
|
||||||
PauseInternal();
|
|
||||||
can_autoplay_ = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShouldAutoplay()) {
|
|
||||||
paused_ = false;
|
|
||||||
ScheduleEvent(EventTypeNames::play);
|
|
||||||
ScheduleNotifyPlaying();
|
|
||||||
|
|
||||||
UpdatePlayState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HTMLMediaElement::ClearWeakMembers(Visitor* visitor) {
|
void HTMLMediaElement::ClearWeakMembers(Visitor* visitor) {
|
||||||
if (!ThreadHeap::IsHeapObjectAlive(audio_source_node_)) {
|
if (!ThreadHeap::IsHeapObjectAlive(audio_source_node_)) {
|
||||||
GetAudioSourceProvider().SetClient(nullptr);
|
GetAudioSourceProvider().SetClient(nullptr);
|
||||||
|
@ -51,10 +51,9 @@ namespace blink {
|
|||||||
class AudioSourceProviderClient;
|
class AudioSourceProviderClient;
|
||||||
class AudioTrack;
|
class AudioTrack;
|
||||||
class AudioTrackList;
|
class AudioTrackList;
|
||||||
class AutoplayUmaHelper;
|
class AutoplayPolicy;
|
||||||
class ContentType;
|
class ContentType;
|
||||||
class CueTimeline;
|
class CueTimeline;
|
||||||
class ElementVisibilityObserver;
|
|
||||||
class EnumerationHistogram;
|
class EnumerationHistogram;
|
||||||
class Event;
|
class Event;
|
||||||
class ExceptionState;
|
class ExceptionState;
|
||||||
@ -330,6 +329,8 @@ class CORE_EXPORT HTMLMediaElement
|
|||||||
return remote_playback_client_;
|
return remote_playback_client_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AutoplayPolicy& GetAutoplayPolicy() const { return *autoplay_policy_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HTMLMediaElement(const QualifiedName&, Document&);
|
HTMLMediaElement(const QualifiedName&, Document&);
|
||||||
~HTMLMediaElement() override;
|
~HTMLMediaElement() override;
|
||||||
@ -524,33 +525,6 @@ class CORE_EXPORT HTMLMediaElement
|
|||||||
// transition to kHaveMetadata.
|
// transition to kHaveMetadata.
|
||||||
void SelectInitialTracksIfNecessary();
|
void SelectInitialTracksIfNecessary();
|
||||||
|
|
||||||
// Return true if and only if a user gesture is required to unlock this
|
|
||||||
// media element for unrestricted autoplay / script control. Don't confuse
|
|
||||||
// this with isGestureNeededForPlayback(). The latter is usually what one
|
|
||||||
// should use, if checking to see if an action is allowed.
|
|
||||||
bool IsLockedPendingUserGesture() const;
|
|
||||||
|
|
||||||
bool IsLockedPendingUserGestureIfCrossOriginExperimentEnabled() const;
|
|
||||||
|
|
||||||
// If the user gesture is required, then this will remove it. Note that
|
|
||||||
// one should not generally call this method directly; use the one on
|
|
||||||
// m_helper and give it a reason.
|
|
||||||
void UnlockUserGesture();
|
|
||||||
|
|
||||||
// Return true if and only if a user gesture is requried for playback. Even
|
|
||||||
// if isLockedPendingUserGesture() return true, this might return false if
|
|
||||||
// the requirement is currently overridden. This does not check if a user
|
|
||||||
// gesture is currently being processed.
|
|
||||||
bool IsGestureNeededForPlayback() const;
|
|
||||||
|
|
||||||
bool IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled() const;
|
|
||||||
|
|
||||||
bool IsGestureNeededForPlaybackIfPendingUserGestureIsLocked() const;
|
|
||||||
|
|
||||||
// Return true if and only if the settings allow autoplay of media on this
|
|
||||||
// frame.
|
|
||||||
bool IsAutoplayAllowedPerSettings() const;
|
|
||||||
|
|
||||||
void SetNetworkState(NetworkState);
|
void SetNetworkState(NetworkState);
|
||||||
|
|
||||||
void AudioTracksTimerFired(TimerBase*);
|
void AudioTracksTimerFired(TimerBase*);
|
||||||
@ -565,8 +539,6 @@ class CORE_EXPORT HTMLMediaElement
|
|||||||
|
|
||||||
EnumerationHistogram& ShowControlsHistogram() const;
|
EnumerationHistogram& ShowControlsHistogram() const;
|
||||||
|
|
||||||
void OnVisibilityChangedForAutoplay(bool is_visible);
|
|
||||||
|
|
||||||
void ViewportFillDebouncerTimerFired(TimerBase*);
|
void ViewportFillDebouncerTimerFired(TimerBase*);
|
||||||
|
|
||||||
TaskRunnerTimer<HTMLMediaElement> load_timer_;
|
TaskRunnerTimer<HTMLMediaElement> load_timer_;
|
||||||
@ -652,8 +624,6 @@ class CORE_EXPORT HTMLMediaElement
|
|||||||
PendingActionFlags pending_action_flags_;
|
PendingActionFlags pending_action_flags_;
|
||||||
|
|
||||||
// FIXME: HTMLMediaElement has way too many state bits.
|
// FIXME: HTMLMediaElement has way too many state bits.
|
||||||
bool locked_pending_user_gesture_ : 1;
|
|
||||||
bool locked_pending_user_gesture_if_cross_origin_experiment_enabled_ : 1;
|
|
||||||
bool playing_ : 1;
|
bool playing_ : 1;
|
||||||
bool should_delay_load_event_ : 1;
|
bool should_delay_load_event_ : 1;
|
||||||
bool have_fired_loaded_data_ : 1;
|
bool have_fired_loaded_data_ : 1;
|
||||||
@ -745,7 +715,7 @@ class CORE_EXPORT HTMLMediaElement
|
|||||||
|
|
||||||
AudioSourceProviderImpl audio_source_provider_;
|
AudioSourceProviderImpl audio_source_provider_;
|
||||||
|
|
||||||
friend class AutoplayUmaHelper; // for isAutoplayAllowedPerSettings
|
friend class AutoplayPolicy;
|
||||||
friend class AutoplayUmaHelperTest;
|
friend class AutoplayUmaHelperTest;
|
||||||
friend class Internals;
|
friend class Internals;
|
||||||
friend class TrackDisplayUpdateScope;
|
friend class TrackDisplayUpdateScope;
|
||||||
@ -755,13 +725,10 @@ class CORE_EXPORT HTMLMediaElement
|
|||||||
friend class HTMLVideoElement;
|
friend class HTMLVideoElement;
|
||||||
friend class MediaControlsOrientationLockDelegateTest;
|
friend class MediaControlsOrientationLockDelegateTest;
|
||||||
|
|
||||||
Member<AutoplayUmaHelper> autoplay_uma_helper_;
|
Member<AutoplayPolicy> autoplay_policy_;
|
||||||
|
|
||||||
WebRemotePlaybackClient* remote_playback_client_;
|
WebRemotePlaybackClient* remote_playback_client_;
|
||||||
|
|
||||||
// class AutoplayVisibilityObserver;
|
|
||||||
Member<ElementVisibilityObserver> autoplay_visibility_observer_;
|
|
||||||
|
|
||||||
IntRect current_intersect_rect_;
|
IntRect current_intersect_rect_;
|
||||||
|
|
||||||
Member<MediaControls> media_controls_;
|
Member<MediaControls> media_controls_;
|
||||||
|
301
third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp
vendored
Normal file
301
third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp
vendored
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "core/html/media/AutoplayPolicy.h"
|
||||||
|
|
||||||
|
#include "core/dom/Document.h"
|
||||||
|
#include "core/dom/ElementVisibilityObserver.h"
|
||||||
|
#include "core/frame/ContentSettingsClient.h"
|
||||||
|
#include "core/frame/LocalFrame.h"
|
||||||
|
#include "core/frame/Settings.h"
|
||||||
|
#include "core/html/HTMLMediaElement.h"
|
||||||
|
#include "core/html/media/AutoplayUmaHelper.h"
|
||||||
|
#include "platform/RuntimeEnabledFeatures.h"
|
||||||
|
#include "platform/UserGestureIndicator.h"
|
||||||
|
#include "public/platform/WebMediaPlayer.h"
|
||||||
|
|
||||||
|
namespace blink {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool IsDocumentCrossOrigin(Document& document) {
|
||||||
|
const LocalFrame* frame = document.GetFrame();
|
||||||
|
return frame && frame->IsCrossOriginSubframe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether |document| is whitelisted for autoplay. If true, the user
|
||||||
|
// gesture lock will be initilized as false, indicating that the element is
|
||||||
|
// allowed to autoplay unmuted without user gesture.
|
||||||
|
bool IsDocumentWhitelisted(Document& document) {
|
||||||
|
DCHECK(document.GetSettings());
|
||||||
|
|
||||||
|
const String& whitelist_scope =
|
||||||
|
document.GetSettings()->GetMediaPlaybackGestureWhitelistScope();
|
||||||
|
if (whitelist_scope.IsNull() || whitelist_scope.IsEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return document.Url().GetString().StartsWith(whitelist_scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if and only if the document settings specifies media playback
|
||||||
|
// requires user gesture.
|
||||||
|
bool ComputeLockedPendingUserGesture(Document& document) {
|
||||||
|
if (!document.GetSettings())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (IsDocumentWhitelisted(document)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.GetSettings()
|
||||||
|
->GetCrossOriginMediaPlaybackRequiresUserGesture() &&
|
||||||
|
IsDocumentCrossOrigin(document)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.GetSettings()->GetMediaPlaybackRequiresUserGesture();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
AutoplayPolicy::AutoplayPolicy(HTMLMediaElement* element)
|
||||||
|
: locked_pending_user_gesture_(false),
|
||||||
|
locked_pending_user_gesture_if_cross_origin_experiment_enabled_(true),
|
||||||
|
element_(element),
|
||||||
|
autoplay_visibility_observer_(nullptr),
|
||||||
|
autoplay_uma_helper_(AutoplayUmaHelper::Create(element)) {
|
||||||
|
locked_pending_user_gesture_ =
|
||||||
|
ComputeLockedPendingUserGesture(element->GetDocument());
|
||||||
|
locked_pending_user_gesture_if_cross_origin_experiment_enabled_ =
|
||||||
|
IsDocumentCrossOrigin(element->GetDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::VideoWillBeDrawnToCanvas() const {
|
||||||
|
autoplay_uma_helper_->VideoWillBeDrawnToCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::DidMoveToNewDocument(Document& old_document) {
|
||||||
|
// If any experiment is enabled, then we want to enable a user gesture by
|
||||||
|
// default, otherwise the experiment does nothing.
|
||||||
|
bool old_document_requires_user_gesture =
|
||||||
|
ComputeLockedPendingUserGesture(old_document);
|
||||||
|
bool new_document_requires_user_gesture =
|
||||||
|
ComputeLockedPendingUserGesture(element_->GetDocument());
|
||||||
|
if (new_document_requires_user_gesture && !old_document_requires_user_gesture)
|
||||||
|
locked_pending_user_gesture_ = true;
|
||||||
|
|
||||||
|
if (IsDocumentCrossOrigin(element_->GetDocument()) &&
|
||||||
|
!IsDocumentCrossOrigin(old_document))
|
||||||
|
locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = true;
|
||||||
|
|
||||||
|
autoplay_uma_helper_->DidMoveToNewDocument(old_document);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsEligibleForAutoplayMuted() const {
|
||||||
|
return element_->IsHTMLVideoElement() && element_->muted() &&
|
||||||
|
RuntimeEnabledFeatures::autoplayMutedVideosEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::StartAutoplayMutedWhenVisible() {
|
||||||
|
// We might end up in a situation where the previous
|
||||||
|
// observer didn't had time to fire yet. We can avoid
|
||||||
|
// creating a new one in this case.
|
||||||
|
if (autoplay_visibility_observer_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
autoplay_visibility_observer_ = new ElementVisibilityObserver(
|
||||||
|
element_, WTF::Bind(&AutoplayPolicy::OnVisibilityChangedForAutoplay,
|
||||||
|
WrapWeakPersistent(this)));
|
||||||
|
autoplay_visibility_observer_->Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::StopAutoplayMutedWhenVisible() {
|
||||||
|
if (!autoplay_visibility_observer_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
autoplay_visibility_observer_->Stop();
|
||||||
|
autoplay_visibility_observer_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::RequestAutoplayUnmute() {
|
||||||
|
DCHECK(!element_->muted());
|
||||||
|
bool was_autoplaying_muted = IsAutoplayingMutedInternal(true);
|
||||||
|
|
||||||
|
TryUnlockingUserGesture();
|
||||||
|
|
||||||
|
if (was_autoplaying_muted) {
|
||||||
|
if (IsGestureNeededForPlayback()) {
|
||||||
|
autoplay_uma_helper_->RecordAutoplayUnmuteStatus(
|
||||||
|
AutoplayUnmuteActionStatus::kFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
autoplay_uma_helper_->RecordAutoplayUnmuteStatus(
|
||||||
|
AutoplayUnmuteActionStatus::kSuccess);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::RequestAutoplayByAttribute() {
|
||||||
|
if (!ShouldAutoplay())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
autoplay_uma_helper_->OnAutoplayInitiated(AutoplaySource::kAttribute);
|
||||||
|
|
||||||
|
if (IsGestureNeededForPlayback()) {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kAutoplayBlocked);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kAutoplayBlocked);
|
||||||
|
} else {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kAutoplayAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the gesture is not needed for playback per the if statement
|
||||||
|
// above.
|
||||||
|
if (!IsEligibleForAutoplayMuted())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Autoplay muted video should be handled by AutoplayPolicy based on the
|
||||||
|
// visibily.
|
||||||
|
StartAutoplayMutedWhenVisible();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nullable<ExceptionCode> AutoplayPolicy::RequestPlay() {
|
||||||
|
if (!UserGestureIndicator::ProcessingUserGesture()) {
|
||||||
|
autoplay_uma_helper_->OnAutoplayInitiated(AutoplaySource::kMethod);
|
||||||
|
if (IsGestureNeededForPlayback()) {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kAutoplayBlocked);
|
||||||
|
return kNotAllowedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kAutoplayBlocked);
|
||||||
|
} else {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kAutoplayAllowed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
||||||
|
CrossOriginAutoplayResult::kPlayedWithGesture);
|
||||||
|
TryUnlockingUserGesture();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsAutoplayingMuted() const {
|
||||||
|
return IsAutoplayingMutedInternal(element_->muted());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsAutoplayingMutedInternal(bool muted) const {
|
||||||
|
if (!element_->IsHTMLVideoElement() ||
|
||||||
|
!RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !element_->paused() && muted && IsLockedPendingUserGesture();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsLockedPendingUserGesture() const {
|
||||||
|
return locked_pending_user_gesture_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::TryUnlockingUserGesture() {
|
||||||
|
if (IsLockedPendingUserGesture() &&
|
||||||
|
UserGestureIndicator::UtilizeUserGesture()) {
|
||||||
|
UnlockUserGesture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::UnlockUserGesture() {
|
||||||
|
locked_pending_user_gesture_ = false;
|
||||||
|
locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsGestureNeededForPlayback() const {
|
||||||
|
if (!locked_pending_user_gesture_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return IsGestureNeededForPlaybackIfPendingUserGestureIsLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsGestureNeededForPlaybackIfPendingUserGestureIsLocked()
|
||||||
|
const {
|
||||||
|
if (element_->GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We want to allow muted video to autoplay if:
|
||||||
|
// - the flag is enabled;
|
||||||
|
// - Data Saver is not enabled;
|
||||||
|
// - Preload was not disabled (low end devices);
|
||||||
|
// - Autoplay is enabled in settings;
|
||||||
|
if (element_->IsHTMLVideoElement() && element_->muted() &&
|
||||||
|
RuntimeEnabledFeatures::autoplayMutedVideosEnabled() &&
|
||||||
|
!(element_->GetDocument().GetSettings() &&
|
||||||
|
element_->GetDocument().GetSettings()->GetDataSaverEnabled()) &&
|
||||||
|
!(element_->GetDocument().GetSettings() &&
|
||||||
|
element_->GetDocument()
|
||||||
|
.GetSettings()
|
||||||
|
->GetForcePreloadNoneForMediaElements()) &&
|
||||||
|
IsAutoplayAllowedPerSettings()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoplayPolicy::OnVisibilityChangedForAutoplay(bool is_visible) {
|
||||||
|
if (!is_visible) {
|
||||||
|
if (element_->can_autoplay_ && element_->Autoplay()) {
|
||||||
|
element_->PauseInternal();
|
||||||
|
element_->can_autoplay_ = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShouldAutoplay()) {
|
||||||
|
element_->paused_ = false;
|
||||||
|
element_->ScheduleEvent(EventTypeNames::play);
|
||||||
|
element_->ScheduleNotifyPlaying();
|
||||||
|
|
||||||
|
element_->UpdatePlayState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()
|
||||||
|
const {
|
||||||
|
if (!locked_pending_user_gesture_if_cross_origin_experiment_enabled_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return IsGestureNeededForPlaybackIfPendingUserGestureIsLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::IsAutoplayAllowedPerSettings() const {
|
||||||
|
LocalFrame* frame = element_->GetDocument().GetFrame();
|
||||||
|
if (!frame)
|
||||||
|
return false;
|
||||||
|
return frame->GetContentSettingsClient()->AllowAutoplay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoplayPolicy::ShouldAutoplay() {
|
||||||
|
if (element_->GetDocument().IsSandboxed(kSandboxAutomaticFeatures))
|
||||||
|
return false;
|
||||||
|
return element_->can_autoplay_ && element_->paused_ && element_->Autoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TRACE(AutoplayPolicy) {
|
||||||
|
visitor->Trace(element_);
|
||||||
|
visitor->Trace(autoplay_visibility_observer_);
|
||||||
|
visitor->Trace(autoplay_uma_helper_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blink
|
113
third_party/WebKit/Source/core/html/media/AutoplayPolicy.h
vendored
Normal file
113
third_party/WebKit/Source/core/html/media/AutoplayPolicy.h
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef AutoplayPolicy_h
|
||||||
|
#define AutoplayPolicy_h
|
||||||
|
|
||||||
|
#include "bindings/core/v8/Nullable.h"
|
||||||
|
#include "core/dom/ExceptionCode.h"
|
||||||
|
#include "platform/heap/Handle.h"
|
||||||
|
|
||||||
|
namespace blink {
|
||||||
|
|
||||||
|
class AutoplayUmaHelper;
|
||||||
|
class Document;
|
||||||
|
class ElementVisibilityObserver;
|
||||||
|
class HTMLMediaElement;
|
||||||
|
|
||||||
|
// AutoplayPolicy is the class for handles autoplay logics.
|
||||||
|
class AutoplayPolicy final : public GarbageCollected<AutoplayPolicy> {
|
||||||
|
public:
|
||||||
|
explicit AutoplayPolicy(HTMLMediaElement*);
|
||||||
|
|
||||||
|
void VideoWillBeDrawnToCanvas() const;
|
||||||
|
|
||||||
|
// Called when the media element is moved to a new document.
|
||||||
|
void DidMoveToNewDocument(Document& old_document);
|
||||||
|
|
||||||
|
// Stop autoplaying the video element whenever its visible.
|
||||||
|
// TODO(mlamouri): hide these methods from HTMLMediaElement.
|
||||||
|
void StopAutoplayMutedWhenVisible();
|
||||||
|
|
||||||
|
// Request autoplay by attribute. This method will check the autoplay
|
||||||
|
// restrictions and record metrics. This method can only be called once per
|
||||||
|
// time the readyState changes to HAVE_ENOUGH_DATA.
|
||||||
|
bool RequestAutoplayByAttribute();
|
||||||
|
|
||||||
|
// Request the playback via play() method. This method will check the autoplay
|
||||||
|
// restrictions and record metrics. This method can only be called once
|
||||||
|
// per call of play().
|
||||||
|
Nullable<ExceptionCode> RequestPlay();
|
||||||
|
|
||||||
|
// Returns whether an umute action should pause an autoplaying element. The
|
||||||
|
// method will check autoplay restrictions and record metrics. This method can
|
||||||
|
// only be called once per call of setMuted().
|
||||||
|
bool RequestAutoplayUnmute();
|
||||||
|
|
||||||
|
bool IsAutoplayingMuted() const;
|
||||||
|
|
||||||
|
// Unlock user gesture if a user gesture can be utilized.
|
||||||
|
void TryUnlockingUserGesture();
|
||||||
|
|
||||||
|
// Return true if and only if a user gesture is requried for playback. Even
|
||||||
|
// if isLockedPendingUserGesture() return true, this might return false if
|
||||||
|
// the requirement is currently overridden. This does not check if a user
|
||||||
|
// gesture is currently being processed.
|
||||||
|
bool IsGestureNeededForPlayback() const;
|
||||||
|
|
||||||
|
DECLARE_VIRTUAL_TRACE();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AutoplayUmaHelper;
|
||||||
|
friend class AutoplayUmaHelperTest;
|
||||||
|
|
||||||
|
// Start autoplaying the video element whenever its visible.
|
||||||
|
void StartAutoplayMutedWhenVisible();
|
||||||
|
|
||||||
|
// Returns whether the media element is eligible to autoplay muted.
|
||||||
|
bool IsEligibleForAutoplayMuted() const;
|
||||||
|
|
||||||
|
bool ShouldAutoplay();
|
||||||
|
|
||||||
|
// If the user gesture is required, then this will remove it. Note that
|
||||||
|
// one should not generally call this method directly; use the one on
|
||||||
|
// m_helper and give it a reason.
|
||||||
|
void UnlockUserGesture();
|
||||||
|
|
||||||
|
// Return true if and only if a user gesture is required to unlock this
|
||||||
|
// media element for unrestricted autoplay/script control. Don't confuse
|
||||||
|
// this with isGestureNeededForPlayback(). The latter is usually what one
|
||||||
|
// should use, if checking to see if an action is allowed.
|
||||||
|
bool IsLockedPendingUserGesture() const;
|
||||||
|
|
||||||
|
bool IsLockedPendingUserGestureIfCrossOriginExperimentEnabled() const;
|
||||||
|
|
||||||
|
bool IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled() const;
|
||||||
|
|
||||||
|
bool IsGestureNeededForPlaybackIfPendingUserGestureIsLocked() const;
|
||||||
|
|
||||||
|
// Return true if and only if the settings allow autoplay of media on this
|
||||||
|
// frame.
|
||||||
|
bool IsAutoplayAllowedPerSettings() const;
|
||||||
|
|
||||||
|
bool IsAutoplayingMutedInternal(bool muted) const;
|
||||||
|
|
||||||
|
// Called when the video visibility changes while autoplaying muted, will
|
||||||
|
// pause the video when invisible and resume the video when visible.
|
||||||
|
void OnVisibilityChangedForAutoplay(bool is_visible);
|
||||||
|
|
||||||
|
bool locked_pending_user_gesture_ : 1;
|
||||||
|
bool locked_pending_user_gesture_if_cross_origin_experiment_enabled_ : 1;
|
||||||
|
|
||||||
|
Member<HTMLMediaElement> element_;
|
||||||
|
Member<ElementVisibilityObserver> autoplay_visibility_observer_;
|
||||||
|
|
||||||
|
Member<AutoplayUmaHelper> autoplay_uma_helper_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(AutoplayPolicy);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace blink
|
||||||
|
|
||||||
|
#endif // AutoplayPolicy_h
|
@ -8,7 +8,9 @@
|
|||||||
#include "core/dom/ElementVisibilityObserver.h"
|
#include "core/dom/ElementVisibilityObserver.h"
|
||||||
#include "core/events/Event.h"
|
#include "core/events/Event.h"
|
||||||
#include "core/frame/Settings.h"
|
#include "core/frame/Settings.h"
|
||||||
|
#include "core/frame/UseCounter.h"
|
||||||
#include "core/html/HTMLMediaElement.h"
|
#include "core/html/HTMLMediaElement.h"
|
||||||
|
#include "core/html/media/AutoplayPolicy.h"
|
||||||
#include "platform/Histogram.h"
|
#include "platform/Histogram.h"
|
||||||
#include "platform/wtf/CurrentTime.h"
|
#include "platform/wtf/CurrentTime.h"
|
||||||
#include "public/platform/Platform.h"
|
#include "public/platform/Platform.h"
|
||||||
@ -110,7 +112,8 @@ void AutoplayUmaHelper::OnAutoplayInitiated(AutoplaySource source) {
|
|||||||
bool data_saver_enabled =
|
bool data_saver_enabled =
|
||||||
element_->GetDocument().GetSettings() &&
|
element_->GetDocument().GetSettings() &&
|
||||||
element_->GetDocument().GetSettings()->GetDataSaverEnabled();
|
element_->GetDocument().GetSettings()->GetDataSaverEnabled();
|
||||||
bool blocked_by_setting = !element_->IsAutoplayAllowedPerSettings();
|
bool blocked_by_setting =
|
||||||
|
!element_->GetAutoplayPolicy().IsAutoplayAllowedPerSettings();
|
||||||
|
|
||||||
if (data_saver_enabled && blocked_by_setting) {
|
if (data_saver_enabled && blocked_by_setting) {
|
||||||
blocked_muted_video_histogram.Count(
|
blocked_muted_video_histogram.Count(
|
||||||
@ -213,6 +216,13 @@ void AutoplayUmaHelper::RecordAutoplayUnmuteStatus(
|
|||||||
autoplay_unmute_histogram.Count(static_cast<int>(status));
|
autoplay_unmute_histogram.Count(static_cast<int>(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AutoplayUmaHelper::VideoWillBeDrawnToCanvas() {
|
||||||
|
if (HasSource() && !IsVisible()) {
|
||||||
|
UseCounter::Count(element_->GetDocument(),
|
||||||
|
UseCounter::kHiddenAutoplayedVideoInCanvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AutoplayUmaHelper::DidMoveToNewDocument(Document& old_document) {
|
void AutoplayUmaHelper::DidMoveToNewDocument(Document& old_document) {
|
||||||
if (!ShouldListenToContextDestroyed())
|
if (!ShouldListenToContextDestroyed())
|
||||||
return;
|
return;
|
||||||
|
@ -75,6 +75,7 @@ class CORE_EXPORT AutoplayUmaHelper : public EventListener,
|
|||||||
void RecordCrossOriginAutoplayResult(CrossOriginAutoplayResult);
|
void RecordCrossOriginAutoplayResult(CrossOriginAutoplayResult);
|
||||||
void RecordAutoplayUnmuteStatus(AutoplayUnmuteActionStatus);
|
void RecordAutoplayUnmuteStatus(AutoplayUnmuteActionStatus);
|
||||||
|
|
||||||
|
void VideoWillBeDrawnToCanvas();
|
||||||
void DidMoveToNewDocument(Document& old_document);
|
void DidMoveToNewDocument(Document& old_document);
|
||||||
|
|
||||||
bool IsVisible() const { return is_visible_; }
|
bool IsVisible() const { return is_visible_; }
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "core/dom/Document.h"
|
#include "core/dom/Document.h"
|
||||||
#include "core/html/HTMLMediaElement.h"
|
#include "core/html/HTMLMediaElement.h"
|
||||||
#include "core/html/HTMLVideoElement.h"
|
#include "core/html/HTMLVideoElement.h"
|
||||||
|
#include "core/html/media/AutoplayPolicy.h"
|
||||||
#include "core/testing/DummyPageHolder.h"
|
#include "core/testing/DummyPageHolder.h"
|
||||||
|
|
||||||
#include "testing/gmock/include/gmock/gmock.h"
|
#include "testing/gmock/include/gmock/gmock.h"
|
||||||
@ -56,7 +57,7 @@ class AutoplayUmaHelperTest : public testing::Test {
|
|||||||
ASSERT_NO_EXCEPTION);
|
ASSERT_NO_EXCEPTION);
|
||||||
HTMLMediaElement& element = MediaElement();
|
HTMLMediaElement& element = MediaElement();
|
||||||
uma_helper_ = new MockAutoplayUmaHelper(&element);
|
uma_helper_ = new MockAutoplayUmaHelper(&element);
|
||||||
element.autoplay_uma_helper_ = uma_helper_;
|
element.autoplay_policy_->autoplay_uma_helper_ = uma_helper_;
|
||||||
::testing::Mock::AllowLeak(&UmaHelper());
|
::testing::Mock::AllowLeak(&UmaHelper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user