blob: e4fdb25fffadd0b4d59378b38c930eaf8423fd7c [file] [log] [blame]
/*
* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include <algorithm>
#include <limits>
#include "base/auto_reset.h"
#include "base/debug/crash_logging.h"
#include "base/memory/ptr_util.h"
#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h"
#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_state.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_audio_source_provider.h"
#include "third_party/blink/public/platform/web_content_decryption_module.h"
#include "third_party/blink/public/platform/web_inband_text_track.h"
#include "third_party/blink/public/platform/web_media_player.h"
#include "third_party/blink/public/platform/web_media_player_source.h"
#include "third_party/blink/public/platform/web_media_stream.h"
#include "third_party/blink/public/platform/web_screen_info.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/core_initializer.h"
#include "third_party/blink/renderer/core/css/media_list.h"
#include "third_party/blink/renderer/core/dom/attribute.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/element_visibility_observer.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/event_queue.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
#include "third_party/blink/renderer/core/html/html_source_element.h"
#include "third_party/blink/renderer/core/html/media/autoplay_policy.h"
#include "third_party/blink/renderer/core/html/media/html_media_element_controls_list.h"
#include "third_party/blink/renderer/core/html/media/html_media_source.h"
#include "third_party/blink/renderer/core/html/media/media_controls.h"
#include "third_party/blink/renderer/core/html/media/media_error.h"
#include "third_party/blink/renderer/core/html/media/media_fragment_uri_parser.h"
#include "third_party/blink/renderer/core/html/time_ranges.h"
#include "third_party/blink/renderer/core/html/track/audio_track.h"
#include "third_party/blink/renderer/core/html/track/audio_track_list.h"
#include "third_party/blink/renderer/core/html/track/automatic_track_selection.h"
#include "third_party/blink/renderer/core/html/track/cue_timeline.h"
#include "third_party/blink/renderer/core/html/track/html_track_element.h"
#include "third_party/blink/renderer/core/html/track/inband_text_track.h"
#include "third_party/blink/renderer/core/html/track/text_track_container.h"
#include "third_party/blink/renderer/core/html/track/text_track_list.h"
#include "third_party/blink/renderer/core/html/track/video_track.h"
#include "third_party/blink/renderer/core/html/track/video_track_list.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/intersection_geometry.h"
#include "third_party/blink/renderer/core/layout/layout_media.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
#include "third_party/blink/renderer/platform/audio/audio_source_provider_client.h"
#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
#include "third_party/blink/renderer/platform/network/mime/content_type.h"
#include "third_party/blink/renderer/platform/network/mime/mime_type_from_url.h"
#include "third_party/blink/renderer/platform/network/network_state_notifier.h"
#include "third_party/blink/renderer/platform/network/parsed_content_type.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/cstring.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#ifndef BLINK_MEDIA_LOG
#define BLINK_MEDIA_LOG DVLOG(3)
#endif
#ifndef LOG_MEDIA_EVENTS
// Default to not logging events because so many are generated they can
// overwhelm the rest of the logging.
#define LOG_MEDIA_EVENTS 0
#endif
#ifndef LOG_OFFICIAL_TIME_STATUS
// Default to not logging status of official time because it adds a fair amount
// of overhead and logging.
#define LOG_OFFICIAL_TIME_STATUS 0
#endif
namespace blink {
using namespace html_names;
using WeakMediaElementSet = HeapHashSet<WeakMember<HTMLMediaElement>>;
using DocumentElementSetMap =
HeapHashMap<WeakMember<Document>, Member<WeakMediaElementSet>>;
namespace {
constexpr float kMostlyFillViewportThreshold = 0.85f;
constexpr TimeDelta kCheckViewportIntersectionInterval =
TimeDelta::FromSeconds(1);
// This enum is used to record histograms. Do not reorder.
enum MediaControlsShow {
kMediaControlsShowAttribute = 0,
kMediaControlsShowFullscreen,
kMediaControlsShowNoScript,
kMediaControlsShowNotShown,
kMediaControlsShowDisabledSettings,
kMediaControlsShowMax
};
// These values are used for the Media.MediaElement.ContentTypeResult histogram.
// Do not reorder.
enum ContentTypeParseableResult {
kIsSupportedParseable = 0,
kMayBeSupportedParseable,
kIsNotSupportedParseable,
kIsSupportedNotParseable,
kMayBeSupportedNotParseable,
kIsNotSupportedNotParseable,
kContentTypeParseableMax
};
// This enum is used to record histograms. Do not reorder.
enum class PlayPromiseRejectReason {
kFailedAutoplayPolicy = 0,
kNoSupportedSources,
kInterruptedByPause,
kInterruptedByLoad,
kCount,
};
// Limits the range of media playback rate.
const double kMinRate = 0.0625;
const double kMaxRate = 16.0;
void ReportContentTypeResultToUMA(String content_type,
MIMETypeRegistry::SupportsType result) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
EnumerationHistogram, content_type_parseable_histogram,
("Media.MediaElement.ContentTypeParseable", kContentTypeParseableMax));
ParsedContentType parsed_content_type(content_type);
ContentTypeParseableResult uma_result = kIsNotSupportedNotParseable;
switch (result) {
case MIMETypeRegistry::kIsSupported:
uma_result = parsed_content_type.IsValid() ? kIsSupportedParseable
: kIsSupportedNotParseable;
break;
case MIMETypeRegistry::kMayBeSupported:
uma_result = parsed_content_type.IsValid() ? kMayBeSupportedParseable
: kMayBeSupportedNotParseable;
break;
case MIMETypeRegistry::kIsNotSupported:
uma_result = parsed_content_type.IsValid() ? kIsNotSupportedParseable
: kIsNotSupportedNotParseable;
break;
}
content_type_parseable_histogram.Count(uma_result);
}
String UrlForLoggingMedia(const KURL& url) {
static const unsigned kMaximumURLLengthForLogging = 128;
if (url.GetString().length() < kMaximumURLLengthForLogging)
return url.GetString();
return url.GetString().Substring(0, kMaximumURLLengthForLogging) + "...";
}
const char* BoolString(bool val) {
return val ? "true" : "false";
}
DocumentElementSetMap& DocumentToElementSetMap() {
DEFINE_STATIC_LOCAL(Persistent<DocumentElementSetMap>, map,
(MakeGarbageCollected<DocumentElementSetMap>()));
return *map;
}
void AddElementToDocumentMap(HTMLMediaElement* element, Document* document) {
DocumentElementSetMap& map = DocumentToElementSetMap();
WeakMediaElementSet* set = nullptr;
auto it = map.find(document);
if (it == map.end()) {
set = MakeGarbageCollected<WeakMediaElementSet>();
map.insert(document, set);
} else {
set = it->value;
}
set->insert(element);
}
void RemoveElementFromDocumentMap(HTMLMediaElement* element,
Document* document) {
DocumentElementSetMap& map = DocumentToElementSetMap();
auto it = map.find(document);
DCHECK(it != map.end());
WeakMediaElementSet* set = it->value;
set->erase(element);
if (set->IsEmpty())
map.erase(it);
}
String BuildElementErrorMessage(const String& error) {
// Prepend a UA-specific-error code before the first ':', to enable better
// collection and aggregation of UA-specific-error codes from
// MediaError.message by web apps. WebMediaPlayer::GetErrorMessage() should
// similarly conform to this format.
DEFINE_STATIC_LOCAL(const String, element_error_prefix,
("MEDIA_ELEMENT_ERROR: "));
StringBuilder builder;
builder.Append(element_error_prefix);
builder.Append(error);
return builder.ToString();
}
class AudioSourceProviderClientLockScope {
STACK_ALLOCATED();
public:
AudioSourceProviderClientLockScope(HTMLMediaElement& element)
: client_(element.AudioSourceNode()) {
if (client_)
client_->lock();
}
~AudioSourceProviderClientLockScope() {
if (client_)
client_->unlock();
}
private:
Member<AudioSourceProviderClient> client_;
};
const AtomicString& AudioKindToString(
WebMediaPlayerClient::AudioTrackKind kind) {
switch (kind) {
case WebMediaPlayerClient::kAudioTrackKindNone:
return g_empty_atom;
case WebMediaPlayerClient::kAudioTrackKindAlternative:
return AudioTrack::AlternativeKeyword();
case WebMediaPlayerClient::kAudioTrackKindDescriptions:
return AudioTrack::DescriptionsKeyword();
case WebMediaPlayerClient::kAudioTrackKindMain:
return AudioTrack::MainKeyword();
case WebMediaPlayerClient::kAudioTrackKindMainDescriptions:
return AudioTrack::MainDescriptionsKeyword();
case WebMediaPlayerClient::kAudioTrackKindTranslation:
return AudioTrack::TranslationKeyword();
case WebMediaPlayerClient::kAudioTrackKindCommentary:
return AudioTrack::CommentaryKeyword();
}
NOTREACHED();
return g_empty_atom;
}
const AtomicString& VideoKindToString(
WebMediaPlayerClient::VideoTrackKind kind) {
switch (kind) {
case WebMediaPlayerClient::kVideoTrackKindNone:
return g_empty_atom;
case WebMediaPlayerClient::kVideoTrackKindAlternative:
return VideoTrack::AlternativeKeyword();
case WebMediaPlayerClient::kVideoTrackKindCaptions:
return VideoTrack::CaptionsKeyword();
case WebMediaPlayerClient::kVideoTrackKindMain:
return VideoTrack::MainKeyword();
case WebMediaPlayerClient::kVideoTrackKindSign:
return VideoTrack::SignKeyword();
case WebMediaPlayerClient::kVideoTrackKindSubtitles:
return VideoTrack::SubtitlesKeyword();
case WebMediaPlayerClient::kVideoTrackKindCommentary:
return VideoTrack::CommentaryKeyword();
}
NOTREACHED();
return g_empty_atom;
}
bool CanLoadURL(const KURL& url, const String& content_type_str) {
DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
ContentType content_type(content_type_str);
String content_mime_type = content_type.GetType().DeprecatedLower();
String content_type_codecs = content_type.Parameter(codecs);
// If the MIME type is missing or is not meaningful, try to figure it out from
// the URL.
if (content_mime_type.IsEmpty() ||
content_mime_type == "application/octet-stream" ||
content_mime_type == "text/plain") {
if (url.ProtocolIsData())
content_mime_type = MimeTypeFromDataURL(url.GetString());
}
// If no MIME type is specified, always attempt to load.
if (content_mime_type.IsEmpty())
return true;
// 4.8.12.3 MIME types - In the absence of a specification to the contrary,
// the MIME type "application/octet-stream" when used with parameters, e.g.
// "application/octet-stream;codecs=theora", is a type that the user agent
// knows it cannot render.
if (content_mime_type != "application/octet-stream" ||
content_type_codecs.IsEmpty()) {
return MIMETypeRegistry::SupportsMediaMIMEType(content_mime_type,
content_type_codecs) !=
MIMETypeRegistry::kIsNotSupported;
}
return false;
}
String PreloadTypeToString(WebMediaPlayer::Preload preload_type) {
switch (preload_type) {
case WebMediaPlayer::kPreloadNone:
return "none";
case WebMediaPlayer::kPreloadMetaData:
return "metadata";
case WebMediaPlayer::kPreloadAuto:
return "auto";
}
NOTREACHED();
return String();
}
bool IsDocumentCrossOrigin(Document& document) {
const LocalFrame* frame = document.GetFrame();
return frame && frame->IsCrossOriginSubframe();
}
void RecordPlayPromiseRejected(PlayPromiseRejectReason reason) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, histogram,
("Media.MediaElement.PlayPromiseReject",
static_cast<int>(PlayPromiseRejectReason::kCount)));
histogram.Count(static_cast<int>(reason));
}
} // anonymous namespace
MIMETypeRegistry::SupportsType HTMLMediaElement::GetSupportsType(
const ContentType& content_type) {
DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
// TODO(https://crbug.com/809912): Finding source of mime parsing crash.
static base::debug::CrashKeyString* content_type_crash_key =
base::debug::AllocateCrashKeyString("media_content_type",
base::debug::CrashKeySize::Size256);
base::debug::ScopedCrashKeyString scoped_crash_key(
content_type_crash_key, content_type.Raw().Utf8().data());
String type = content_type.GetType().DeprecatedLower();
// The codecs string is not lower-cased because MP4 values are case sensitive
// per http://tools.ietf.org/html/rfc4281#page-7.
String type_codecs = content_type.Parameter(codecs);
if (type.IsEmpty())
return MIMETypeRegistry::kIsNotSupported;
// 4.8.12.3 MIME types - The canPlayType(type) method must return the empty
// string if type is a type that the user agent knows it cannot render or is
// the type "application/octet-stream"
if (type == "application/octet-stream")
return MIMETypeRegistry::kIsNotSupported;
// Check if stricter parsing of |contentType| will cause problems.
// TODO(jrummell): Either switch to ParsedContentType or remove this UMA,
// depending on the results reported.
MIMETypeRegistry::SupportsType result =
MIMETypeRegistry::SupportsMediaMIMEType(type, type_codecs);
ReportContentTypeResultToUMA(content_type.Raw(), result);
return result;
}
const HashSet<AtomicString>& HTMLMediaElement::GetCheckedAttributeNames()
const {
DEFINE_STATIC_LOCAL(HashSet<AtomicString>, attribute_set, ({"src"}));
return attribute_set;
}
bool HTMLMediaElement::IsHLSURL(const KURL& url) {
// Keep the same logic as in media_codec_util.h.
if (url.IsNull() || url.IsEmpty())
return false;
if (!url.IsLocalFile() && !url.ProtocolIs("http") && !url.ProtocolIs("https"))
return false;
return url.GetString().Contains("m3u8");
}
bool HTMLMediaElement::MediaTracksEnabledInternally() {
return RuntimeEnabledFeatures::AudioVideoTracksEnabled() ||
RuntimeEnabledFeatures::BackgroundVideoTrackOptimizationEnabled();
}
// static
void HTMLMediaElement::OnMediaControlsEnabledChange(Document* document) {
auto it = DocumentToElementSetMap().find(document);
if (it == DocumentToElementSetMap().end())
return;
DCHECK(it->value);
WeakMediaElementSet& elements = *it->value;
for (const auto& element : elements) {
element->UpdateControlsVisibility();
if (element->GetMediaControls())
element->GetMediaControls()->OnMediaControlsEnabledChange();
}
}
HTMLMediaElement::HTMLMediaElement(const QualifiedName& tag_name,
Document& document)
: HTMLElement(tag_name, document),
PausableObject(&document),
load_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
this,
&HTMLMediaElement::LoadTimerFired),
progress_event_timer_(
document.GetTaskRunner(TaskType::kMediaElementEvent),
this,
&HTMLMediaElement::ProgressEventTimerFired),
playback_progress_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
this,
&HTMLMediaElement::PlaybackProgressTimerFired),
audio_tracks_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
this,
&HTMLMediaElement::AudioTracksTimerFired),
check_viewport_intersection_timer_(
document.GetTaskRunner(TaskType::kInternalMedia),
this,
&HTMLMediaElement::CheckViewportIntersectionTimerFired),
removed_from_document_timer_(
document.GetTaskRunner(TaskType::kInternalMedia),
this,
&HTMLMediaElement::OnRemovedFromDocumentTimerFired),
played_time_ranges_(),
async_event_queue_(EventQueue::Create(GetExecutionContext(),
TaskType::kMediaElementEvent)),
playback_rate_(1.0f),
default_playback_rate_(1.0f),
network_state_(kNetworkEmpty),
ready_state_(kHaveNothing),
ready_state_maximum_(kHaveNothing),
volume_(1.0f),
last_seek_time_(0),
previous_progress_time_(std::numeric_limits<double>::max()),
duration_(std::numeric_limits<double>::quiet_NaN()),
last_time_update_event_media_time_(
std::numeric_limits<double>::quiet_NaN()),
default_playback_start_position_(0),
load_state_(kWaitingForSource),
deferred_load_state_(kNotDeferred),
deferred_load_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
this,
&HTMLMediaElement::DeferredLoadTimerFired),
cc_layer_(nullptr),
display_mode_(kUnknown),
official_playback_position_(0),
official_playback_position_needs_update_(true),
fragment_end_time_(std::numeric_limits<double>::quiet_NaN()),
pending_action_flags_(0),
playing_(false),
should_delay_load_event_(false),
have_fired_loaded_data_(false),
can_autoplay_(true),
muted_(false),
paused_(true),
seeking_(false),
sent_stalled_event_(false),
ignore_preload_none_(false),
text_tracks_visible_(false),
should_perform_automatic_track_selection_(true),
tracks_are_ready_(true),
processing_preference_change_(false),
playing_remotely_(false),
mostly_filling_viewport_(false),
was_always_muted_(true),
audio_tracks_(AudioTrackList::Create(*this)),
video_tracks_(VideoTrackList::Create(*this)),
audio_source_node_(nullptr),
autoplay_policy_(new AutoplayPolicy(this)),
remote_playback_client_(nullptr),
media_controls_(nullptr),
controls_list_(HTMLMediaElementControlsList::Create(this)),
lazy_load_visibility_observer_(nullptr) {
BLINK_MEDIA_LOG << "HTMLMediaElement(" << (void*)this << ")";
LocalFrame* frame = document.GetFrame();
if (frame) {
remote_playback_client_ =
frame->Client()->CreateWebRemotePlaybackClient(*this);
}
SetHasCustomStyleCallbacks();
AddElementToDocumentMap(this, &document);
UseCounter::Count(document, WebFeature::kHTMLMediaElement);
}
HTMLMediaElement::~HTMLMediaElement() {
BLINK_MEDIA_LOG << "~HTMLMediaElement(" << (void*)this << ")";
// audio_source_node_ is explicitly cleared by AudioNode::dispose().
// Since AudioNode::dispose() is guaranteed to be always called before
// the AudioNode is destructed, audio_source_node_ is explicitly cleared
// even if the AudioNode and the HTMLMediaElement die together.
DCHECK(!audio_source_node_);
}
void HTMLMediaElement::Dispose() {
// Destroying the player may cause a resource load to be canceled,
// which could result in LocalDOMWindow::dispatchWindowLoadEvent() being
// called via ResourceFetch::didLoadResource(), then
// FrameLoader::checkCompleted(). But it's guaranteed that the load event
// doesn't get dispatched during the object destruction.
// See Document::isDelayingLoadEvent().
// Also see http://crbug.com/275223 for more details.
ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
}
void HTMLMediaElement::DidMoveToNewDocument(Document& old_document) {
BLINK_MEDIA_LOG << "didMoveToNewDocument(" << (void*)this << ")";
load_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
progress_event_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
playback_progress_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
audio_tracks_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
check_viewport_intersection_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
deferred_load_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
removed_from_document_timer_.MoveToNewTaskRunner(
GetDocument().GetTaskRunner(TaskType::kInternalMedia));
autoplay_policy_->DidMoveToNewDocument(old_document);
if (should_delay_load_event_) {
GetDocument().IncrementLoadEventDelayCount();
// Note: Keeping the load event delay count increment on oldDocument that
// was added when should_delay_load_event_ was set so that destruction of
// web_media_player_ can not cause load event dispatching in oldDocument.
} else {
// Incrementing the load event delay count so that destruction of
// web_media_player_ can not cause load event dispatching in oldDocument.
old_document.IncrementLoadEventDelayCount();
}
RemoveElementFromDocumentMap(this, &old_document);
AddElementToDocumentMap(this, &GetDocument());
// FIXME: This is a temporary fix to prevent this object from causing the
// MediaPlayer to dereference LocalFrame and FrameLoader pointers from the
// previous document. This restarts the load, as if the src attribute had been
// set. A proper fix would provide a mechanism to allow this object to
// refresh the MediaPlayer's LocalFrame and FrameLoader references on document
// changes so that playback can be resumed properly.
ignore_preload_none_ = false;
InvokeLoadAlgorithm();
// Decrement the load event delay count on oldDocument now that
// web_media_player_ has been destroyed and there is no risk of dispatching a
// load event from within the destructor.
old_document.DecrementLoadEventDelayCount();
PausableObject::DidMoveToNewExecutionContext(&GetDocument());
HTMLElement::DidMoveToNewDocument(old_document);
}
bool HTMLMediaElement::SupportsFocus() const {
if (ownerDocument()->IsMediaDocument())
return false;
// If no controls specified, we should still be able to focus the element if
// it has tabIndex.
return ShouldShowControls() || HTMLElement::SupportsFocus();
}
bool HTMLMediaElement::IsMouseFocusable() const {
return false;
}
void HTMLMediaElement::ParseAttribute(
const AttributeModificationParams& params) {
const QualifiedName& name = params.name;
if (name == kSrcAttr) {
BLINK_MEDIA_LOG << "parseAttribute(" << (void*)this
<< ", kSrcAttr, old=" << params.old_value
<< ", new=" << params.new_value << ")";
// Trigger a reload, as long as the 'src' attribute is present.
if (!params.new_value.IsNull()) {
ignore_preload_none_ = false;
InvokeLoadAlgorithm();
}
} else if (name == kControlsAttr) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementControlsAttribute);
UpdateControlsVisibility();
} else if (name == kControlslistAttr) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementControlsListAttribute);
if (params.old_value != params.new_value) {
controls_list_->DidUpdateAttributeValue(params.old_value,
params.new_value);
if (GetMediaControls())
GetMediaControls()->OnControlsListUpdated();
}
} else if (name == kPreloadAttr) {
SetPlayerPreload();
} else if (name == kDisableremoteplaybackAttr) {
// This attribute is an extension described in the Remote Playback API spec.
// Please see: https://w3c.github.io/remote-playback
UseCounter::Count(GetDocument(),
WebFeature::kDisableRemotePlaybackAttribute);
if (params.old_value != params.new_value) {
if (web_media_player_) {
web_media_player_->RequestRemotePlaybackDisabled(
!params.new_value.IsNull());
}
}
} else {
HTMLElement::ParseAttribute(params);
}
}
void HTMLMediaElement::ParserDidSetAttributes() {
HTMLElement::ParserDidSetAttributes();
if (FastHasAttribute(kMutedAttr))
muted_ = true;
}
// This method is being used as a way to know that cloneNode finished cloning
// attribute as there is no callback notifying about the end of a cloning
// operation. Indeed, it is required per spec to set the muted state based on
// the content attribute when the object is created.
void HTMLMediaElement::CloneNonAttributePropertiesFrom(const Element& other,
CloneChildrenFlag flag) {
HTMLElement::CloneNonAttributePropertiesFrom(other, flag);
if (FastHasAttribute(kMutedAttr))
muted_ = true;
}
void HTMLMediaElement::FinishParsingChildren() {
HTMLElement::FinishParsingChildren();
if (Traversal<HTMLTrackElement>::FirstChild(*this))
ScheduleTextTrackResourceLoad();
}
bool HTMLMediaElement::LayoutObjectIsNeeded(const ComputedStyle& style) const {
return ShouldShowControls() && HTMLElement::LayoutObjectIsNeeded(style);
}
LayoutObject* HTMLMediaElement::CreateLayoutObject(const ComputedStyle&) {
return new LayoutMedia(this);
}
Node::InsertionNotificationRequest HTMLMediaElement::InsertedInto(
ContainerNode& insertion_point) {
BLINK_MEDIA_LOG << "insertedInto(" << (void*)this << ", " << insertion_point
<< ")";
HTMLElement::InsertedInto(insertion_point);
if (insertion_point.isConnected()) {
UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementInDocument);
if ((!getAttribute(kSrcAttr).IsEmpty() || src_object_) &&
network_state_ == kNetworkEmpty) {
ignore_preload_none_ = false;
InvokeLoadAlgorithm();
}
}
return kInsertionShouldCallDidNotifySubtreeInsertions;
}
void HTMLMediaElement::DidNotifySubtreeInsertionsToDocument() {
UpdateControlsVisibility();
}
void HTMLMediaElement::RemovedFrom(ContainerNode& insertion_point) {
BLINK_MEDIA_LOG << "removedFrom(" << (void*)this << ", " << insertion_point
<< ")";
removed_from_document_timer_.StartOneShot(TimeDelta(), FROM_HERE);
HTMLElement::RemovedFrom(insertion_point);
}
void HTMLMediaElement::AttachLayoutTree(AttachContext& context) {
HTMLElement::AttachLayoutTree(context);
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
void HTMLMediaElement::DidRecalcStyle(StyleRecalcChange) {
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
void HTMLMediaElement::ScheduleTextTrackResourceLoad() {
BLINK_MEDIA_LOG << "scheduleTextTrackResourceLoad(" << (void*)this << ")";
pending_action_flags_ |= kLoadTextTrackResource;
if (!load_timer_.IsActive())
load_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
void HTMLMediaElement::ScheduleNextSourceChild() {
// Schedule the timer to try the next <source> element WITHOUT resetting state
// ala invokeLoadAlgorithm.
pending_action_flags_ |= kLoadMediaResource;
load_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
void HTMLMediaElement::ScheduleEvent(const AtomicString& event_name) {
Event* event = Event::CreateCancelable(event_name);
event->SetTarget(this);
ScheduleEvent(event);
}
void HTMLMediaElement::ScheduleEvent(Event* event) {
#if LOG_MEDIA_EVENTS
BLINK_MEDIA_LOG << "ScheduleEvent(" << (void*)this << ")"
<< " - scheduling '" << event->type() << "'";
#endif
async_event_queue_->EnqueueEvent(FROM_HERE, *event);
}
void HTMLMediaElement::LoadTimerFired(TimerBase*) {
if (pending_action_flags_ & kLoadTextTrackResource)
HonorUserPreferencesForAutomaticTextTrackSelection();
if (pending_action_flags_ & kLoadMediaResource) {
if (load_state_ == kLoadingFromSourceElement)
LoadNextSourceChild();
else
LoadInternal();
}
pending_action_flags_ = 0;
}
MediaError* HTMLMediaElement::error() const {
return error_;
}
void HTMLMediaElement::SetSrc(const AtomicString& url) {
setAttribute(kSrcAttr, url);
}
void HTMLMediaElement::SetSrc(const USVStringOrTrustedURL& stringOrURL,
ExceptionState& exception_state) {
setAttribute(kSrcAttr, stringOrURL, exception_state);
}
void HTMLMediaElement::SetSrcObject(MediaStreamDescriptor* src_object) {
BLINK_MEDIA_LOG << "setSrcObject(" << (void*)this << ")";
src_object_ = src_object;
InvokeLoadAlgorithm();
}
HTMLMediaElement::NetworkState HTMLMediaElement::getNetworkState() const {
return network_state_;
}
String HTMLMediaElement::canPlayType(const String& mime_type) const {
MIMETypeRegistry::SupportsType support =
GetSupportsType(ContentType(mime_type));
String can_play;
// 4.8.12.3
switch (support) {
case MIMETypeRegistry::kIsNotSupported:
can_play = g_empty_string;
break;
case MIMETypeRegistry::kMayBeSupported:
can_play = "maybe";
break;
case MIMETypeRegistry::kIsSupported:
can_play = "probably";
break;
}
BLINK_MEDIA_LOG << "canPlayType(" << (void*)this << ", " << mime_type
<< ") -> " << can_play;
return can_play;
}
void HTMLMediaElement::load() {
BLINK_MEDIA_LOG << "load(" << (void*)this << ")";
autoplay_policy_->TryUnlockingUserGesture();
ignore_preload_none_ = true;
InvokeLoadAlgorithm();
}
// TODO(srirama.m): Currently ignore_preload_none_ is reset before calling
// invokeLoadAlgorithm() in all places except load(). Move it inside here
// once microtask is implemented for "Await a stable state" step
// in resource selection algorithm.
void HTMLMediaElement::InvokeLoadAlgorithm() {
BLINK_MEDIA_LOG << "invokeLoadAlgorithm(" << (void*)this << ")";
// Perform the cleanup required for the resource load algorithm to run.
StopPeriodicTimers();
load_timer_.Stop();
CancelDeferredLoad();
// FIXME: Figure out appropriate place to reset LoadTextTrackResource if
// necessary and set pending_action_flags_ to 0 here.
pending_action_flags_ &= ~kLoadMediaResource;
sent_stalled_event_ = false;
have_fired_loaded_data_ = false;
display_mode_ = kUnknown;
autoplay_policy_->StopAutoplayMutedWhenVisible();
// 1 - Abort any already-running instance of the resource selection algorithm
// for this element.
load_state_ = kWaitingForSource;
current_source_node_ = nullptr;
// 2 - Let pending tasks be a list of tasks from the media element's media
// element task source in one of the task queues.
//
// 3 - For each task in the pending tasks that would run resolve pending
// play promises or project pending play prmoises algorithms, immediately
// resolve or reject those promises in the order the corresponding tasks
// were queued.
//
// TODO(mlamouri): the promises are first resolved then rejected but the
// order between resolved/rejected promises isn't respected. This could be
// improved when the same task is used for both cases.
//
// TODO(mlamouri): don't run the callback synchronously if we are not allowed
// to run scripts. It can happen in some edge cases. https://crbug.com/660382
if (play_promise_resolve_task_handle_.IsActive() &&
!ScriptForbiddenScope::IsScriptForbidden()) {
play_promise_resolve_task_handle_.Cancel();
ResolveScheduledPlayPromises();
}
if (play_promise_reject_task_handle_.IsActive() &&
!ScriptForbiddenScope::IsScriptForbidden()) {
play_promise_reject_task_handle_.Cancel();
RejectScheduledPlayPromises();
}
// 4 - Remove each task in pending tasks from its task queue.
CancelPendingEventsAndCallbacks();
// 5 - If the media element's networkState is set to NETWORK_LOADING or
// NETWORK_IDLE, queue a task to fire a simple event named abort at the media
// element.
if (network_state_ == kNetworkLoading || network_state_ == kNetworkIdle)
ScheduleEvent(event_type_names::kAbort);
ResetMediaPlayerAndMediaSource();
// 6 - If the media element's networkState is not set to NETWORK_EMPTY, then
// run these substeps
if (network_state_ != kNetworkEmpty) {
// 4.1 - Queue a task to fire a simple event named emptied at the media
// element.
ScheduleEvent(event_type_names::kEmptied);
// 4.2 - If a fetching process is in progress for the media element, the
// user agent should stop it.
SetNetworkState(kNetworkEmpty);
// 4.4 - Forget the media element's media-resource-specific tracks.
ForgetResourceSpecificTracks();
// 4.5 - If readyState is not set to kHaveNothing, then set it to that
// state.
ready_state_ = kHaveNothing;
ready_state_maximum_ = kHaveNothing;
DCHECK(!paused_ || play_promise_resolvers_.IsEmpty());
// 4.6 - If the paused attribute is false, then run these substeps
if (!paused_) {
// 4.6.1 - Set the paused attribute to true.
paused_ = true;
// 4.6.2 - Take pending play promises and reject pending play promises
// with the result and an "AbortError" DOMException.
RecordPlayPromiseRejected(PlayPromiseRejectReason::kInterruptedByLoad);
RejectPlayPromises(DOMExceptionCode::kAbortError,
"The play() request was interrupted by a new load "
"request. https://goo.gl/LdLk22");
}
// 4.7 - If seeking is true, set it to false.
seeking_ = false;
// 4.8 - Set the current playback position to 0.
// Set the official playback position to 0.
// If this changed the official playback position, then queue a task
// to fire a simple event named timeupdate at the media element.
// 4.9 - Set the initial playback position to 0.
SetOfficialPlaybackPosition(0);
ScheduleTimeupdateEvent(false);
// 4.10 - Set the timeline offset to Not-a-Number (NaN).
// 4.11 - Update the duration attribute to Not-a-Number (NaN).
GetCueTimeline().UpdateActiveCues(0);
} else if (!paused_) {
// TODO(foolip): There is a proposal to always reset the paused state
// in the media element load algorithm, to avoid a bogus play() promise
// rejection: https://github.com/whatwg/html/issues/869
// This is where that change would have an effect, and it is measured to
// verify the assumption that it's a very rare situation.
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementLoadNetworkEmptyNotPaused);
}
// 7 - Set the playbackRate attribute to the value of the defaultPlaybackRate
// attribute.
setPlaybackRate(defaultPlaybackRate());
// 8 - Set the error attribute to null and the can autoplay flag to true.
error_ = nullptr;
can_autoplay_ = true;
// 9 - Invoke the media element's resource selection algorithm.
InvokeResourceSelectionAlgorithm();
// 10 - Note: Playback of any previously playing media resource for this
// element stops.
}
void HTMLMediaElement::InvokeResourceSelectionAlgorithm() {
BLINK_MEDIA_LOG << "invokeResourceSelectionAlgorithm(" << (void*)this << ")";
// The resource selection algorithm
// 1 - Set the networkState to NETWORK_NO_SOURCE
SetNetworkState(kNetworkNoSource);
// 2 - Set the element's show poster flag to true
// TODO(srirama.m): Introduce show poster flag and update it as per spec
played_time_ranges_ = TimeRanges::Create();
// FIXME: Investigate whether these can be moved into network_state_ !=
// kNetworkEmpty block above
// so they are closer to the relevant spec steps.
last_seek_time_ = 0;
duration_ = std::numeric_limits<double>::quiet_NaN();
// 3 - Set the media element's delaying-the-load-event flag to true (this
// delays the load event)
SetShouldDelayLoadEvent(true);
if (GetMediaControls() && isConnected())
GetMediaControls()->Reset();
// 4 - Await a stable state, allowing the task that invoked this algorithm to
// continue
// TODO(srirama.m): Remove scheduleNextSourceChild() and post a microtask
// instead. See http://crbug.com/593289 for more details.
ScheduleNextSourceChild();
}
void HTMLMediaElement::LoadInternal() {
// HTMLMediaElement::textTracksAreReady will need "... the text tracks whose
// mode was not in the disabled state when the element's resource selection
// algorithm last started".
text_tracks_when_resource_selection_began_.clear();
if (text_tracks_) {
for (unsigned i = 0; i < text_tracks_->length(); ++i) {
TextTrack* track = text_tracks_->AnonymousIndexedGetter(i);
if (track->mode() != TextTrack::DisabledKeyword())
text_tracks_when_resource_selection_began_.push_back(track);
}
}
SelectMediaResource();
}
void HTMLMediaElement::SelectMediaResource() {
BLINK_MEDIA_LOG << "selectMediaResource(" << (void*)this << ")";
enum Mode { kObject, kAttribute, kChildren, kNothing };
Mode mode = kNothing;
// 6 - If the media element has an assigned media provider object, then let
// mode be object.
if (src_object_) {
mode = kObject;
} else if (FastHasAttribute(kSrcAttr)) {
// Otherwise, if the media element has no assigned media provider object
// but has a src attribute, then let mode be attribute.
mode = kAttribute;
} else if (HTMLSourceElement* element =
Traversal<HTMLSourceElement>::FirstChild(*this)) {
// Otherwise, if the media element does not have an assigned media
// provider object and does not have a src attribute, but does have a
// source element child, then let mode be children and let candidate be
// the first such source element child in tree order.
mode = kChildren;
next_child_node_to_consider_ = element;
current_source_node_ = nullptr;
} else {
// Otherwise the media element has no assigned media provider object and
// has neither a src attribute nor a source element child: set the
// networkState to kNetworkEmpty, and abort these steps; the synchronous
// section ends.
// TODO(mlamouri): Setting the network state to empty implies that there
// should be no |web_media_player_|. However, if a previous playback ended
// due to an error, we can get here and still have one. Decide on a plan
// to deal with this properly. https://crbug.com/789737
load_state_ = kWaitingForSource;
SetShouldDelayLoadEvent(false);
if (!GetWebMediaPlayer() || (ready_state_ < kHaveFutureData &&
ready_state_maximum_ < kHaveFutureData)) {
SetNetworkState(kNetworkEmpty);
} else {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementEmptyLoadWithFutureData);
}
UpdateDisplayState();
BLINK_MEDIA_LOG << "selectMediaResource(" << (void*)this
<< "), nothing to load";
return;
}
// 7 - Set the media element's networkState to NETWORK_LOADING.
SetNetworkState(kNetworkLoading);
// 8 - Queue a task to fire a simple event named loadstart at the media
// element.
ScheduleEvent(event_type_names::kLoadstart);
// 9 - Run the appropriate steps...
switch (mode) {
case kObject:
LoadSourceFromObject();
BLINK_MEDIA_LOG << "selectMediaResource(" << (void*)this
<< ", using 'srcObject' attribute";
break;
case kAttribute:
LoadSourceFromAttribute();
BLINK_MEDIA_LOG << "selectMediaResource(" << (void*)this
<< "), using 'src' attribute url";
break;
case kChildren:
LoadNextSourceChild();
BLINK_MEDIA_LOG << "selectMediaResource(" << (void*)this
<< "), using source element";
break;
default:
NOTREACHED();
}
}
void HTMLMediaElement::LoadSourceFromObject() {
DCHECK(src_object_);
load_state_ = kLoadingFromSrcObject;
// No type is available when the resource comes from the 'srcObject'
// attribute.
LoadResource(WebMediaPlayerSource(WebMediaStream(src_object_)), String());
}
void HTMLMediaElement::LoadSourceFromAttribute() {
load_state_ = kLoadingFromSrcAttr;
const AtomicString& src_value = FastGetAttribute(kSrcAttr);
// If the src attribute's value is the empty string ... jump down to the
// failed step below
if (src_value.IsEmpty()) {
BLINK_MEDIA_LOG << "LoadSourceFromAttribute(" << (void*)this
<< "), empty 'src'";
MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
BuildElementErrorMessage("Empty src attribute"));
return;
}
KURL media_url = GetDocument().CompleteURL(src_value);
if (!IsSafeToLoadURL(media_url, kComplain)) {
MediaLoadingFailed(
WebMediaPlayer::kNetworkStateFormatError,
BuildElementErrorMessage("Media load rejected by URL safety check"));
return;
}
// No type is available when the url comes from the 'src' attribute so
// MediaPlayer will have to pick a media engine based on the file extension.
LoadResource(WebMediaPlayerSource(WebURL(media_url)), String());
}
void HTMLMediaElement::LoadNextSourceChild() {
String content_type;
KURL media_url = SelectNextSourceChild(&content_type, kComplain);
if (!media_url.IsValid()) {
WaitForSourceChange();
return;
}
// Reset the MediaPlayer and MediaSource if any
ResetMediaPlayerAndMediaSource();
load_state_ = kLoadingFromSourceElement;
LoadResource(WebMediaPlayerSource(WebURL(media_url)), content_type);
}
void HTMLMediaElement::LoadResource(const WebMediaPlayerSource& source,
const String& content_type) {
DCHECK(IsMainThread());
KURL url;
if (source.IsURL()) {
url = source.GetAsURL();
DCHECK(IsSafeToLoadURL(url, kComplain));
BLINK_MEDIA_LOG << "loadResource(" << (void*)this << ", "
<< UrlForLoggingMedia(url) << ", " << content_type << ")";
}
LocalFrame* frame = GetDocument().GetFrame();
if (!frame) {
MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
BuildElementErrorMessage(
"Resource load failure: document has no frame"));
return;
}
// The resource fetch algorithm
SetNetworkState(kNetworkLoading);
// Set current_src_ *before* changing to the cache url, the fact that we are
// loading from the app cache is an internal detail not exposed through the
// media element API.
current_src_ = url;
if (audio_source_node_)
audio_source_node_->OnCurrentSrcChanged(current_src_);
// Update remote playback client with the new src and consider it incompatible
// until proved otherwise.
RemotePlaybackCompatibilityChanged(current_src_, false);
BLINK_MEDIA_LOG << "loadResource(" << (void*)this << ") - current_src_ -> "
<< UrlForLoggingMedia(current_src_);
StartProgressEventTimer();
// Reset display mode to force a recalculation of what to show because we are
// resetting the player.
SetDisplayMode(kUnknown);
SetPlayerPreload();
DCHECK(!media_source_);
bool attempt_load = true;
media_source_ = HTMLMediaSource::Lookup(url.GetString());
if (media_source_ && !media_source_->AttachToElement(this)) {
// Forget our reference to the MediaSource, so we leave it alone
// while processing remainder of load failure.
media_source_ = nullptr;
attempt_load = false;
}
bool can_load_resource =
source.IsMediaStream() || CanLoadURL(url, content_type);
if (attempt_load && can_load_resource) {
DCHECK(!GetWebMediaPlayer());
// Conditionally defer the load if effective preload is 'none'.
// Skip this optional deferral for MediaStream sources or any blob URL,
// including MediaSource blob URLs.
if (!source.IsMediaStream() && !url.ProtocolIs("blob") &&
EffectivePreloadType() == WebMediaPlayer::kPreloadNone) {
BLINK_MEDIA_LOG << "loadResource(" << (void*)this
<< ") : Delaying load because preload == 'none'";
DeferLoad();
} else {
StartPlayerLoad();
}
} else {
MediaLoadingFailed(
WebMediaPlayer::kNetworkStateFormatError,
BuildElementErrorMessage(attempt_load
? "Unable to load URL due to content type"
: "Unable to attach MediaSource"));
}
// If there is no poster to display, allow the media engine to render video
// frames as soon as they are available.
UpdateDisplayState();
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
void HTMLMediaElement::StartPlayerLoad() {
DCHECK(!web_media_player_);
WebMediaPlayerSource source;
if (src_object_) {
source = WebMediaPlayerSource(WebMediaStream(src_object_));
} else {
// Filter out user:pass as those two URL components aren't
// considered for media resource fetches (including for the CORS
// use-credentials mode.) That behavior aligns with Gecko, with IE
// being more restrictive and not allowing fetches to such URLs.
//
// Spec reference: http://whatwg.org/c/#concept-media-load-resource
//
// FIXME: when the HTML spec switches to specifying resource
// fetches in terms of Fetch (http://fetch.spec.whatwg.org), and
// along with that potentially also specifying a setting for its
// 'authentication flag' to control how user:pass embedded in a
// media resource URL should be treated, then update the handling
// here to match.
KURL request_url = current_src_;
if (!request_url.User().IsEmpty())
request_url.SetUser(String());
if (!request_url.Pass().IsEmpty())
request_url.SetPass(String());
KURL kurl(request_url);
source = WebMediaPlayerSource(WebURL(kurl));
}
LocalFrame* frame = GetDocument().GetFrame();
// TODO(srirama.m): Figure out how frame can be null when
// coming from executeDeferredLoad()
if (!frame) {
MediaLoadingFailed(
WebMediaPlayer::kNetworkStateFormatError,
BuildElementErrorMessage("Player load failure: document has no frame"));
return;
}
web_media_player_ = frame->Client()->CreateWebMediaPlayer(
*this, source, this,
frame->GetPage()->GetChromeClient().GetWebLayerTreeView(frame));
if (!web_media_player_) {
MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
BuildElementErrorMessage(
"Player load failure: error creating media player"));
return;
}
if (GetLayoutObject())
GetLayoutObject()->SetShouldDoFullPaintInvalidation();
// Make sure if we create/re-create the WebMediaPlayer that we update our
// wrapper.
audio_source_provider_.Wrap(web_media_player_->GetAudioSourceProvider());
web_media_player_->SetVolume(EffectiveMediaVolume());
web_media_player_->SetPoster(PosterImageURL());
const auto preload = EffectivePreloadType();
web_media_player_->SetPreload(preload);
web_media_player_->RequestRemotePlaybackDisabled(
FastHasAttribute(kDisableremoteplaybackAttr));
auto load_timing = web_media_player_->Load(GetLoadType(), source, CorsMode());
if (load_timing == WebMediaPlayer::LoadTiming::kDeferred) {
// Deferred media loading is not part of the spec, but intuition is that
// this should not hold up the Window's "load" event (similar to user
// gesture requirements).
SetShouldDelayLoadEvent(false);
}
if (IsFullscreen())
web_media_player_->EnteredFullscreen();
web_media_player_->BecameDominantVisibleContent(mostly_filling_viewport_);
}
void HTMLMediaElement::SetPlayerPreload() {
if (web_media_player_)
web_media_player_->SetPreload(EffectivePreloadType());
if (LoadIsDeferred() &&
EffectivePreloadType() != WebMediaPlayer::kPreloadNone)
StartDeferredLoad();
}
bool HTMLMediaElement::LoadIsDeferred() const {
return deferred_load_state_ != kNotDeferred;
}
void HTMLMediaElement::DeferLoad() {
// This implements the "optional" step 4 from the resource fetch algorithm
// "If mode is remote".
DCHECK(!deferred_load_timer_.IsActive());
DCHECK_EQ(deferred_load_state_, kNotDeferred);
// 1. Set the networkState to NETWORK_IDLE.
// 2. Queue a task to fire a simple event named suspend at the element.
ChangeNetworkStateFromLoadingToIdle();
// 3. Queue a task to set the element's delaying-the-load-event
// flag to false. This stops delaying the load event.
deferred_load_timer_.StartOneShot(TimeDelta(), FROM_HERE);
// 4. Wait for the task to be run.
deferred_load_state_ = kWaitingForStopDelayingLoadEventTask;
// Continued in executeDeferredLoad().
}
void HTMLMediaElement::CancelDeferredLoad() {
deferred_load_timer_.Stop();
deferred_load_state_ = kNotDeferred;
}
void HTMLMediaElement::ExecuteDeferredLoad() {
DCHECK_GE(deferred_load_state_, kWaitingForTrigger);
// resource fetch algorithm step 4 - continued from deferLoad().
// 5. Wait for an implementation-defined event (e.g. the user requesting that
// the media element begin playback). This is assumed to be whatever 'event'
// ended up calling this method.
CancelDeferredLoad();
// 6. Set the element's delaying-the-load-event flag back to true (this
// delays the load event again, in case it hasn't been fired yet).
SetShouldDelayLoadEvent(true);
// 7. Set the networkState to NETWORK_LOADING.
SetNetworkState(kNetworkLoading);
StartProgressEventTimer();
StartPlayerLoad();
}
void HTMLMediaElement::StartDeferredLoad() {
if (deferred_load_state_ == kWaitingForTrigger) {
ExecuteDeferredLoad();
return;
}
if (deferred_load_state_ == kExecuteOnStopDelayingLoadEventTask)
return;
DCHECK_EQ(deferred_load_state_, kWaitingForStopDelayingLoadEventTask);
deferred_load_state_ = kExecuteOnStopDelayingLoadEventTask;
}
void HTMLMediaElement::DeferredLoadTimerFired(TimerBase*) {
SetShouldDelayLoadEvent(false);
if (deferred_load_state_ == kExecuteOnStopDelayingLoadEventTask) {
ExecuteDeferredLoad();
return;
}
DCHECK_EQ(deferred_load_state_, kWaitingForStopDelayingLoadEventTask);
deferred_load_state_ = kWaitingForTrigger;
}
WebMediaPlayer::LoadType HTMLMediaElement::GetLoadType() const {
if (media_source_)
return WebMediaPlayer::kLoadTypeMediaSource;
if (src_object_)
return WebMediaPlayer::kLoadTypeMediaStream;
return WebMediaPlayer::kLoadTypeURL;
}
bool HTMLMediaElement::TextTracksAreReady() const {
// 4.8.12.11.1 Text track model
// ...
// The text tracks of a media element are ready if all the text tracks whose
// mode was not in the disabled state when the element's resource selection
// algorithm last started now have a text track readiness state of loaded or
// failed to load.
for (const auto& text_track : text_tracks_when_resource_selection_began_) {
if (text_track->GetReadinessState() == TextTrack::kLoading ||
text_track->GetReadinessState() == TextTrack::kNotLoaded)
return false;
}
return true;
}
void HTMLMediaElement::TextTrackReadyStateChanged(TextTrack* track) {
if (GetWebMediaPlayer() &&
text_tracks_when_resource_selection_began_.Contains(track)) {
if (track->GetReadinessState() != TextTrack::kLoading) {
SetReadyState(
static_cast<ReadyState>(GetWebMediaPlayer()->GetReadyState()));
}
} else {
// The track readiness state might have changed as a result of the user
// clicking the captions button. In this case, a check whether all the
// resources have failed loading should be done in order to hide the CC
// button.
// TODO(mlamouri): when an HTMLTrackElement fails to load, it is not
// propagated to the TextTrack object in a web exposed fashion. We have to
// keep relying on a custom glue to the controls while this is taken care
// of on the web side. See https://crbug.com/669977
if (GetMediaControls() &&
track->GetReadinessState() == TextTrack::kFailedToLoad) {
GetMediaControls()->OnTrackElementFailedToLoad();
}
}
}
void HTMLMediaElement::TextTrackModeChanged(TextTrack* track) {
// Mark this track as "configured" so configureTextTracks won't change the
// mode again.
if (track->TrackType() == TextTrack::kTrackElement)
track->SetHasBeenConfigured(true);
ConfigureTextTrackDisplay();
DCHECK(textTracks()->Contains(track));
textTracks()->ScheduleChangeEvent();
}
void HTMLMediaElement::DisableAutomaticTextTrackSelection() {
should_perform_automatic_track_selection_ = false;
}
bool HTMLMediaElement::IsSafeToLoadURL(const KURL& url,
InvalidURLAction action_if_invalid) {
if (!url.IsValid()) {
BLINK_MEDIA_LOG << "isSafeToLoadURL(" << (void*)this << ", "
<< UrlForLoggingMedia(url)
<< ") -> FALSE because url is invalid";
return false;
}
LocalFrame* frame = GetDocument().GetFrame();
if (!frame || !GetDocument().GetSecurityOrigin()->CanDisplay(url)) {
if (action_if_invalid == kComplain) {
GetDocument().AddConsoleMessage(ConsoleMessage::Create(
kSecurityMessageSource, kErrorMessageLevel,
"Not allowed to load local resource: " + url.ElidedString()));
}
BLINK_MEDIA_LOG << "isSafeToLoadURL(" << (void*)this << ", "
<< UrlForLoggingMedia(url)
<< ") -> FALSE rejected by SecurityOrigin";
return false;
}
if (!GetDocument().GetContentSecurityPolicy()->AllowMediaFromSource(url)) {
BLINK_MEDIA_LOG << "isSafeToLoadURL(" << (void*)this << ", "
<< UrlForLoggingMedia(url)
<< ") -> rejected by Content Security Policy";
return false;
}
return true;
}
bool HTMLMediaElement::IsMediaDataCORSSameOrigin() const {
if (!GetWebMediaPlayer())
return true;
const auto network_state = GetWebMediaPlayer()->GetNetworkState();
if (network_state == WebMediaPlayer::kNetworkStateNetworkError)
return false;
return !GetWebMediaPlayer()->WouldTaintOrigin();
}
bool HTMLMediaElement::IsInCrossOriginFrame() const {
return IsDocumentCrossOrigin(GetDocument());
}
void HTMLMediaElement::StartProgressEventTimer() {
if (progress_event_timer_.IsActive())
return;
previous_progress_time_ = WTF::CurrentTime();
// 350ms is not magic, it is in the spec!
progress_event_timer_.StartRepeating(TimeDelta::FromMilliseconds(350),
FROM_HERE);
}
void HTMLMediaElement::WaitForSourceChange() {
BLINK_MEDIA_LOG << "waitForSourceChange(" << (void*)this << ")";
StopPeriodicTimers();
load_state_ = kWaitingForSource;
// 6.17 - Waiting: Set the element's networkState attribute to the
// NETWORK_NO_SOURCE value
SetNetworkState(kNetworkNoSource);
// 6.18 - Set the element's delaying-the-load-event flag to false. This stops
// delaying the load event.
SetShouldDelayLoadEvent(false);
UpdateDisplayState();
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
void HTMLMediaElement::NoneSupported(const String& input_message) {
BLINK_MEDIA_LOG << "NoneSupported(" << (void*)this << ", message='"
<< input_message << "')";
StopPeriodicTimers();
load_state_ = kWaitingForSource;
current_source_node_ = nullptr;
String empty_string;
const String& message = MediaShouldBeOpaque() ? empty_string : input_message;
// 4.8.12.5
// The dedicated media source failure steps are the following steps:
// 1 - Set the error attribute to a new MediaError object whose code attribute
// is set to MEDIA_ERR_SRC_NOT_SUPPORTED.
error_ = MediaError::Create(MediaError::kMediaErrSrcNotSupported, message);
// 2 - Forget the media element's media-resource-specific text tracks.
ForgetResourceSpecificTracks();
// 3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE
// value.
SetNetworkState(kNetworkNoSource);
// 4 - Set the element's show poster flag to true.
UpdateDisplayState();
// 5 - Fire a simple event named error at the media element.
ScheduleEvent(event_type_names::kError);
// 6 - Reject pending play promises with NotSupportedError.
ScheduleRejectPlayPromises(DOMExceptionCode::kNotSupportedError);
CloseMediaSource();
// 7 - Set the element's delaying-the-load-event flag to false. This stops
// delaying the load event.
SetShouldDelayLoadEvent(false);
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
void HTMLMediaElement::MediaEngineError(MediaError* err) {
DCHECK_GE(ready_state_, kHaveMetadata);
BLINK_MEDIA_LOG << "mediaEngineError(" << (void*)this << ", "
<< static_cast<int>(err->code()) << ")";
// 1 - The user agent should cancel the fetching process.
StopPeriodicTimers();
load_state_ = kWaitingForSource;
// 2 - Set the error attribute to a new MediaError object whose code attribute
// is set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
error_ = err;
// 3 - Queue a task to fire a simple event named error at the media element.
ScheduleEvent(event_type_names::kError);
// 4 - Set the element's networkState attribute to the NETWORK_IDLE value.
SetNetworkState(kNetworkIdle);
// 5 - Set the element's delaying-the-load-event flag to false. This stops
// delaying the load event.
SetShouldDelayLoadEvent(false);
// 6 - Abort the overall resource selection algorithm.
current_source_node_ = nullptr;
}
void HTMLMediaElement::CancelPendingEventsAndCallbacks() {
BLINK_MEDIA_LOG << "cancelPendingEventsAndCallbacks(" << (void*)this << ")";
async_event_queue_->CancelAllEvents();
for (HTMLSourceElement* source =
Traversal<HTMLSourceElement>::FirstChild(*this);
source; source = Traversal<HTMLSourceElement>::NextSibling(*source))
source->CancelPendingErrorEvent();
}
void HTMLMediaElement::NetworkStateChanged() {
SetNetworkState(GetWebMediaPlayer()->GetNetworkState());
}
void HTMLMediaElement::MediaLoadingFailed(WebMediaPlayer::NetworkState error,
const String& input_message) {
BLINK_MEDIA_LOG << "MediaLoadingFailed(" << (void*)this << ", "
<< static_cast<int>(error) << ", message='" << input_message
<< "')";
bool should_be_opaque = MediaShouldBeOpaque();
if (should_be_opaque)
error = WebMediaPlayer::kNetworkStateNetworkError;
String empty_string;
const String& message = should_be_opaque ? empty_string : input_message;
StopPeriodicTimers();
// If we failed while trying to load a <source> element, the movie was never
// parsed, and there are more <source> children, schedule the next one
if (ready_state_ < kHaveMetadata &&
load_state_ == kLoadingFromSourceElement) {
// resource selection algorithm
// Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM
// manipulation task source, to fire a simple event named error at the
// candidate element.
if (current_source_node_) {
current_source_node_->ScheduleErrorEvent();
} else {
BLINK_MEDIA_LOG << "mediaLoadingFailed(" << (void*)this
<< ") - error event not sent, <source> was removed";
}
// 9.Otherwise.10 - Asynchronously await a stable state. The synchronous
// section consists of all the remaining steps of this algorithm until the
// algorithm says the synchronous section has ended.
// 9.Otherwise.11 - Forget the media element's media-resource-specific
// tracks.
ForgetResourceSpecificTracks();
if (HavePotentialSourceChild()) {
BLINK_MEDIA_LOG << "mediaLoadingFailed(" << (void*)this
<< ") - scheduling next <source>";
ScheduleNextSourceChild();
} else {
BLINK_MEDIA_LOG << "mediaLoadingFailed(" << (void*)this
<< ") - no more <source> elements, waiting";
WaitForSourceChange();
}
return;
}
if (error == WebMediaPlayer::kNetworkStateNetworkError &&
ready_state_ >= kHaveMetadata) {
MediaEngineError(MediaError::Create(MediaError::kMediaErrNetwork, message));
} else if (error == WebMediaPlayer::kNetworkStateDecodeError) {
MediaEngineError(MediaError::Create(MediaError::kMediaErrDecode, message));
} else if ((error == WebMediaPlayer::kNetworkStateFormatError ||
error == WebMediaPlayer::kNetworkStateNetworkError) &&
load_state_ == kLoadingFromSrcAttr) {
if (message.IsEmpty()) {
// Generate a more meaningful error message to differentiate the two types
// of MEDIA_SRC_ERR_NOT_SUPPORTED.
NoneSupported(BuildElementErrorMessage(
error == WebMediaPlayer::kNetworkStateFormatError ? "Format error"
: "Network error"));
} else {
NoneSupported(message);
}
}
UpdateDisplayState();
}
void HTMLMediaElement::SetNetworkState(WebMediaPlayer::NetworkState state) {
BLINK_MEDIA_LOG << "setNetworkState(" << (void*)this << ", "
<< static_cast<int>(state) << ") - current state is "
<< static_cast<int>(network_state_);
if (state == WebMediaPlayer::kNetworkStateEmpty) {
// Just update the cached state and leave, we can't do anything.
SetNetworkState(kNetworkEmpty);
return;
}
if (state == WebMediaPlayer::kNetworkStateFormatError ||
state == WebMediaPlayer::kNetworkStateNetworkError ||
state == WebMediaPlayer::kNetworkStateDecodeError) {
MediaLoadingFailed(state, web_media_player_->GetErrorMessage());
return;
}
if (state == WebMediaPlayer::kNetworkStateIdle) {
if (network_state_ > kNetworkIdle) {
ChangeNetworkStateFromLoadingToIdle();
SetShouldDelayLoadEvent(false);
} else {
SetNetworkState(kNetworkIdle);
}
}
if (state == WebMediaPlayer::kNetworkStateLoading) {
if (network_state_ < kNetworkLoading || network_state_ == kNetworkNoSource)
StartProgressEventTimer();
SetNetworkState(kNetworkLoading);
}
if (state == WebMediaPlayer::kNetworkStateLoaded) {
if (network_state_ != kNetworkIdle)
ChangeNetworkStateFromLoadingToIdle();
}
}
void HTMLMediaElement::ChangeNetworkStateFromLoadingToIdle() {
progress_event_timer_.Stop();
if (!MediaShouldBeOpaque()) {
// Schedule one last progress event so we guarantee that at least one is
// fired for files that load very quickly.
if (GetWebMediaPlayer() && GetWebMediaPlayer()->DidLoadingProgress())
ScheduleEvent(event_type_names::kProgress);
ScheduleEvent(event_type_names::kSuspend);
SetNetworkState(kNetworkIdle);
}
}
void HTMLMediaElement::ReadyStateChanged() {
SetReadyState(static_cast<ReadyState>(GetWebMediaPlayer()->GetReadyState()));
}
void HTMLMediaElement::SetReadyState(ReadyState state) {
BLINK_MEDIA_LOG << "setReadyState(" << (void*)this << ", "
<< static_cast<int>(state) << ") - current state is "
<< static_cast<int>(ready_state_);
// Set "wasPotentiallyPlaying" BEFORE updating ready_state_,
// potentiallyPlaying() uses it
bool was_potentially_playing = PotentiallyPlaying();
ReadyState old_state = ready_state_;
ReadyState new_state = state;
bool tracks_are_ready = TextTracksAreReady();
if (new_state == old_state && tracks_are_ready_ == tracks_are_ready)
return;
tracks_are_ready_ = tracks_are_ready;
if (tracks_are_ready) {
ready_state_ = new_state;
} else {
// If a media file has text tracks the readyState may not progress beyond
// kHaveFutureData until the text tracks are ready, regardless of the state
// of the media file.
if (new_state <= kHaveMetadata)
ready_state_ = new_state;
else
ready_state_ = kHaveCurrentData;
}
if (old_state > ready_state_maximum_)
ready_state_maximum_ = old_state;
if (network_state_ == kNetworkEmpty)
return;
if (seeking_) {
// 4.8.12.9, step 9 note: If the media element was potentially playing
// immediately before it started seeking, but seeking caused its readyState
// attribute to change to a value lower than kHaveFutureData, then a waiting
// will be fired at the element.
if (was_potentially_playing && ready_state_ < kHaveFutureData)
ScheduleEvent(event_type_names::kWaiting);
// 4.8.12.9 steps 12-14
if (ready_state_ >= kHaveCurrentData)
FinishSeek();
} else {
if (was_potentially_playing && ready_state_ < kHaveFutureData) {
// Force an update to official playback position. Automatic updates from
// currentPlaybackPosition() will be blocked while ready_state_ remains
// < kHaveFutureData. This blocking is desired after 'waiting' has been
// fired, but its good to update it one final time to accurately reflect
// media time at the moment we ran out of data to play.
SetOfficialPlaybackPosition(CurrentPlaybackPosition());
// 4.8.12.8
ScheduleTimeupdateEvent(false);
ScheduleEvent(event_type_names::kWaiting);
}
}
// Once enough of the media data has been fetched to determine the duration of
// the media resource, its dimensions, and other metadata...
if (ready_state_ >= kHaveMetadata && old_state < kHaveMetadata) {
CreatePlaceholderTracksIfNecessary();
MediaFragmentURIParser fragment_parser(current_src_);
fragment_end_time_ = fragment_parser.EndTime();
// Set the current playback position and the official playback position to
// the earliest possible position.
SetOfficialPlaybackPosition(EarliestPossiblePosition());
duration_ = web_media_player_->Duration();
ScheduleEvent(event_type_names::kDurationchange);
if (IsHTMLVideoElement())
ScheduleEvent(event_type_names::kResize);
ScheduleEvent(event_type_names::kLoadedmetadata);
bool jumped = false;
if (default_playback_start_position_ > 0) {
Seek(default_playback_start_position_);
jumped = true;
}
default_playback_start_position_ = 0;
double initial_playback_position = fragment_parser.StartTime();
if (std::isnan(initial_playback_position))
initial_playback_position = 0;
if (!jumped && initial_playback_position > 0) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementSeekToFragmentStart);
Seek(initial_playback_position);
jumped = true;
}
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
bool should_update_display_state = false;
bool is_potentially_playing = PotentiallyPlaying();
if (ready_state_ >= kHaveCurrentData && old_state < kHaveCurrentData &&
!have_fired_loaded_data_) {
// Force an update to official playback position to catch non-zero start
// times that were not known at kHaveMetadata, but are known now that the
// first packets have been demuxed.
SetOfficialPlaybackPosition(CurrentPlaybackPosition());
have_fired_loaded_data_ = true;
should_update_display_state = true;
ScheduleEvent(event_type_names::kLoadeddata);
SetShouldDelayLoadEvent(false);
// If the player did a lazy load, it's expecting to be called when the
// element actually becomes visible to complete the load.
if (IsHTMLVideoElement() && web_media_player_->DidLazyLoad() &&
!is_potentially_playing) {
lazy_load_visibility_observer_ = new ElementVisibilityObserver(
this,
WTF::BindRepeating(&HTMLMediaElement::OnVisibilityChangedForLazyLoad,
WrapWeakPersistent(this)));
lazy_load_visibility_observer_->Start();
}
}
if (ready_state_ == kHaveFutureData && old_state <= kHaveCurrentData &&
tracks_are_ready) {
ScheduleEvent(event_type_names::kCanplay);
if (is_potentially_playing)
ScheduleNotifyPlaying();
should_update_display_state = true;
}
if (ready_state_ == kHaveEnoughData && old_state < kHaveEnoughData &&
tracks_are_ready) {
if (old_state <= kHaveCurrentData) {
ScheduleEvent(event_type_names::kCanplay);
if (is_potentially_playing)
ScheduleNotifyPlaying();
}
if (autoplay_policy_->RequestAutoplayByAttribute()) {
paused_ = false;
ScheduleEvent(event_type_names::kPlay);
ScheduleNotifyPlaying();
can_autoplay_ = false;
}
ScheduleEvent(event_type_names::kCanplaythrough);
should_update_display_state = true;
}
if (should_update_display_state)
UpdateDisplayState();
UpdatePlayState();
GetCueTimeline().UpdateActiveCues(currentTime());
}
void HTMLMediaElement::ProgressEventTimerFired(TimerBase*) {
if (network_state_ != kNetworkLoading)
return;
// If this is an cross-origin request, and we haven't discovered whether
// the media is actually playable yet, don't fire any progress events as
// those may let the page know information about the resource that it's
// not supposed to know.
if (MediaShouldBeOpaque())
return;
double time = WTF::CurrentTime();
double timedelta = time - previous_progress_time_;
if (GetWebMediaPlayer() && GetWebMediaPlayer()->DidLoadingProgress()) {
ScheduleEvent(event_type_names::kProgress);
previous_progress_time_ = time;
sent_stalled_event_ = false;
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
} else if (!media_source_ && timedelta > 3.0 && !sent_stalled_event_) {
// Note the !media_source_ condition above. The 'stalled' event is not
// fired when using MSE. MSE's resource is considered 'local' (we don't
// manage the donwload - the app does), so the HTML5 spec text around
// 'stalled' does not apply. See discussion in https://crbug.com/517240
// We also don't need to take any action wrt delaying-the-load-event.
// MediaSource disables the delayed load when first attached.
ScheduleEvent(event_type_names::kStalled);
sent_stalled_event_ = true;
SetShouldDelayLoadEvent(false);
}
}
void HTMLMediaElement::AddPlayedRange(double start, double end) {
BLINK_MEDIA_LOG << "addPlayedRange(" << (void*)this << ", " << start << ", "
<< end << ")";
if (!played_time_ranges_)
played_time_ranges_ = TimeRanges::Create();
played_time_ranges_->Add(start, end);
}
bool HTMLMediaElement::SupportsSave() const {
// Check if download is disabled per settings.
if (GetDocument().GetSettings() &&
GetDocument().GetSettings()->GetHideDownloadUI()) {
return false;
}
// URLs that lead to nowhere are ignored.
if (current_src_.IsNull() || current_src_.IsEmpty())
return false;
// If we have no source, we can't download.
if (network_state_ == kNetworkEmpty || network_state_ == kNetworkNoSource)
return false;
// It is not useful to offer a save feature on local files.
if (current_src_.IsLocalFile())
return false;
// MediaStream can't be downloaded.
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return false;
// MediaSource can't be downloaded.
if (HasMediaSource())
return false;
// HLS stream shouldn't have a download button.
if (IsHLSURL(current_src_))
return false;
// Infinite streams don't have a clear end at which to finish the download.
if (duration() == std::numeric_limits<double>::infinity())
return false;
return true;
}
void HTMLMediaElement::SetIgnorePreloadNone() {
BLINK_MEDIA_LOG << "setIgnorePreloadNone(" << (void*)this << ")";
ignore_preload_none_ = true;
SetPlayerPreload();
}
void HTMLMediaElement::Seek(double time) {
BLINK_MEDIA_LOG << "seek(" << (void*)this << ", " << time << ")";
// 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
// FIXME: remove web_media_player_ check once we figure out how
// web_media_player_ is going out of sync with readystate.
// web_media_player_ is cleared but readystate is not set to HAVE_NOTHING.
if (!web_media_player_ || ready_state_ == kHaveNothing)
return;
// Ignore preload none and start load if necessary.
SetIgnorePreloadNone();
// Get the current time before setting seeking_, last_seek_time_ is returned
// once it is set.
double now = currentTime();
// 3 - If the element's seeking IDL attribute is true, then another instance
// of this algorithm is already running. Abort that other instance of the
// algorithm without waiting for the step that it is running to complete.
// Nothing specific to be done here.
// 4 - Set the seeking IDL attribute to true.
// The flag will be cleared when the engine tells us the time has actually
// changed.
seeking_ = true;
// 6 - If the new playback position is later than the end of the media
// resource, then let it be the end of the media resource instead.
time = std::min(time, duration());
// 7 - If the new playback position is less than the earliest possible
// position, let it be that position instead.
time = std::max(time, EarliestPossiblePosition());
// Ask the media engine for the time value in the movie's time scale before
// comparing with current time. This is necessary because if the seek time is
// not equal to currentTime but the delta is less than the movie's time scale,
// we will ask the media engine to "seek" to the current movie time, which may
// be a noop and not generate a timechanged callback. This means seeking_
// will never be cleared and we will never fire a 'seeked' event.
double media_time = GetWebMediaPlayer()->MediaTimeForTimeValue(time);
if (time != media_time) {
BLINK_MEDIA_LOG << "seek(" << (void*)this << ", " << time
<< ") - media timeline equivalent is " << media_time;
time = media_time;
}
// 8 - If the (possibly now changed) new playback position is not in one of
// the ranges given in the seekable attribute, then let it be the position in
// one of the ranges given in the seekable attribute that is the nearest to
// the new playback position. ... If there are no ranges given in the seekable
// attribute then set the seeking IDL attribute to false and abort these
// steps.
TimeRanges* seekable_ranges = seekable();
if (!seekable_ranges->length()) {
seeking_ = false;
return;
}
time = seekable_ranges->Nearest(time, now);
if (playing_ && last_seek_time_ < now)
AddPlayedRange(last_seek_time_, now);
last_seek_time_ = time;
// 10 - Queue a task to fire a simple event named seeking at the element.
ScheduleEvent(event_type_names::kSeeking);
// 11 - Set the current playback position to the given new playback position.
GetWebMediaPlayer()->Seek(time);
// 14-17 are handled, if necessary, when the engine signals a readystate
// change or otherwise satisfies seek completion and signals a time change.
}
void HTMLMediaElement::FinishSeek() {
BLINK_MEDIA_LOG << "finishSeek(" << (void*)this << ")";
// 14 - Set the seeking IDL attribute to false.
seeking_ = false;
// Force an update to officialPlaybackPosition. Periodic updates generally
// handle this, but may be skipped paused or waiting for data.
SetOfficialPlaybackPosition(CurrentPlaybackPosition());
// 16 - Queue a task to fire a simple event named timeupdate at the element.
ScheduleTimeupdateEvent(false);
// 17 - Queue a task to fire a simple event named seeked at the element.
ScheduleEvent(event_type_names::kSeeked);
SetDisplayMode(kVideo);
}
HTMLMediaElement::ReadyState HTMLMediaElement::getReadyState() const {
return ready_state_;
}
bool HTMLMediaElement::HasVideo() const {
return GetWebMediaPlayer() && GetWebMediaPlayer()->HasVideo();
}
bool HTMLMediaElement::HasAudio() const {
return GetWebMediaPlayer() && GetWebMediaPlayer()->HasAudio();
}
bool HTMLMediaElement::seeking() const {
return seeking_;
}
// https://www.w3.org/TR/html51/semantics-embedded-content.html#earliest-possible-position
// The earliest possible position is not explicitly exposed in the API; it
// corresponds to the start time of the first range in the seekable attribute’s
// TimeRanges object, if any, or the current playback position otherwise.
double HTMLMediaElement::EarliestPossiblePosition() const {
TimeRanges* seekable_ranges = seekable();
if (seekable_ranges && seekable_ranges->length() > 0)
return seekable_ranges->start(0, ASSERT_NO_EXCEPTION);
return CurrentPlaybackPosition();
}
double HTMLMediaElement::CurrentPlaybackPosition() const {
// "Official" playback position won't take updates from "current" playback
// position until ready_state_ > kHaveMetadata, but other callers (e.g.
// pauseInternal) may still request currentPlaybackPosition at any time.
// From spec: "Media elements have a current playback position, which must
// initially (i.e., in the absence of media data) be zero seconds."
if (ready_state_ == kHaveNothing)
return 0;
if (GetWebMediaPlayer())
return GetWebMediaPlayer()->CurrentTime();
if (ready_state_ >= kHaveMetadata) {
BLINK_MEDIA_LOG
<< __func__ << " readyState = " << ready_state_
<< " but no webMediaPlayer to provide currentPlaybackPosition";
}
return 0;
}
double HTMLMediaElement::OfficialPlaybackPosition() const {
// Hold updates to official playback position while paused or waiting for more
// data. The underlying media player may continue to make small advances in
// currentTime (e.g. as samples in the last rendered audio buffer are played
// played out), but advancing currentTime while paused/waiting sends a mixed
// signal about the state of playback.
bool waiting_for_data = ready_state_ <= kHaveCurrentData;
if (official_playback_position_needs_update_ && !paused_ &&
!waiting_for_data) {
SetOfficialPlaybackPosition(CurrentPlaybackPosition());
}
#if LOG_OFFICIAL_TIME_STATUS
static const double kMinCachedDeltaForWarning = 0.01;
double delta =
std::abs(official_playback_position_ - CurrentPlaybackPosition());
if (delta > kMinCachedDeltaForWarning) {
BLINK_MEDIA_LOG << "CurrentTime(" << (void*)this
<< ") - WARNING, cached time is " << delta
<< "seconds off of media time when paused/waiting";
}
#endif
return official_playback_position_;
}
void HTMLMediaElement::SetOfficialPlaybackPosition(double position) const {
#if LOG_OFFICIAL_TIME_STATUS
BLINK_MEDIA_LOG << "SetOfficialPlaybackPosition(" << (void*)this
<< ") was:" << official_playback_position_
<< " now:" << position;
#endif
// Internal player position may advance slightly beyond duration because
// many files use imprecise duration. Clamp official position to duration when
// known. Duration may be unknown when readyState < HAVE_METADATA.
official_playback_position_ =
std::isnan(duration()) ? position : std::min(duration(), position);
if (official_playback_position_ != position) {
BLINK_MEDIA_LOG << "setOfficialPlaybackPosition(" << (void*)this
<< ") position:" << position
<< " truncated to duration:" << official_playback_position_;
}
// Once set, official playback position should hold steady until the next
// stable state. We approximate this by using a microtask to mark the
// need for an update after the current (micro)task has completed. When
// needed, the update is applied in the next call to
// officialPlaybackPosition().
official_playback_position_needs_update_ = false;
Microtask::EnqueueMicrotask(
WTF::Bind(&HTMLMediaElement::RequireOfficialPlaybackPositionUpdate,
WrapWeakPersistent(this)));
}
void HTMLMediaElement::RequireOfficialPlaybackPositionUpdate() const {
official_playback_position_needs_update_ = true;
}
double HTMLMediaElement::currentTime() const {
if (default_playback_start_position_)
return default_playback_start_position_;
if (seeking_) {
BLINK_MEDIA_LOG << "currentTime(" << (void*)this
<< ") - seeking, returning " << last_seek_time_;
return last_seek_time_;
}
return OfficialPlaybackPosition();
}
void HTMLMediaElement::setCurrentTime(double time) {
// If the media element's readyState is kHaveNothing, then set the default
// playback start position to that time.
if (ready_state_ == kHaveNothing) {
default_playback_start_position_ = time;
return;
}
Seek(time);
}
double HTMLMediaElement::duration() const {
return duration_;
}
bool HTMLMediaElement::paused() const {
return paused_;
}
double HTMLMediaElement::defaultPlaybackRate() const {
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return 1.0;
return default_playback_rate_;
}
void HTMLMediaElement::setDefaultPlaybackRate(double rate) {
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return;
if (default_playback_rate_ == rate)
return;
default_playback_rate_ = rate;
ScheduleEvent(event_type_names::kRatechange);
}
double HTMLMediaElement::playbackRate() const {
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return 1.0;
return playback_rate_;
}
void HTMLMediaElement::setPlaybackRate(double rate,
ExceptionState& exception_state) {
BLINK_MEDIA_LOG << "setPlaybackRate(" << (void*)this << ", " << rate << ")";
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return;
if (rate != 0.0 && (rate < kMinRate || rate > kMaxRate)) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementMediaPlaybackRateOutOfRange);
// When the proposed playbackRate is unsupported, throw a NotSupportedError
// DOMException and don't update the value.
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"The provided playback rate (" + String::Number(rate) +
") is not in the " + "supported playback range.");
// Do not update |playback_rate_|.
return;
}
if (playback_rate_ != rate) {
playback_rate_ = rate;
ScheduleEvent(event_type_names::kRatechange);
}
UpdatePlaybackRate();
}
HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::GetDirectionOfPlayback()
const {
return playback_rate_ >= 0 ? kForward : kBackward;
}
void HTMLMediaElement::UpdatePlaybackRate() {
// FIXME: remove web_media_player_ check once we figure out how
// web_media_player_ is going out of sync with readystate.
// web_media_player_ is cleared but readystate is not set to kHaveNothing.
if (web_media_player_ && PotentiallyPlaying())
GetWebMediaPlayer()->SetRate(playbackRate());
}
bool HTMLMediaElement::ended() const {
// 4.8.12.8 Playing the media resource
// The ended attribute must return true if the media element has ended
// playback and the direction of playback is forwards, and false otherwise.
return EndedPlayback() && GetDirectionOfPlayback() == kForward;
}
bool HTMLMediaElement::Autoplay() const {
return FastHasAttribute(kAutoplayAttr);
}
String HTMLMediaElement::preload() const {
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return PreloadTypeToString(WebMediaPlayer::kPreloadNone);
return PreloadTypeToString(PreloadType());
}
void HTMLMediaElement::setPreload(const AtomicString& preload) {
BLINK_MEDIA_LOG << "setPreload(" << (void*)this << ", " << preload << ")";
if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
return;
setAttribute(kPreloadAttr, preload);
}
WebMediaPlayer::Preload HTMLMediaElement::PreloadType() const {
const AtomicString& preload = FastGetAttribute(kPreloadAttr);
if (DeprecatedEqualIgnoringCase(preload, "none")) {
UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadNone);
return WebMediaPlayer::kPreloadNone;
}
// If the source scheme is requires network, force preload to 'none' on Data
// Saver and for low end devices.
if (GetDocument().GetSettings() &&
((GetNetworkStateNotifier().SaveDataEnabled() &&
!GetDocument().GetSettings()->GetDataSaverHoldbackMediaApi()) ||
GetDocument().GetSettings()->GetForcePreloadNoneForMediaElements()) &&
(current_src_.Protocol() != "blob" && current_src_.Protocol() != "data" &&
current_src_.Protocol() != "file")) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementPreloadForcedNone);
return WebMediaPlayer::kPreloadNone;
}
if (DeprecatedEqualIgnoringCase(preload, "metadata")) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementPreloadMetadata);
return WebMediaPlayer::kPreloadMetaData;
}
// Force preload to 'metadata' on cellular connections.
if (GetNetworkStateNotifier().IsCellularConnectionType()) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementPreloadForcedMetadata);
return WebMediaPlayer::kPreloadMetaData;
}
// Per HTML spec, "The empty string ... maps to the Automatic state."
// https://html.spec.whatwg.org/#attr-media-preload
if (DeprecatedEqualIgnoringCase(preload, "auto") ||
DeprecatedEqualIgnoringCase(preload, "")) {
UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadAuto);
return WebMediaPlayer::kPreloadAuto;
}
// "The attribute's missing value default is user-agent defined, though the
// Metadata state is suggested as a compromise between reducing server load
// and providing an optimal user experience."
// The spec does not define an invalid value default:
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=28950
UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadDefault);
return WebMediaPlayer::kPreloadMetaData;
}
String HTMLMediaElement::EffectivePreload() const {
return PreloadTypeToString(EffectivePreloadType());
}
WebMediaPlayer::Preload HTMLMediaElement::EffectivePreloadType() const {
if (Autoplay() && !autoplay_policy_->IsGestureNeededForPlayback())
return WebMediaPlayer::kPreloadAuto;
WebMediaPlayer::Preload preload = PreloadType();
if (ignore_preload_none_ && preload == WebMediaPlayer::kPreloadNone)
return WebMediaPlayer::kPreloadMetaData;
return preload;
}
ScriptPromise HTMLMediaElement::playForBindings(ScriptState* script_state) {
// We have to share the same logic for internal and external callers. The
// internal callers do not want to receive a Promise back but when ::play()
// is called, |play_promise_resolvers_| needs to be populated. What this code
// does is to populate |play_promise_resolvers_| before calling ::play() and
// remove the Promise if ::play() failed.
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
play_promise_resolvers_.push_back(resolver);
base::Optional<DOMExceptionCode> code = Play();
if (code) {
DCHECK(!play_promise_resolvers_.IsEmpty());
play_promise_resolvers_.pop_back();
String message;
switch (code.value()) {
case DOMExceptionCode::kNotAllowedError:
message = autoplay_policy_->GetPlayErrorMessage();
RecordPlayPromiseRejected(
PlayPromiseRejectReason::kFailedAutoplayPolicy);
break;
case DOMExceptionCode::kNotSupportedError:
message = "The element has no supported sources.";
RecordPlayPromiseRejected(PlayPromiseRejectReason::kNoSupportedSources);
break;
default:
NOTREACHED();
}
resolver->Reject(DOMException::Create(code.value(), message));
return promise;
}
return promise;
}
base::Optional<DOMExceptionCode> HTMLMediaElement::Play() {
BLINK_MEDIA_LOG << "play(" << (void*)this << ")";
base::Optional<DOMExceptionCode> exception_code =
autoplay_policy_->RequestPlay();
if (exception_code == DOMExceptionCode::kNotAllowedError) {
// If we're already playing, then this play would do nothing anyway.
// Call playInternal to handle scheduling the promise resolution.
if (!paused_) {
PlayInternal();
return base::nullopt;
}
return exception_code;
}
autoplay_policy_->StopAutoplayMutedWhenVisible();
if (error_ && error_->code() == MediaError::kMediaErrSrcNotSupported)
return DOMExceptionCode::kNotSupportedError;
DCHECK(!exception_code.has_value());
PlayInternal();
return base::nullopt;
}
void HTMLMediaElement::PlayInternal() {
BLINK_MEDIA_LOG << "playInternal(" << (void*)this << ")";
// Playback aborts any lazy loading.
if (lazy_load_visibility_observer_) {
lazy_load_visibility_observer_->Stop();
lazy_load_visibility_observer_ = nullptr;
}
// 4.8.12.8. Playing the media resource
if (network_state_ == kNetworkEmpty)
InvokeResourceSelectionAlgorithm();
// Generally "ended" and "looping" are exclusive. Here, the loop attribute
// is ignored to seek back to start in case loop was set after playback
// ended. See http://crbug.com/364442
if (EndedPlayback(LoopCondition::kIgnored))
Seek(0);
if (paused_) {
paused_ = false;
ScheduleEvent(event_type_names::kPlay);
if (ready_state_ <= kHaveCurrentData)
ScheduleEvent(event_type_names::kWaiting);
else if (ready_state_ >= kHaveFutureData)
ScheduleNotifyPlaying();
} else if (ready_state_ >= kHaveFutureData) {
ScheduleResolvePlayPromises();
}
can_autoplay_ = false;
SetIgnorePreloadNone();
UpdatePlayState();
}
void HTMLMediaElement::pause() {
BLINK_MEDIA_LOG << "pause(" << (void*)this << ")";
autoplay_policy_->StopAutoplayMutedWhenVisible();
PauseInternal();
}
void HTMLMediaElement::PauseInternal() {
BLINK_MEDIA_LOG << "pauseInternal(" << (void*)this << ")";
if (network_state_ == kNetworkEmpty)
InvokeResourceSelectionAlgorithm();
can_autoplay_ = false;
if (!paused_) {
paused_ = true;
ScheduleTimeupdateEvent(false);
ScheduleEvent(event_type_names::kPause);
// Force an update to official playback position. Automatic updates from
// currentPlaybackPosition() will be blocked while paused_ = true. This
// blocking is desired while paused, but its good to update it one final
// time to accurately reflect movie time at the moment we paused.
SetOfficialPlaybackPosition(CurrentPlaybackPosition());
ScheduleRejectPlayPromises(DOMExceptionCode::kAbortError);
}
UpdatePlayState();
}
void HTMLMediaElement::RequestRemotePlayback() {
if (GetWebMediaPlayer())
GetWebMediaPlayer()->RequestRemotePlayback();
}
void HTMLMediaElement::RequestRemotePlaybackControl() {
if (GetWebMediaPlayer())
GetWebMediaPlayer()->RequestRemotePlaybackControl();
}
void HTMLMediaElement::RequestRemotePlaybackStop() {
if (GetWebMediaPlayer())
GetWebMediaPlayer()->RequestRemotePlaybackStop();
}
void HTMLMediaElement::FlingingStarted() {
if (GetWebMediaPlayer())
GetWebMediaPlayer()->FlingingStarted();
}
void HTMLMediaElement::FlingingStopped() {
if (GetWebMediaPlayer())
GetWebMediaPlayer()->FlingingStopped();
}
void HTMLMediaElement::CloseMediaSource() {
if (!media_source_)
return;
media_source_->Close();
media_source_ = nullptr;
}
bool HTMLMediaElement::Loop() const {
return FastHasAttribute(kLoopAttr);
}
void HTMLMediaElement::SetLoop(bool b) {
BLINK_MEDIA_LOG << "setLoop(" << (void*)this << ", " << BoolString(b) << ")";
SetBooleanAttribute(kLoopAttr, b);
}
bool HTMLMediaElement::ShouldShowControls(
const RecordMetricsBehavior record_metrics) const {
Settings* settings = GetDocument().GetSettings();
if (settings && !settings->GetMediaControlsEnabled()) {
if (record_metrics == RecordMetricsBehavior::kDoRecord)
ShowControlsHistogram().Count(kMediaControlsShowDisabledSettings);
return false;
}
if (FastHasAttribute(kControlsAttr)) {
if (record_metrics == RecordMetricsBehavior::kDoRecord)
ShowControlsHistogram().Count(kMediaControlsShowAttribute);
return true;
}
if (IsFullscreen()) {
if (record_metrics == RecordMetricsBehavior::kDoRecord)
ShowControlsHistogram().Count(kMediaControlsShowFullscreen);
return true;
}
LocalFrame* frame = GetDocument().GetFrame();
if (frame && !GetDocument().CanExecuteScripts(kNotAboutToExecuteScript)) {
if (record_metrics == RecordMetricsBehavior::kDoRecord)
ShowControlsHistogram().Count(kMediaControlsShowNoScript);
return true;
}
if (record_metrics == RecordMetricsBehavior::kDoRecord)
ShowControlsHistogram().Count(kMediaControlsShowNotShown);
return false;
}
DOMTokenList* HTMLMediaElement::controlsList() const {
return controls_list_.Get();
}
HTMLMediaElementControlsList* HTMLMediaElement::ControlsListInternal() const {
return controls_list_.Get();
}
double HTMLMediaElement::volume() const {
return volume_;
}
void HTMLMediaElement::setVolume(double vol, ExceptionState& exception_state) {
BLINK_MEDIA_LOG << "setVolume(" << (void*)this << ", " << vol << ")";
if (volume_ == vol)
return;
if (vol < 0.0f || vol > 1.0f) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"volume", vol, 0.0, ExceptionMessages::kInclusiveBound, 1.0,
ExceptionMessages::kInclusiveBound));
return;
}
volume_ = vol;
if (GetWebMediaPlayer())
GetWebMediaPlayer()->SetVolume(EffectiveMediaVolume());
ScheduleEvent(event_type_names::kVolumechange);
}
bool HTMLMediaElement::muted() const {
return muted_;
}
void HTMLMediaElement::setMuted(bool muted) {
BLINK_MEDIA_LOG << "setMuted(" << (void*)this << ", " << BoolString(muted)
<< ")";
if (muted_ == muted)
return;
muted_ = muted;
ScheduleEvent(event_type_names::kVolumechange);
// If it is unmute and AutoplayPolicy doesn't want the playback to continue,
// pause the playback.
if (!muted_ && !autoplay_policy_->RequestAutoplayUnmute())
pause();
// If playback was not paused by the autoplay policy and got unmuted, the
// element is marked as being allowed to play unmuted.
if (!muted_ && PotentiallyPlaying())
was_always_muted_ = false;
// This is called at the end to make sure the WebMediaPlayer has the right
// information.
if (GetWebMediaPlayer())
GetWebMediaPlayer()->SetVolume(EffectiveMediaVolume());
autoplay_policy_->StopAutoplayMutedWhenVisible();
}
double HTMLMediaElement::EffectiveMediaVolume() const {
if (muted_)
return 0;
return volume_;
}
// The spec says to fire periodic timeupdate events (those sent while playing)
// every "15 to 250ms", we choose the slowest frequency
static const TimeDelta kMaxTimeupdateEventFrequency =
TimeDelta::FromMilliseconds(250);
void HTMLMediaElement::StartPlaybackProgressTimer() {
if (playback_progress_timer_.IsActive())
return;
previous_progress_time_ = WTF::CurrentTime();
playback_progress_timer_.StartRepeating(kMaxTimeupdateEventFrequency,
FROM_HERE);
}
void HTMLMediaElement::PlaybackProgressTimerFired(TimerBase*) {
if (!std::isnan(fragment_end_time_) && currentTime() >= fragment_end_time_ &&
GetDirectionOfPlayback() == kForward) {
fragment_end_time_ = std::numeric_limits<double>::quiet_NaN();
if (!paused_) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLMediaElementPauseAtFragmentEnd);
// changes paused to true and fires a simple event named pause at the
// media element.
PauseInternal();
}
}
if (!seeking_)
ScheduleTimeupdateEvent(true);
if (!playbackRate())
return;
GetCueTimeline().UpdateActiveCues(currentTime());
}
void HTMLMediaElement::ScheduleTimeupdateEvent(bool periodic_event) {
// Per spec, consult current playback position to check for changing time.
double media_time = CurrentPlaybackPosition();
bool media_time_has_progressed =
media_time != last_time_update_event_media_time_;
if (periodic_event && !media_time_has_progressed)
return;
ScheduleEvent(event_type_names::kTimeupdate);
last_time_update_event_media_time_ = media_time;
// Ensure periodic event fires 250ms from _this_ event. Restarting the timer
// cancels pending callbacks.
if (!periodic_event && playback_progress_timer_.IsActive()) {
playback_progress_timer_.StartRepeating(kMaxTimeupdateEventFrequency,
FROM_HERE);
}
}
void HTMLMediaElement::TogglePlayState() {
if (paused())
Play();
else
pause();
}
AudioTrackList& HTMLMediaElement::audioTracks() {
return *audio_tracks_;
}
void HTMLMediaElement::AudioTrackChanged(AudioTrack* track) {
BLINK_MEDIA_LOG << "audioTrackChanged(" << (void*)this
<< ") trackId= " << String(track->id())
<< " enabled=" << BoolString(track->enabled());
DCHECK(MediaTracksEnabledInternally());
audioTracks().ScheduleChangeEvent();
if (media_source_)
media_source_->OnTrackChanged(track);
if (!audio_tracks_timer_.IsActive())
audio_tracks_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
void HTMLMediaElement::AudioTracksTimerFired(TimerBase*) {
Vector<WebMediaPlayer::TrackId> enabled_track_ids;
for (unsigned i = 0; i < audioTracks().length(); ++i) {
AudioTrack* track = audioTracks().AnonymousIndexedGetter(i);
if (track->enabled())
enabled_track_ids.push_back(track->id());
}
GetWebMediaPlayer()->EnabledAudioTracksChanged(enabled_track_ids);
}
WebMediaPlayer::TrackId HTMLMediaElement::AddAudioTrack(
const WebString& id,
WebMediaPlayerClient::AudioTrackKind kind,
const WebString& label,
const WebString& language,
bool enabled) {
AtomicString kind_string = AudioKindToString(kind);
BLINK_MEDIA_LOG << "addAudioTrack(" << (void*)this << ", '" << (String)id
<< "', ' " << (AtomicString)kind_string << "', '"
<< (String)label << "', '" << (String)language << "', "
<< BoolString(enabled) << ")";
AudioTrack* audio_track =
AudioTrack::Create(id, kind_string, label, language, enabled);
audioTracks().Add(audio_track);
return audio_track->id();
}
void HTMLMediaElement::RemoveAudioTrack(WebMediaPlayer::TrackId track_id) {
BLINK_MEDIA_LOG << "removeAudioTrack(" << (void*)this << ")";
audioTracks().Remove(track_id);
}
VideoTrackList& HTMLMediaElement::videoTracks() {
return *video_tracks_;
}
void HTMLMediaElement::SelectedVideoTrackChanged(VideoTrack* track) {
BLINK_MEDIA_LOG << "selectedVideoTrackChanged(" << (void*)this
<< ") selectedTrackId="
<< (track->selected() ? String(track->id()) : "none");
DCHECK(MediaTracksEnabledInternally());
if (track->selected())
videoTracks().TrackSelected(track->id());
videoTracks().ScheduleChangeEvent();
if (media_source_)
media_source_->OnTrackChanged(track);
WebMediaPlayer::TrackId id = track->id();
GetWebMediaPlayer()->SelectedVideoTrackChanged(track->selected() ? &id
: nullptr);
}
WebMediaPlayer::TrackId HTMLMediaElement::AddVideoTrack(
const WebString& id,
WebMediaPlayerClient::VideoTrackKind kind,
const WebString& label,
const WebString& language,
bool selected) {
AtomicString kind_string = VideoKindToString(kind);
BLINK_MEDIA_LOG << "addVideoTrack(" << (void*)this << ", '" << (String)id
<< "', '" << (AtomicString)kind_string << "', '"
<< (String)label << "', '" << (String)language << "', "
<< BoolString(selected) << ")";
// If another track was selected (potentially by the user), leave it selected.
if (selected && videoTracks().selectedIndex() != -1)
selected = false;
VideoTrack* video_track =
VideoTrack::Create(id, kind_string, label, language, selected);
videoTracks().Add(video_track);
return video_track->id();
}
void HTMLMediaElement::RemoveVideoTrack(WebMediaPlayer::TrackId track_id) {
BLINK_MEDIA_LOG << "removeVideoTrack(" << (void*)this << ")";
videoTracks().Remove(track_id);
}
void HTMLMediaElement::AddTextTrack(WebInbandTextTrack* web_track) {
// 4.8.12.11.2 Sourcing in-band text tracks
// 1. Associate the relevant data with a new text track and its corresponding
// new TextTrack object.
InbandTextTrack* text_track = InbandTextTrack::Create(web_track);
// 2. Set the new text track's kind, label, and language based on the
// semantics of the relevant data, as defined by the relevant specification.
// If there is no label in that data, then the label must be set to the empty
// string.
// 3. Associate the text track list of cues with the rules for updating the
// text track rendering appropriate for the format in question.
// 4. If the new text track's kind is metadata, then set the text track
// in-band metadata track dispatch type as follows, based on the type of the
// media resource:
// 5. Populate the new text track's list of cues with the cues parsed so far,
// folllowing the guidelines for exposing cues, and begin updating it
// dynamically as necessary.
// - Thess are all done by the media engine.
// 6. Set the new text track's readiness state to loaded.
text_track->SetReadinessState(TextTrack::kLoaded);
// 7. Set the new text track's mode to the mode consistent with the user's
// preferences and the requirements of the relevant specification for the
// data.
// - This will happen in honorUserPreferencesForAutomaticTextTrackSelection()
ScheduleTextTrackResourceLoad();
// 8. Add the new text track to the media element's list of text tracks.
// 9. Fire an event with the name addtrack, that does not bubble and is not
// cancelable, and that uses the TrackEvent interface, with the track
// attribute initialized to the text track's TextTrack object, at the media
// element's textTracks attribute's TextTrackList object.
textTracks()->Append(text_track);
}
void HTMLMediaElement::RemoveTextTrack(WebInbandTextTrack* web_track) {
if (!text_tracks_)
return;
// This cast is safe because InbandTextTrack is the only concrete
// implementation of WebInbandTextTrackClient.
InbandTextTrack* text_track = ToInbandTextTrack(web_track->Client());
if (!text_track)
return;
text_tracks_->Remove(text_track);
}
void HTMLMediaElement::ForgetResourceSpecificTracks() {
// Implements the "forget the media element's media-resource-specific tracks"
// algorithm. The order is explicitly specified as text, then audio, and
// finally video. Also 'removetrack' events should not be fired.
if (text_tracks_) {
TrackDisplayUpdateScope scope(GetCueTimeline());
text_tracks_->RemoveAllInbandTracks();
}
audio_tracks_->RemoveAll();
video_tracks_->RemoveAll();
audio_tracks_timer_.Stop();
}
TextTrack* HTMLMediaElement::addTextTrack(const AtomicString& kind,
const AtomicString& label,
const AtomicString& language,
ExceptionState& exception_state) {
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-addtexttrack
// The addTextTrack(kind, label, language) method of media elements, when
// invoked, must run the following steps:
// 1. Create a new TextTrack object.
// 2. Create a new text track corresponding to the new object, and set its
// text track kind to kind, its text track label to label, its text
// track language to language, ..., and its text track list of cues to
// an empty list.
TextTrack* text_track = TextTrack::Create(kind, label, language);
// ..., its text track readiness state to the text track loaded state, ...
text_track->SetReadinessState(TextTrack::kLoaded);
// 3. Add the new text track to the media element's list of text tracks.
// 4. Queue a task to fire a trusted event with the name addtrack, that
// does not bubble and is not cancelable, and that uses the TrackEvent
// interface, with the track attribute initialised to the new text
// track's TextTrack object, at the media element's textTracks
// attribute's TextTrackList object.
textTracks()->Append(text_track);
// Note: Due to side effects when changing track parameters, we have to
// first append the track to the text track list.
// FIXME: Since setMode() will cause a 'change' event to be queued on the
// same task source as the 'addtrack' event (see above), the order is
// wrong. (The 'change' event shouldn't be fired at all in this case...)
// ..., its text track mode to the text track hidden mode, ...
text_track->setMode(TextTrack::HiddenKeyword());
// 5. Return the new TextTrack object.
return text_track;
}
TextTrackList* HTMLMediaElement::textTracks() {
if (!text_tracks_)
text_tracks_ = TextTrackList::Create(this);
return text_tracks_.Get();
}
void HTMLMediaElement::DidAddTrackElement(HTMLTrackElement* track_element) {
// 4.8.12.11.3 Sourcing out-of-band text tracks
// When a track element's parent element changes and the new parent is a media
// element, then the user agent must add the track element's corresponding
// text track to the media element's list of text tracks ... [continues in
// TextTrackList::append]
TextTrack* text_track = track_element->track();
if (!text_track)
return;
textTracks()->Append(text_track);
// Do not schedule the track loading until parsing finishes so we don't start
// before all tracks in the markup have been added.
if (IsFinishedParsingChildren())
ScheduleTextTrackResourceLoad();
}
void HTMLMediaElement::DidRemoveTrackElement(HTMLTrackElement* track_element) {
KURL url = track_element->GetNonEmptyURLAttribute(kSrcAttr);
BLINK_MEDIA_LOG << "didRemoveTrackElement(" << (void*)this << ") - 'src' is "
<< UrlForLoggingMedia(url);
TextTrack* text_track = track_element->track();
if (!text_track)
return;
text_track->SetHasBeenConfigured(false);
if (!text_tracks_)
return;
// 4.8.12.11.3 Sourcing out-of-band text tracks
// When a track element's parent element changes and the old parent was a
// media element, then the user agent must remove the track element's
// corresponding text track from the media element's list of text tracks.
text_tracks_->Remove(text_track);
wtf_size_t index =
text_tracks_when_resource_selection_began_.Find(text_track);
if (index != kNotFound)
text_tracks_when_resource_selection_began_.EraseAt(index);
}
void HTMLMediaElement::HonorUserPreferencesForAutomaticTextTrackSelection() {
if (!text_tracks_ || !text_tracks_->length())
return;
if (!should_perform_automatic_track_selection_)
return;
AutomaticTrackSelection::Configuration configuration;
if (processing_preference_change_)
configuration.disable_currently_enabled_tracks = true;
if (text_tracks_visible_)
configuration.force_enable_subtitle_or_caption_track = true;
Settings* settings = GetDocument().GetSettings();
if (settings) {
configuration.text_track_kind_user_preference =
settings->GetTextTrackKindUserPreference();
}
AutomaticTrackSelection track_selection(configuration);
track_selection.Perform(*text_tracks_);
}
bool HTMLMediaElement::HavePotentialSourceChild() {
// Stash the current <source> node and next nodes so we can restore them after
// checking to see there is another potential.
HTMLSourceElement* current_source_node = current_source_node_;
Node* next_node = next_child_node_to_consider_;
KURL next_url = SelectNextSourceChild(nullptr, kDoNothing);
current_source_node_ = current_source_node;
next_child_node_to_consider_ = next_node;
return next_url.IsValid();
}
KURL HTMLMediaElement::SelectNextSourceChild(
String* content_type,
InvalidURLAction action_if_invalid) {
// Don't log if this was just called to find out if there are any valid
// <source> elements.
bool should_log = action_if_invalid != kDoNothing;
if (should_log)
BLINK_MEDIA_LOG << "selectNextSourceChild(" << (void*)this << ")";
if (!next_child_node_to_consider_) {
if (should_log) {
BLINK_MEDIA_LOG << "selectNextSourceChild(" << (void*)this
<< ") -> 0x0000, \"\"";
}
return KURL();
}
KURL media_url;
Node* node;
HTMLSourceElement* source = nullptr;
String type;
bool looking_for_start_node = next_child_node_to_consider_;
bool can_use_source_element = false;
NodeVector potential_source_nodes;
GetChildNodes(*this, potential_source_nodes);
for (unsigned i = 0;
!can_use_source_element && i < potential_source_nodes.size(); ++i) {
node = potential_source_nodes[i].Get();
if (looking_for_start_node && next_child_node_to_consider_ != node)
continue;
looking_for_start_node = false;
if (!IsHTMLSourceElement(*node))
continue;
if (node->parentNode() != this)
continue;
source = ToHTMLSourceElement(node);
// 2. If candidate does not have a src attribute, or if its src
// attribute's value is the empty string ... jump down to the failed
// step below
const AtomicString& src_value = source->FastGetAttribute(kSrcAttr);
if (should_log) {
BLINK_MEDIA_LOG << "selectNextSourceChild(" << (void*)this
<< ") - 'src' is " << UrlForLoggingMedia(media_url);
}
if (src_value.IsEmpty())
goto checkAgain;
// 3. Let urlString be the resulting URL string that would have resulted
// from parsing the URL specified by candidate's src attribute's value
// relative to the candidate's node document when the src attribute was
// last changed.
media_url = source->GetDocument().CompleteURL(src_value);
// 4. If urlString was not obtained successfully, then end the
// synchronous section, and jump down to the failed with elements step
// below.
if (!IsSafeToLoadURL(media_url, action_if_invalid))
goto checkAgain;
// 5. If candidate has a type attribute whose value, when parsed as a
// MIME type ...
type = source->type();
if (type.IsEmpty() && media_url.ProtocolIsData())
type = MimeTypeFromDataURL(media_url);
if (!type.IsEmpty()) {
if (should_log) {
BLINK_MEDIA_LOG << "selectNextSourceChild(" << (void*)this
<< ") - 'type' is '" << type << "'";
}
if (!GetSupportsType(ContentType(type)))
goto checkAgain;
}
// Making it this far means the <source> looks reasonable.
can_use_source_element = true;
checkAgain:
if (!can_use_source_element && action_if_invalid == kComplain && source)
source->ScheduleErrorEvent();
}
if (can_use_source_element) {
if (content_type)
*content_type = type;
current_source_node_ = source;
next_child_node_to_consider_ = source->nextSibling();
} else {
current_source_node_ = nullptr;
next_child_node_to_consider_ = nullptr;
}
if (should_log) {
BLINK_MEDIA_LOG << "selectNextSourceChild(" << (void*)this << ") -> "
<< current_source_node_.Get() << ", "
<< (can_use_source_element ? UrlForLoggingMedia(media_url)
: "");
}
return can_use_source_element ? media_url : KURL();
}
void HTMLMediaElement::SourceWasAdded(HTMLSourceElement* source) {
BLINK_MEDIA_LOG << "sourceWasAdded(" << (void*)this << ", " << source << ")";
KURL url = source->GetNonEmptyURLAttribute(kSrcAttr);
BLINK_MEDIA_LOG << "sourceWasAdded(" << (void*)this << ") - 'src' is "
<< UrlForLoggingMedia(url);
// We should only consider a <source> element when there is not src attribute
// at all.
if (FastHasAttribute(kSrcAttr))
return;
// 4.8.8 - If a source element is inserted as a child of a media element that
// has no src attribute and whose networkState has the value NETWORK_EMPTY,
// the user agent must invoke the media element's resource selection
// algorithm.
if (getNetworkState() == HTMLMediaElement::kNetworkEmpty) {
InvokeResourceSelectionAlgorithm();
// Ignore current |next_child_node_to_consider_| and consider |source|.
next_child_node_to_consider_ = source;
return;
}
if (current_source_node_ && source == current_source_node_->nextSibling()) {
BLINK_MEDIA_LOG << "sourceWasAdded(" << (void*)this
<< ") - <source> inserted immediately after current source";
// Ignore current |next_child_node_to_consider_| and consider |source|.
next_child_node_to_consider_ = source;
return;
}
// Consider current |next_child_node_to_consider_| as it is already in the
// middle of processing.
if (next_child_node_to_consider_)
return;
if (load_state_ != kWaitingForSource)
return;
// 4.8.9.5, resource selection algorithm, source elements section:
// 21. Wait until the node after pointer is a node other than the end of the
// list. (This step might wait forever.)
// 22. Asynchronously await a stable state...
// 23. Set the element's delaying-the-load-event flag back to true (this
// delays the load event again, in case it hasn't been fired yet).
SetShouldDelayLoadEvent(true);
// 24. Set the networkState back to NETWORK_LOADING.
SetNetworkState(kNetworkLoading);
// 25. Jump back to the find next candidate step above.
next_child_node_to_consider_ = source;
ScheduleNextSourceChild();
}
void HTMLMediaElement::SourceWasRemoved(HTMLSourceElement* source) {
BLINK_MEDIA_LOG << "sourceWasRemoved(" << (void*)this << ", " << source
<< ")";
KURL url = source->GetNonEmptyURLAttribute(kSrcAttr);
BLINK_MEDIA_LOG << "sourceWasRemoved(" << (void*)this << ") - 'src' is "
<< UrlForLoggingMedia(url);
if (source != current_source_node_ && source != next_child_node_to_consider_)
return;
if (source == next_child_node_to_consider_) {
if (current_source_node_)
next_child_node_to_consider_ = current_source_node_->nextSibling();
BLINK_MEDIA_LOG << "sourceWasRemoved(" << (void*)this
<< ") - next_child_node_to_consider_ set to "
<< next_child_node_to_consider_.Get();
} else if (source == current_source_node_) {
// Clear the current source node pointer, but don't change the movie as the
// spec says:
// 4.8.8 - Dynamically modifying a source element and its attribute when the
// element is already inserted in a video or audio element will have no
// effect.
current_source_node_ = nullptr;
BLINK_MEDIA_LOG << "SourceWasRemoved(" << (void*)this
<< ") - current_source_node_ set to 0";
}
}
void HTMLMediaElement::TimeChanged() {
BLINK_MEDIA_LOG << "timeChanged(" << (void*)this << ")";
GetCueTimeline().UpdateActiveCues(currentTime());
// 4.8.12.9 steps 12-14. Needed if no ReadyState change is associated with the
// seek.
if (seeking_ && ready_state_ >= kHaveCurrentData &&
!GetWebMediaPlayer()->Seeking())
FinishSeek();
double now = CurrentPlaybackPosition();
double dur = duration();
// When the current playback position reaches the end of the media resource
// when the direction of playback is forwards, then the user agent must follow
// these steps:
if (!std::isnan(dur) && dur && now >= dur &&
GetDirectionOfPlayback() == kForward) {
// If the media element has a loop attribute specified
if (Loop()) {
// then seek to the earliest possible position of the media resource and
// abort these steps.
Seek(EarliestPossiblePosition());
} else {
// Queue a task to fire a simple event named timeupdate at the media
// element.
ScheduleTimeupdateEvent(false);
// If the media element has still ended playback, and the direction of
// playback is still forwards, and paused is false,
if (!paused_) {
// changes paused to true and fires a simple event named pause at the
// media element.
paused_ = true;
ScheduleEvent(event_type_names::kPause);
ScheduleRejectPlayPromises(DOMExceptionCode::kAbortError);
}
// Queue a task to fire a simple event named ended at the media element.
ScheduleEvent(event_type_names::kEnded);
}
}
UpdatePlayState();
}
void HTMLMediaElement::DurationChanged() {
BLINK_MEDIA_LOG << "durationChanged(" << (void*)this << ")";
// durationChanged() is triggered by media player.
CHECK(web_media_player_);
double new_duration = web_media_player_->Duration();
// If the duration is changed such that the *current playback position* ends
// up being greater than the time of the end of the media resource, then the
// user agent must also seek to the time of the end of the media resource.
DurationChanged(new_duration, CurrentPlaybackPosition() > new_duration);
}
void HTMLMediaElement::DurationChanged(double duration, bool request_seek) {
BLINK_MEDIA_LOG << "durationChanged(" << (void*)this << ", " << duration
<< ", " << BoolString(request_seek) << ")";
// Abort if duration unchanged.
if (duration_ == duration)
return;
BLINK_MEDIA_LOG << "durationChanged(" << (void*)this << ") : " << duration_
<< " -> " << duration;
duration_ = duration;
ScheduleEvent(event_type_names::kDurationchange);
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
if (request_seek)
Seek(duration);
}
void HTMLMediaElement::RequestSeek(double time) {
// The player is the source of this seek request.
setCurrentTime(time);
}
void HTMLMediaElement::RemoteRouteAvailabilityChanged(
WebRemotePlaybackAvailability availability) {
if (RemotePlaybackClient() &&
!RuntimeEnabledFeatures::NewRemotePlaybackPipelineEnabled()) {
// The new remote playback pipeline is using the Presentation API for
// remote playback device availability monitoring.
RemotePlaybackClient()->AvailabilityChanged(availability);
}
}
bool HTMLMediaElement::HasRemoteRoutes() const {
// TODO(mlamouri): used by MediaControlsPainter; should be refactored out.
return RemotePlaybackClient() &&
RemotePlaybackClient()->RemotePlaybackAvailable();
}
void HTMLMediaElement::ConnectedToRemoteDevice() {
playing_remotely_ = true;
if (RemotePlaybackClient())
RemotePlaybackClient()->StateChanged(WebRemotePlaybackState::kConnecting);
}
void HTMLMediaElement::DisconnectedFromRemoteDevice() {
playing_remotely_ = false;
if (RemotePlaybackClient())
RemotePlaybackClient()->StateChanged(WebRemotePlaybackState::kDisconnected);
}
void HTMLMediaElement::CancelledRemotePlaybackRequest() {
if (RemotePlaybackClient())
RemotePlaybackClient()->PromptCancelled();
}
void HTMLMediaElement::RemotePlaybackStarted() {
if (RemotePlaybackClient())
RemotePlaybackClient()->StateChanged(WebRemotePlaybackState::kConnected);
}
void HTMLMediaElement::RemotePlaybackCompatibilityChanged(const WebURL& url,
bool is_compatible) {
if (RuntimeEnabledFeatures::NewRemotePlaybackPipelineEnabled() &&
RemotePlaybackClient()) {
RemotePlaybackClient()->SourceChanged(url, is_compatible);
}
}
bool HTMLMediaElement::HasSelectedVideoTrack() {
return video_tracks_ && video_tracks_->selectedIndex() != -1;
}
WebMediaPlayer::TrackId HTMLMediaElement::GetSelectedVideoTrackId() {
DCHECK(HasSelectedVideoTrack());
int selected_track_index = video_tracks_->selectedIndex();
VideoTrack* track =
video_tracks_->AnonymousIndexedGetter(selected_track_index);
return track->id();
}
bool HTMLMediaElement::WasAlwaysMuted() {
return was_always_muted_;
}
// MediaPlayerPresentation methods
void HTMLMediaElement::Repaint() {
if (cc_layer_)
cc_layer_->SetNeedsDisplay();
UpdateDisplayState();
if (GetLayoutObject())
GetLayoutObject()->SetShouldDoFullPaintInvalidation();
}
void HTMLMediaElement::SizeChanged() {
BLINK_MEDIA_LOG << "sizeChanged(" << (void*)this << ")";
DCHECK(HasVideo()); // "resize" makes no sense in absence of video.
if (ready_state_ > kHaveNothing && IsHTMLVideoElement())
ScheduleEvent(event_type_names::kResize);
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
TimeRanges* HTMLMediaElement::buffered() const {
if (media_source_)
return media_source_->Buffered();
if (!GetWebMediaPlayer())
return TimeRanges::Create();
return TimeRanges::Create(GetWebMediaPlayer()->Buffered());
}
TimeRanges* HTMLMediaElement::played() {
if (playing_) {
double time = currentTime();
if (time > last_seek_time_)
AddPlayedRange(last_seek_time_, time);
}
if (!played_time_ranges_)
played_time_ranges_ = TimeRanges::Create();
return played_time_ranges_->Copy();
}
TimeRanges* HTMLMediaElement::seekable() const {
if (!GetWebMediaPlayer())
return TimeRanges::Create();
if (media_source_)
return media_source_->Seekable();
return TimeRanges::Create(GetWebMediaPlayer()->Seekable());
}
bool HTMLMediaElement::PotentiallyPlaying() const {
// "pausedToBuffer" means the media engine's rate is 0, but only because it
// had to stop playing when it ran out of buffered data. A movie in this state
// is "potentially playing", modulo the checks in couldPlayIfEnoughData().
bool paused_to_buffer =
ready_state_maximum_ >= kHaveFutureData && ready_state_ < kHaveFutureData;
return (paused_to_buffer || ready_state_ >= kHaveFutureData) &&
CouldPlayIfEnoughData();
}
bool HTMLMediaElement::CouldPlayIfEnoughData() const {
return !paused() && !EndedPlayback() && !StoppedDueToErrors();
}
bool HTMLMediaElement::EndedPlayback(LoopCondition loop_condition) const {
double dur = duration();
if (std::isnan(dur))
return false;
// 4.8.12.8 Playing the media resource
// A media element is said to have ended playback when the element's
// readyState attribute is HAVE_METADATA or greater,
if (ready_state_ < kHaveMetadata)
return false;
// and the current playback position is the end of the media resource and the
// direction of playback is forwards, Either the media element does not have a
// loop attribute specified,
double now = CurrentPlaybackPosition();
// Log whether we get a playback position of infinity().
// TODO(ossu): Once this stat expires, the duration check, below, can be moved
// up to the top of the function.
UMA_HISTOGRAM_BOOLEAN("Media.MediaElement.PlaybackPositionIsInfinity",
now == std::numeric_limits<double>::infinity());
// If we have infinite duration, we'll never have played for long enough to
// have ended playback.
if (dur == std::numeric_limits<double>::infinity())
return false;
if (GetDirectionOfPlayback() == kForward) {
return dur > 0 && now >= dur &&
(loop_condition == LoopCondition::kIgnored || !Loop());
}
// or the current playback position is the earliest possible position and the
// direction of playback is backwards
DCHECK_EQ(GetDirectionOfPlayback(), kBackward);
return now <= EarliestPossiblePosition();
}
bool HTMLMediaElement::StoppedDueToErrors() const {
if (ready_state_ >= kHaveMetadata && error_) {
TimeRanges* seekable_ranges = seekable();
if (!seekable_ranges->Contain(currentTime()))
return true;
}
return false;
}
void HTMLMediaElement::UpdatePlayState() {
bool is_playing = GetWebMediaPlayer() && !GetWebMediaPlayer()->Paused();
bool should_be_playing = PotentiallyPlaying();
BLINK_MEDIA_LOG << "updatePlayState(" << (void*)this
<< ") - shouldBePlaying = " << BoolString(should_be_playing)
<< ", isPlaying = " << BoolString(is_playing);
if (should_be_playing && !muted_)
was_always_muted_ = false;
if (should_be_playing) {
SetDisplayMode(kVideo);
if (!is_playing) {
// Set rate, muted before calling play in case they were set before the
// media engine was setup. The media engine should just stash the rate
// and muted values since it isn't already playing.
GetWebMediaPlayer()->SetRate(playbackRate());
GetWebMediaPlayer()->SetVolume(EffectiveMediaVolume());
GetWebMediaPlayer()->Play();
}
StartPlaybackProgressTimer();
playing_ = true;
} else { // Should not be playing right now
if (is_playing) {
GetWebMediaPlayer()->Pause();
}
playback_progress_timer_.Stop();
playing_ = false;
double time = currentTime();
if (time > last_seek_time_)
AddPlayedRange(last_seek_time_, time);
}
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
}
void HTMLMediaElement::StopPeriodicTimers() {
progress_event_timer_.Stop();
playback_progress_timer_.Stop();
check_viewport_intersection_timer_.Stop();
if (lazy_load_visibility_observer_) {
lazy_load_visibility_observer_->Stop();
lazy_load_visibility_observer_ = nullptr;
}
}
void HTMLMediaElement::
ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking() {
GetAudioSourceProvider().SetClient(nullptr);
if (web_media_player_) {
audio_source_provider_.Wrap(nullptr);
web_media_player_.reset();
}
}
void HTMLMediaElement::ClearMediaPlayer() {
ForgetResourceSpecificTracks();
CloseMediaSource();
CancelDeferredLoad();
{
AudioSourceProviderClientLockScope scope(*this);
ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
}
StopPeriodicTimers();
load_timer_.Stop();
pending_action_flags_ = 0;
load_state_ = kWaitingForSource;
// We can't cast if we don't have a media player.
playing_remotely_ = false;
RemoteRouteAvailabilityChanged(WebRemotePlaybackAvailability::kUnknown);
if (GetLayoutObject())
GetLayoutObject()->SetShouldDoFullPaintInvalidation();
}
void HTMLMediaElement::ContextDestroyed(ExecutionContext*) {
BLINK_MEDIA_LOG << "contextDestroyed(" << (void*)this << ")";
// Close the async event queue so that no events are enqueued.
CancelPendingEventsAndCallbacks();
// Clear everything in the Media Element
ClearMediaPlayer();
ready_state_ = kHaveNothing;
ready_state_maximum_ = kHaveNothing;
SetNetworkState(kNetworkEmpty);
SetShouldDelayLoadEvent(false);
current_source_node_ = nullptr;
official_playback_position_ = 0;
official_playback_position_needs_update_ = true;
GetCueTimeline().UpdateActiveCues(0);
playing_ = false;
paused_ = true;
seeking_ = false;
if (GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
StopPeriodicTimers();
removed_from_document_timer_.Stop();
}
bool HTMLMediaElement::HasPendingActivity() const {
// The delaying-the-load-event flag is set by resource selection algorithm
// when looking for a resource to load, before networkState has reached to
// kNetworkLoading.
if (should_delay_load_event_)
return true;
// When networkState is kNetworkLoading, progress and stalled events may be
// fired.
if (network_state_ == kNetworkLoading)
return true;
{
// Disable potential updating of playback position, as that will
// require v8 allocations; not allowed while GCing
// (hasPendingActivity() is called during a v8 GC.)
base::AutoReset<bool> scope(&official_playback_position_needs_update_,
false);
// When playing or if playback may continue, timeupdate events may be fired.
if (CouldPlayIfEnoughData())
return true;
}
// When the seek finishes timeupdate and seeked events will be fired.
if (seeking_)
return true;
// When connected to a MediaSource, e.g. setting MediaSource.duration will
// cause a durationchange event to be fired.
if (media_source_)
return true;
// Wait for any pending events to be fired.
if (async_event_queue_->HasPendingEvents())
return true;
return false;
}
bool HTMLMediaElement::IsFullscreen() const {
return Fullscreen::IsFullscreenElement(*this);
}
cc::Layer* HTMLMediaElement::CcLayer() const {
return cc_layer_;
}
bool HTMLMediaElement::HasClosedCaptions() const {
if (!text_tracks_)
return false;
for (unsigned i = 0; i < text_tracks_->length(); ++i) {
if (text_tracks_->AnonymousIndexedGetter(i)->CanBeRendered())
return true;
}
return false;
}
bool HTMLMediaElement::TextTracksVisible() const {
return text_tracks_visible_;
}
// static
void HTMLMediaElement::AssertShadowRootChildren(ShadowRoot& shadow_root) {
#if DCHECK_IS_ON()
// There can be up to three children: an interstitial (media remoting or
// picture in picture), text track container, and media controls. The media
// controls has to be the last child if present, and has to be the next
// sibling of the text track container if both present. When present, media
// remoting interstitial has to be the first child.
unsigned number_of_children = shadow_root.CountChildren();
DCHECK_LE(number_of_children, 3u);
Node* first_child = shadow_root.firstChild();
Node* last_child = shadow_root.lastChild();
if (number_of_children == 1) {
DCHECK(first_child->IsTextTrackContainer() ||
first_child->IsMediaControls() ||
first_child->IsMediaRemotingInterstitial() ||
first_child->IsPictureInPictureInterstitial());
} else if (number_of_children == 2) {
DCHECK(first_child->IsTextTrackContainer() ||
first_child->IsMediaRemotingInterstitial() ||
first_child->IsPictureInPictureInterstitial());
DCHECK(last_child->IsTextTrackContainer() || last_child->IsMediaControls());
if (first_child->IsTextTrackContainer())
DCHECK(last_child->IsMediaControls());
} else if (number_of_children == 3) {
Node* second_child = first_child->nextSibling();
DCHECK(first_child->IsMediaRemotingInterstitial() ||
first_child->IsPictureInPictureInterstitial());
DCHECK(second_child->IsTextTrackContainer());
DCHECK(last_child->IsMediaControls());
}
#endif
}
TextTrackContainer& HTMLMediaElement::EnsureTextTrackContainer() {
ShadowRoot& shadow_root = EnsureUserAgentShadowRoot();
AssertShadowRootChildren(shadow_root);
Node* first_child = shadow_root.firstChild();
if (first_child && first_child->IsTextTrackContainer())
return ToTextTrackContainer(*first_child);
Node* to_be_inserted = first_child;
if (first_child && (first_child->IsMediaRemotingInterstitial() ||
first_child->IsPictureInPictureInterstitial())) {
Node* second_child = first_child->nextSibling();
if (second_child && second_child->IsTextTrackContainer())
return ToTextTrackContainer(*second_child);
to_be_inserted = second_child;
}
TextTrackContainer* text_track_container = TextTrackContainer::Create(*this);
// The text track container should be inserted before the media controls,
// so that they are rendered behind them.
shadow_root.InsertBefore(text_track_container, to_be_inserted);
AssertShadowRootChildren(shadow_root);
return *text_track_container;
}
void HTMLMediaElement::UpdateTextTrackDisplay() {
BLINK_MEDIA_LOG << "updateTextTrackDisplay(" << (void*)this << ")";
EnsureTextTrackContainer().UpdateDisplay(
*this, TextTrackContainer::kDidNotStartExposingControls);
}
void HTMLMediaElement::MediaControlsDidBecomeVisible() {
BLINK_MEDIA_LOG << "mediaControlsDidBecomeVisible(" << (void*)this << ")";
// When the user agent starts exposing a user interface for a video element,
// the user agent should run the rules for updating the text track rendering
// of each of the text tracks in the video element's list of text tracks ...
if (IsHTMLVideoElement() && TextTracksVisible()) {
EnsureTextTrackContainer().UpdateDisplay(
*this, TextTrackContainer::kDidStartExposingControls);
}
}
void HTMLMediaElement::SetTextTrackKindUserPreferenceForAllMediaElements(
Document* document) {
auto it = DocumentToElementSetMap().find(document);
if (it == DocumentToElementSetMap().end())
return;
DCHECK(it->value);
WeakMediaElementSet& elements = *it->value;
for (const auto& element : elements)
element->AutomaticTrackSelectionForUpdatedUserPreference();
}
void HTMLMediaElement::AutomaticTrackSelectionForUpdatedUserPreference() {
if (!text_tracks_ || !text_tracks_->length())
return;
MarkCaptionAndSubtitleTracksAsUnconfigured();
processing_preference_change_ = true;
text_tracks_visible_ = false;
HonorUserPreferencesForAutomaticTextTrackSelection();
processing_preference_change_ = false;
// If a track is set to 'showing' post performing automatic track selection,
// set text tracks state to visible to update the CC button and display the
// track.
text_tracks_visible_ = text_tracks_->HasShowingTracks();
UpdateTextTrackDisplay();
}
void HTMLMediaElement::MarkCaptionAndSubtitleTracksAsUnconfigured() {
if (!text_tracks_)
return;
// Mark all tracks as not "configured" so that
// honorUserPreferencesForAutomaticTextTrackSelection() will reconsider
// which tracks to display in light of new user preferences (e.g. default
// tracks should not be displayed if the user has turned off captions and
// non-default tracks should be displayed based on language preferences if
// the user has turned captions on).
for (unsigned i = 0; i < text_tracks_->length(); ++i) {
TextTrack* text_track = text_tracks_->AnonymousIndexedGetter(i);
if (text_track->IsVisualKind())
text_track->SetHasBeenConfigured(false);
}
}
uint64_t HTMLMediaElement::webkitAudioDecodedByteCount() const {
if (!GetWebMediaPlayer())
return 0;
return GetWebMediaPlayer()->AudioDecodedByteCount();
}
uint64_t HTMLMediaElement::webkitVideoDecodedByteCount() const {
if (!GetWebMediaPlayer())
return 0;
return GetWebMediaPlayer()->VideoDecodedByteCount();
}
bool HTMLMediaElement::IsURLAttribute(const Attribute& attribute) const {
return attribute.GetName() == kSrcAttr ||
HTMLElement::IsURLAttribute(attribute);
}
void HTMLMediaElement::SetShouldDelayLoadEvent(bool should_delay) {
if (should_delay_load_event_ == should_delay)
return;
BLINK_MEDIA_LOG << "setShouldDelayLoadEvent(" << (void*)this << ", "
<< BoolString(should_delay) << ")";
should_delay_load_event_ = should_delay;
if (should_delay)
GetDocument().IncrementLoadEventDelayCount();
else
GetDocument().DecrementLoadEventDelayCount();
}
MediaControls* HTMLMediaElement::GetMediaControls() const {
return media_controls_;
}
void HTMLMediaElement::EnsureMediaControls() {
if (GetMediaControls())
return;
ShadowRoot& shadow_root = EnsureUserAgentShadowRoot();
media_controls_ =
CoreInitializer::GetInstance().CreateMediaControls(*this, shadow_root);
// The media controls should be inserted after the text track container,
// so that they are rendered in front of captions and subtitles. This check
// is verifying the contract.
AssertShadowRootChildren(shadow_root);
}
void HTMLMediaElement::UpdateControlsVisibility() {
if (!isConnected())
return;
bool native_controls = ShouldShowControls(RecordMetricsBehavior::kDoRecord);
// When LazyInitializeMediaControls is enabled, initialize the controls only
// if native controls should be used or if using the cast overlay.
if (!RuntimeEnabledFeatures::LazyInitializeMediaControlsEnabled() ||
RuntimeEnabledFeatures::MediaCastOverlayButtonEnabled() ||
native_controls) {
EnsureMediaControls();
// TODO(mlamouri): this doesn't sound needed but the following tests, on
// Android fails when removed:
// fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html
GetMediaControls()->Reset();
}
if (native_controls)
GetMediaControls()->MaybeShow();
else if (GetMediaControls())
GetMediaControls()->Hide();
if (web_media_player_)
web_media_player_->OnHasNativeControlsChanged(native_controls);
}
CueTimeline& HTMLMediaElement::GetCueTimeline() {
if (!cue_timeline_)
cue_timeline_ = new CueTimeline(*this);
return *cue_timeline_;
}
void HTMLMediaElement::ConfigureTextTrackDisplay() {
DCHECK(text_tracks_);
BLINK_MEDIA_LOG << "configureTextTrackDisplay(" << (void*)this << ")";
if (processing_preference_change_)
return;
bool have_visible_text_track = text_tracks_->HasShowingTracks();
text_tracks_visible_ = have_visible_text_track;
if (!have_visible_text_track && !GetMediaControls())
return;
GetCueTimeline().UpdateActiveCues(currentTime());
// Note: The "time marches on" algorithm (updateActiveCues) runs the "rules
// for updating the text track rendering" (updateTextTrackDisplay) only for
// "affected tracks", i.e. tracks where the the active cues have changed.
// This misses cues in tracks that changed mode between hidden and showing.
// This appears to be a spec bug, which we work around here:
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=28236
UpdateTextTrackDisplay();
}
// TODO(srirama.m): Merge it to resetMediaElement if possible and remove it.
void HTMLMediaElement::ResetMediaPlayerAndMediaSource() {
CloseMediaSource();
{
AudioSourceProviderClientLockScope scope(*this);
ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
}
// We haven't yet found out if any remote routes are available.
playing_remotely_ = false;
RemoteRouteAvailabilityChanged(WebRemotePlaybackAvailability::kUnknown);
if (audio_source_node_)
GetAudioSourceProvider().SetClient(audio_source_node_);
}
void HTMLMediaElement::SetAudioSourceNode(
AudioSourceProviderClient* source_node) {
DCHECK(IsMainThread());
audio_source_node_ = source_node;
// No need to lock the |audio_source_node| because it locks itself when
// setFormat() is invoked.
GetAudioSourceProvider().SetClient(audio_source_node_);
}
WebMediaPlayer::CORSMode HTMLMediaElement::CorsMode() const {
const AtomicString& cross_origin_mode = FastGetAttribute(kCrossoriginAttr);
if (cross_origin_mode.IsNull())
return WebMediaPlayer::kCORSModeUnspecified;
if (DeprecatedEqualIgnoringCase(cross_origin_mode, "use-credentials"))
return WebMediaPlayer::kCORSModeUseCredentials;
return WebMediaPlayer::kCORSModeAnonymous;
}
void HTMLMediaElement::SetCcLayer(cc::Layer* cc_layer) {
if (cc_layer == cc_layer_)
return;
// If either of the layers is null we need to enable or disable compositing.
// This is done by triggering a style recalc.
if (!cc_layer_ || !cc_layer)
SetNeedsCompositingUpdate();
if (cc_layer_)
GraphicsLayer::UnregisterContentsLayer(cc_layer_);
cc_layer_ = cc_layer;
if (cc_layer_)
GraphicsLayer::RegisterContentsLayer(cc_layer_);
}
void HTMLMediaElement::MediaSourceOpened(WebMediaSource* web_media_source) {
SetShouldDelayLoadEvent(false);
media_source_->SetWebMediaSourceAndOpen(base::WrapUnique(web_media_source));
}
bool HTMLMediaElement::IsInteractiveContent() const {
return FastHasAttribute(kControlsAttr);
}
void HTMLMediaElement::Trace(blink::Visitor* visitor) {
visitor->Trace(played_time_ranges_);
visitor->Trace(async_event_queue_);
visitor->Trace(error_);
visitor->Trace(current_source_node_);
visitor->Trace(next_child_node_to_consider_);
visitor->Trace(media_source_);
visitor->Trace(audio_tracks_);
visitor->Trace(video_tracks_);
visitor->Trace(cue_timeline_);
visitor->Trace(text_tracks_);
visitor->Trace(text_tracks_when_resource_selection_began_);
visitor->Trace(play_promise_resolvers_);
visitor->Trace(play_promise_resolve_list_);
visitor->Trace(play_promise_reject_list_);
visitor->Trace(audio_source_provider_);
visitor->Trace(src_object_);
visitor->Trace(autoplay_policy_);
visitor->Trace(media_controls_);
visitor->Trace(controls_list_);
visitor->Trace(lazy_load_visibility_observer_);
visitor->template RegisterWeakMembers<HTMLMediaElement,
&HTMLMediaElement::ClearWeakMembers>(
this);
Supplementable<HTMLMediaElement>::Trace(visitor);
HTMLElement::Trace(visitor);
PausableObject::Trace(visitor);
}
void HTMLMediaElement::CreatePlaceholderTracksIfNecessary() {
if (!MediaTracksEnabledInternally())
return;
// Create a placeholder audio track if the player says it has audio but it
// didn't explicitly announce the tracks.
if (HasAudio() && !audioTracks().length()) {
AddAudioTrack("audio", WebMediaPlayerClient::kAudioTrackKindMain,
"Audio Track", "", true);
}
// Create a placeholder video track if the player says it has video but it
// didn't explicitly announce the tracks.
if (HasVideo() && !videoTracks().length()) {
AddVideoTrack("video", WebMediaPlayerClient::kVideoTrackKindMain,
"Video Track", "", true);
}
}
void HTMLMediaElement::SetNetworkState(NetworkState state) {
if (network_state_ == state)
return;
network_state_ = state;
if (GetMediaControls())
GetMediaControls()->NetworkStateChanged();
}
void HTMLMediaElement::VideoWillBeDrawnToCanvas() const {
DCHECK(IsHTMLVideoElement());
UseCounter::Count(GetDocument(), WebFeature::kVideoInCanvas);
autoplay_policy_->VideoWillBeDrawnToCanvas();
}
void HTMLMediaElement::ScheduleResolvePlayPromises() {
// TODO(mlamouri): per spec, we should create a new task but we can't create
// a new cancellable task without cancelling the previous one. There are two
// approaches then: cancel the previous task and create a new one with the
// appended promise list or append the new promise to the current list. The
// latter approach is preferred because it might be the less observable
// change.
DCHECK(play_promise_resolve_list_.IsEmpty() ||
play_promise_resolve_task_handle_.IsActive());
if (play_promise_resolvers_.IsEmpty())
return;
play_promise_resolve_list_.AppendVector(play_promise_resolvers_);
play_promise_resolvers_.clear();
if (play_promise_resolve_task_handle_.IsActive())
return;
play_promise_resolve_task_handle_ = PostCancellableTask(
*GetDocument().GetTaskRunner(TaskType::kMediaElementEvent), FROM_HERE,
WTF::Bind(&HTMLMediaElement::ResolveScheduledPlayPromises,
WrapWeakPersistent(this)));
}
void HTMLMediaElement::ScheduleRejectPlayPromises(DOMExceptionCode code) {
// TODO(mlamouri): per spec, we should create a new task but we can't create
// a new cancellable task without cancelling the previous one. There are two
// approaches then: cancel the previous task and create a new one with the
// appended promise list or append the new promise to the current list. The
// latter approach is preferred because it might be the less observable
// change.
DCHECK(play_promise_reject_list_.IsEmpty() ||
play_promise_reject_task_handle_.IsActive());
if (play_promise_resolvers_.IsEmpty())
return;
play_promise_reject_list_.AppendVector(play_promise_resolvers_);
play_promise_resolvers_.clear();
if (play_promise_reject_task_handle_.IsActive())
return;
// TODO(nhiroki): Bind this error code to a cancellable task instead of a
// member field.
play_promise_error_code_ = code;
play_promise_reject_task_handle_ = PostCancellableTask(
*GetDocument().GetTaskRunner(TaskType::kMediaElementEvent), FROM_HERE,
WTF::Bind(&HTMLMediaElement::RejectScheduledPlayPromises,
WrapWeakPersistent(this)));
}
void HTMLMediaElement::ScheduleNotifyPlaying() {
ScheduleEvent(event_type_names::kPlaying);
ScheduleResolvePlayPromises();
}
void HTMLMediaElement::ResolveScheduledPlayPromises() {
for (auto& resolver : play_promise_resolve_list_)
resolver->Resolve();
play_promise_resolve_list_.clear();
}
void HTMLMediaElement::RejectScheduledPlayPromises() {
// TODO(mlamouri): the message is generated based on the code because
// arguments can't be passed to a cancellable task. In order to save space
// used by the object, the string isn't saved.
DCHECK(play_promise_error_code_ == DOMExceptionCode::kAbortError ||
play_promise_error_code_ == DOMExceptionCode::kNotSupportedError);
if (play_promise_error_code_ == DOMExceptionCode::kAbortError) {
RecordPlayPromiseRejected(PlayPromiseRejectReason::kInterruptedByPause);
RejectPlayPromisesInternal(DOMExceptionCode::kAbortError,
"The play() request was interrupted by a call "
"to pause(). https://goo.gl/LdLk22");
} else {
RecordPlayPromiseRejected(PlayPromiseRejectReason::kNoSupportedSources);
RejectPlayPromisesInternal(
DOMExceptionCode::kNotSupportedError,
"Failed to load because no supported source was found.");
}
}
void HTMLMediaElement::RejectPlayPromises(DOMExceptionCode code,
const String& message) {
play_promise_reject_list_.AppendVector(play_promise_resolvers_);
play_promise_resolvers_.clear();
RejectPlayPromisesInternal(code, message);
}
void HTMLMediaElement::RejectPlayPromisesInternal(DOMExceptionCode code,
const String& message) {
DCHECK(code == DOMExceptionCode::kAbortError ||
code == DOMExceptionCode::kNotSupportedError);
for (auto& resolver : play_promise_reject_list_)
resolver->Reject(DOMException::Create(code, message));
play_promise_reject_list_.clear();
}
EnumerationHistogram& HTMLMediaElement::ShowControlsHistogram() const {
if (IsHTMLVideoElement()) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, histogram,
("Media.Controls.Show.Video", kMediaControlsShowMax));
return histogram;
}
DEFINE_STATIC_LOCAL(EnumerationHistogram, histogram,
("Media.Controls.Show.Audio", kMediaControlsShowMax));
return histogram;
}
void HTMLMediaElement::OnRemovedFromDocumentTimerFired(TimerBase*) {
if (InActiveDocument())
return;
PauseInternal();
}
void HTMLMediaElement::ClearWeakMembers(Visitor* visitor) {
if (!ThreadHeap::IsHeapObjectAlive(audio_source_node_)) {
GetAudioSourceProvider().SetClient(nullptr);
audio_source_node_ = nullptr;
}
}
void HTMLMediaElement::AudioSourceProviderImpl::Wrap(
WebAudioSourceProvider* provider) {
MutexLocker locker(provide_input_lock);
if (web_audio_source_provider_ && provider != web_audio_source_provider_)
web_audio_source_provider_->SetClient(nullptr);
web_audio_source_provider_ = provider;
if (web_audio_source_provider_)
web_audio_source_provider_->SetClient(client_.Get());
}
void HTMLMediaElement::AudioSourceProviderImpl::SetClient(
AudioSourceProviderClient* client) {
MutexLocker locker(provide_input_lock);
if (client)
client_ = new HTMLMediaElement::AudioClientImpl(client);
else
client_.Clear();
if (web_audio_source_provider_)
web_audio_source_provider_->SetClient(client_.Get());
}
void HTMLMediaElement::AudioSourceProviderImpl::ProvideInput(
AudioBus* bus,
size_t frames_to_process) {
DCHECK(bus);
MutexTryLocker try_locker(provide_input_lock);
if (!try_locker.Locked() || !web_audio_source_provider_ || !client_.Get()) {
bus->Zero();
return;
}
// Wrap the AudioBus channel data using WebVector.
unsigned n = bus->NumberOfChannels();
WebVector<float*> web_audio_data(n);
for (unsigned i = 0; i < n; ++i)
web_audio_data[i] = bus->Channel(i)->MutableData();
web_audio_source_provider_->ProvideInput(web_audio_data, frames_to_process);
}
void HTMLMediaElement::AudioClientImpl::SetFormat(size_t number_of_channels,
float sample_rate) {
if (client_)
client_->SetFormat(number_of_channels, sample_rate);
}
void HTMLMediaElement::AudioClientImpl::Trace(blink::Visitor* visitor) {
visitor->Trace(client_);
}
void HTMLMediaElement::AudioSourceProviderImpl::Trace(blink::Visitor* visitor) {
visitor->Trace(client_);
}
void HTMLMediaElement::ActivateViewportIntersectionMonitoring(bool activate) {
if (activate && !check_viewport_intersection_timer_.IsActive()) {
check_viewport_intersection_timer_.StartRepeating(
kCheckViewportIntersectionInterval, FROM_HERE);
} else if (!activate) {
check_viewport_intersection_timer_.Stop();
}
}
bool HTMLMediaElement::HasNativeControls() {
return ShouldShowControls(RecordMetricsBehavior::kDoRecord);
}
bool HTMLMediaElement::IsAudioElement() {
return IsHTMLAudioElement();
}
WebMediaPlayer::DisplayType HTMLMediaElement::DisplayType() const {
return IsFullscreen() ? WebMediaPlayer::DisplayType::kFullscreen
: WebMediaPlayer::DisplayType::kInline;
}
gfx::ColorSpace HTMLMediaElement::TargetColorSpace() {
const LocalFrame* frame = GetDocument().GetFrame();
if (!frame)
return gfx::ColorSpace();
return frame->GetPage()->GetChromeClient().GetScreenInfo().color_space;
}
bool HTMLMediaElement::WasAutoplayInitiated() {
return autoplay_policy_->WasAutoplayInitiated();
}
void HTMLMediaElement::RequestPlay() {
autoplay_policy_->EnsureAutoplayInitiatedSet();
PlayInternal();
}
void HTMLMediaElement::RequestPause() {
PauseInternal();
}
bool HTMLMediaElement::MediaShouldBeOpaque() const {
return !IsMediaDataCORSSameOrigin() && ready_state_ < kHaveMetadata &&
!FastGetAttribute(kSrcAttr).IsEmpty() &&
EffectivePreloadType() != WebMediaPlayer::kPreloadNone;
}
void HTMLMediaElement::CheckViewportIntersectionTimerFired(TimerBase*) {
bool should_report_root_bounds = true;
IntersectionGeometry geometry(nullptr, *this, Vector<Length>(),
should_report_root_bounds);
geometry.ComputeGeometry();
IntRect intersect_rect = geometry.IntersectionIntRect();
if (current_intersect_rect_ == intersect_rect)
return;
current_intersect_rect_ = intersect_rect;
bool is_mostly_filling_viewport =
(current_intersect_rect_.Size().Area() >
kMostlyFillViewportThreshold * geometry.RootIntRect().Size().Area());
if (mostly_filling_viewport_ == is_mostly_filling_viewport)
return;
mostly_filling_viewport_ = is_mostly_filling_viewport;
if (web_media_player_)
web_media_player_->BecameDominantVisibleContent(mostly_filling_viewport_);
}
void HTMLMediaElement::OnVisibilityChangedForLazyLoad(bool is_visible) {
if (!is_visible || !web_media_player_)
return;
web_media_player_->OnBecameVisible();
lazy_load_visibility_observer_->Stop();
lazy_load_visibility_observer_ = nullptr;
}
STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveNothing,
HTMLMediaElement::kHaveNothing);
STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveMetadata,
HTMLMediaElement::kHaveMetadata);
STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveCurrentData,
HTMLMediaElement::kHaveCurrentData);
STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveFutureData,
HTMLMediaElement::kHaveFutureData);
STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveEnoughData,
HTMLMediaElement::kHaveEnoughData);
} // namespace blink