2 * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
32 #include "base/auto_reset.h"
33 #include "base/debug/crash_logging.h"
34 #include "base/feature_list.h"
35 #include "base/memory/ptr_util.h"
36 #include "base/metrics/histogram_functions.h"
37 #include "base/metrics/histogram_macros.h"
38 #include "base/synchronization/lock.h"
39 #include "base/time/time.h"
40 #include "cc/layers/layer.h"
41 #include "media/base/media_content_type.h"
42 #include "media/base/media_switches.h"
43 #include "services/media_session/public/mojom/media_session.mojom-blink.h"
44 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
45 #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
46 #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
47 #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
48 #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-shared.h"
49 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream.h"
50 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
51 #include "third_party/blink/public/platform/task_type.h"
52 #include "third_party/blink/public/platform/web_media_player.h"
53 #include "third_party/blink/public/platform/web_media_player_source.h"
54 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
55 #include "third_party/blink/renderer/core/core_initializer.h"
56 #include "third_party/blink/renderer/core/core_probes_inl.h"
57 #include "third_party/blink/renderer/core/css/style_change_reason.h"
58 #include "third_party/blink/renderer/core/css/style_engine.h"
59 #include "third_party/blink/renderer/core/dom/attribute.h"
60 #include "third_party/blink/renderer/core/dom/dom_exception.h"
61 #include "third_party/blink/renderer/core/dom/element_traversal.h"
62 #include "third_party/blink/renderer/core/dom/events/event.h"
63 #include "third_party/blink/renderer/core/dom/events/event_queue.h"
64 #include "third_party/blink/renderer/core/dom/shadow_root.h"
65 #include "third_party/blink/renderer/core/execution_context/agent.h"
66 #include "third_party/blink/renderer/core/fileapi/url_file_api.h"
67 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
68 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
69 #include "third_party/blink/renderer/core/frame/local_frame.h"
70 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
71 #include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
72 #include "third_party/blink/renderer/core/frame/settings.h"
73 #include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
74 #include "third_party/blink/renderer/core/html/html_source_element.h"
75 #include "third_party/blink/renderer/core/html/media/audio_output_device_controller.h"
76 #include "third_party/blink/renderer/core/html/media/autoplay_policy.h"
77 #include "third_party/blink/renderer/core/html/media/html_media_element_controls_list.h"
78 #include "third_party/blink/renderer/core/html/media/media_controls.h"
79 #include "third_party/blink/renderer/core/html/media/media_error.h"
80 #include "third_party/blink/renderer/core/html/media/media_fragment_uri_parser.h"
81 #include "third_party/blink/renderer/core/html/media/media_source_attachment.h"
82 #include "third_party/blink/renderer/core/html/media/media_source_handle.h"
83 #include "third_party/blink/renderer/core/html/media/media_source_tracer.h"
84 #include "third_party/blink/renderer/core/html/time_ranges.h"
85 #include "third_party/blink/renderer/core/html/track/audio_track.h"
86 #include "third_party/blink/renderer/core/html/track/audio_track_list.h"
87 #include "third_party/blink/renderer/core/html/track/automatic_track_selection.h"
88 #include "third_party/blink/renderer/core/html/track/cue_timeline.h"
89 #include "third_party/blink/renderer/core/html/track/html_track_element.h"
90 #include "third_party/blink/renderer/core/html/track/loadable_text_track.h"
91 #include "third_party/blink/renderer/core/html/track/text_track_container.h"
92 #include "third_party/blink/renderer/core/html/track/text_track_list.h"
93 #include "third_party/blink/renderer/core/html/track/video_track.h"
94 #include "third_party/blink/renderer/core/html/track/video_track_list.h"
95 #include "third_party/blink/renderer/core/html_names.h"
96 #include "third_party/blink/renderer/core/inspector/console_message.h"
97 #include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
98 #include "third_party/blink/renderer/core/layout/layout_media.h"
99 #include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
100 #include "third_party/blink/renderer/core/page/chrome_client.h"
101 #include "third_party/blink/renderer/core/page/page.h"
102 #include "third_party/blink/renderer/core/speech/speech_synthesis_base.h"
103 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
104 #include "third_party/blink/renderer/platform/audio/audio_source_provider_client.h"
105 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
106 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
107 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
108 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
109 #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
110 #include "third_party/blink/renderer/platform/network/mime/content_type.h"
111 #include "third_party/blink/renderer/platform/network/mime/mime_type_from_url.h"
112 #include "third_party/blink/renderer/platform/network/network_state_notifier.h"
113 #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
114 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
115 #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
116 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
117 #include "third_party/blink/renderer/platform/wtf/functional.h"
118 #include "third_party/blink/renderer/platform/wtf/vector.h"
119 #include "ui/accessibility/accessibility_features.h"
120 #include "ui/display/screen_info.h"
122 #if BUILDFLAG(IS_TIZEN_TV)
123 #include "third_party/blink/public/platform/web_application_type.h"
126 #ifndef LOG_MEDIA_EVENTS
127 // Default to not logging events because so many are generated they can
128 // overwhelm the rest of the logging.
129 #define LOG_MEDIA_EVENTS 0
132 #ifndef LOG_OFFICIAL_TIME_STATUS
133 // Default to not logging status of official time because it adds a fair amount
134 // of overhead and logging.
135 #define LOG_OFFICIAL_TIME_STATUS 0
140 using WeakMediaElementSet = HeapHashSet<WeakMember<HTMLMediaElement>>;
141 using DocumentElementSetMap =
142 HeapHashMap<WeakMember<Document>, Member<WeakMediaElementSet>>;
146 // When enabled, CSS media queries are supported in <source> elements.
147 BASE_FEATURE(kVideoSourceMediaQuerySupport,
148 "VideoSourceMediaQuerySupport",
149 base::FEATURE_ENABLED_BY_DEFAULT);
151 // This enum is used to record histograms. Do not reorder.
152 enum class MediaControlsShow {
158 kUserExplicitlyEnabled,
159 kUserExplicitlyDisabled,
160 kMaxValue = kUserExplicitlyDisabled,
163 // The state of the HTMLMediaElement when ProgressEventTimerFired is invoked.
164 // These values are histogrammed, so please only add values to the end.
165 enum class ProgressEventTimerState {
166 // networkState is not NETWORK_LOADING.
168 // MediaShouldBeOpaque() is true.
169 kMediaShouldBeOpaque,
170 // "progress" event was scheduled.
172 // No progress. The "stalled" event was scheduled.
174 // No progress. No "stalled" event scheduled because a Media Source Attachment
176 kHasMediaSourceAttachment,
177 // No progress. No "stalled" event scheduled because there was recent
180 // No progress. No "stalled" event scheduled because it was already scheduled.
181 kStalledEventAlreadyScheduled,
182 kMaxValue = kStalledEventAlreadyScheduled
185 // Records the state of the HTMLMediaElement when its "progress event" timer
187 // TODO(crbug.com/1143317): Remove once the bug is fixed.
188 void RecordProgressEventTimerState(ProgressEventTimerState state) {
189 UMA_HISTOGRAM_ENUMERATION("Media.ProgressEventTimerState", state);
192 static const base::TimeDelta kStalledNotificationInterval = base::Seconds(3);
194 String UrlForLoggingMedia(const KURL& url) {
195 static const unsigned kMaximumURLLengthForLogging = 128;
197 if (url.GetString().length() < kMaximumURLLengthForLogging)
198 return url.GetString();
199 return url.GetString().Substring(0, kMaximumURLLengthForLogging) + "...";
202 const char* BoolString(bool val) {
203 return val ? "true" : "false";
206 DocumentElementSetMap& DocumentToElementSetMap() {
207 DEFINE_STATIC_LOCAL(Persistent<DocumentElementSetMap>, map,
208 (MakeGarbageCollected<DocumentElementSetMap>()));
212 void AddElementToDocumentMap(HTMLMediaElement* element, Document* document) {
213 DocumentElementSetMap& map = DocumentToElementSetMap();
214 WeakMediaElementSet* set = nullptr;
215 auto it = map.find(document);
216 if (it == map.end()) {
217 set = MakeGarbageCollected<WeakMediaElementSet>();
218 map.insert(document, set);
222 set->insert(element);
225 void RemoveElementFromDocumentMap(HTMLMediaElement* element,
226 Document* document) {
227 DocumentElementSetMap& map = DocumentToElementSetMap();
228 auto it = map.find(document);
229 DCHECK(it != map.end());
230 WeakMediaElementSet* set = it->value;
236 String BuildElementErrorMessage(const String& error) {
237 // Prepend a UA-specific-error code before the first ':', to enable better
238 // collection and aggregation of UA-specific-error codes from
239 // MediaError.message by web apps. WebMediaPlayer::GetErrorMessage() should
240 // similarly conform to this format.
241 DEFINE_STATIC_LOCAL(const String, element_error_prefix,
242 ("MEDIA_ELEMENT_ERROR: "));
243 StringBuilder builder;
244 builder.Append(element_error_prefix);
245 builder.Append(error);
246 return builder.ToString();
249 class AudioSourceProviderClientLockScope {
253 explicit AudioSourceProviderClientLockScope(HTMLMediaElement& element)
254 : client_(element.AudioSourceNode()) {
258 ~AudioSourceProviderClientLockScope() {
264 AudioSourceProviderClient* client_;
267 const AtomicString& AudioKindToString(
268 WebMediaPlayerClient::AudioTrackKind kind) {
270 case WebMediaPlayerClient::kAudioTrackKindNone:
272 case WebMediaPlayerClient::kAudioTrackKindAlternative:
273 return AudioTrack::AlternativeKeyword();
274 case WebMediaPlayerClient::kAudioTrackKindDescriptions:
275 return AudioTrack::DescriptionsKeyword();
276 case WebMediaPlayerClient::kAudioTrackKindMain:
277 return AudioTrack::MainKeyword();
278 case WebMediaPlayerClient::kAudioTrackKindMainDescriptions:
279 return AudioTrack::MainDescriptionsKeyword();
280 case WebMediaPlayerClient::kAudioTrackKindTranslation:
281 return AudioTrack::TranslationKeyword();
282 case WebMediaPlayerClient::kAudioTrackKindCommentary:
283 return AudioTrack::CommentaryKeyword();
290 const AtomicString& VideoKindToString(
291 WebMediaPlayerClient::VideoTrackKind kind) {
293 case WebMediaPlayerClient::kVideoTrackKindNone:
295 case WebMediaPlayerClient::kVideoTrackKindAlternative:
296 return VideoTrack::AlternativeKeyword();
297 case WebMediaPlayerClient::kVideoTrackKindCaptions:
298 return VideoTrack::CaptionsKeyword();
299 case WebMediaPlayerClient::kVideoTrackKindMain:
300 return VideoTrack::MainKeyword();
301 case WebMediaPlayerClient::kVideoTrackKindSign:
302 return VideoTrack::SignKeyword();
303 case WebMediaPlayerClient::kVideoTrackKindSubtitles:
304 return VideoTrack::SubtitlesKeyword();
305 case WebMediaPlayerClient::kVideoTrackKindCommentary:
306 return VideoTrack::CommentaryKeyword();
313 bool CanLoadURL(const KURL& url, const String& content_type_str) {
314 DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
316 ContentType content_type(content_type_str);
317 String content_mime_type = content_type.GetType().DeprecatedLower();
318 String content_type_codecs = content_type.Parameter(codecs);
320 // If the MIME type is missing or is not meaningful, try to figure it out from
322 if (content_mime_type.empty() ||
323 content_mime_type == "application/octet-stream" ||
324 content_mime_type == "text/plain") {
325 if (url.ProtocolIsData())
326 content_mime_type = MimeTypeFromDataURL(url.GetString());
329 // If no MIME type is specified, always attempt to load.
330 if (content_mime_type.empty())
333 // 4.8.12.3 MIME types - In the absence of a specification to the contrary,
334 // the MIME type "application/octet-stream" when used with parameters, e.g.
335 // "application/octet-stream;codecs=theora", is a type that the user agent
336 // knows it cannot render.
337 if (content_mime_type != "application/octet-stream" ||
338 content_type_codecs.empty()) {
339 return MIMETypeRegistry::SupportsMediaMIMEType(content_mime_type,
340 content_type_codecs) !=
341 MIMETypeRegistry::kNotSupported;
347 String PreloadTypeToString(WebMediaPlayer::Preload preload_type) {
348 switch (preload_type) {
349 case WebMediaPlayer::kPreloadNone:
351 case WebMediaPlayer::kPreloadMetaData:
353 case WebMediaPlayer::kPreloadAuto:
361 void RecordShowControlsUsage(const HTMLMediaElement* element,
362 MediaControlsShow value) {
363 if (element->IsHTMLVideoElement()) {
364 base::UmaHistogramEnumeration("Media.Controls.Show.Video", value);
367 base::UmaHistogramEnumeration("Media.Controls.Show.Audio", value);
370 bool IsValidPlaybackRate(double rate) {
371 return rate == 0.0 || (rate >= HTMLMediaElement::kMinPlaybackRate &&
372 rate <= HTMLMediaElement::kMaxPlaybackRate);
375 std::ostream& operator<<(std::ostream& stream,
376 HTMLMediaElement const& media_element) {
377 return stream << static_cast<void const*>(&media_element);
380 } // anonymous namespace
383 MIMETypeRegistry::SupportsType HTMLMediaElement::GetSupportsType(
384 const ContentType& content_type) {
385 // TODO(https://crbug.com/809912): Finding source of mime parsing crash.
386 static base::debug::CrashKeyString* content_type_crash_key =
387 base::debug::AllocateCrashKeyString("media_content_type",
388 base::debug::CrashKeySize::Size256);
389 base::debug::ScopedCrashKeyString scoped_crash_key(
390 content_type_crash_key, content_type.Raw().Utf8().c_str());
392 String type = content_type.GetType().DeprecatedLower();
393 // The codecs string is not lower-cased because MP4 values are case sensitive
394 // per http://tools.ietf.org/html/rfc4281#page-7.
395 String type_codecs = content_type.Parameter("codecs");
398 return MIMETypeRegistry::kNotSupported;
400 // 4.8.12.3 MIME types - The canPlayType(type) method must return the empty
401 // string if type is a type that the user agent knows it cannot render or is
402 // the type "application/octet-stream"
403 if (type == "application/octet-stream")
404 return MIMETypeRegistry::kNotSupported;
406 // |contentType| could be handled using ParsedContentType, but there are
407 // still a lot of sites using codec strings that don't work with the
408 // stricter parsing rules.
409 MIMETypeRegistry::SupportsType result =
410 MIMETypeRegistry::SupportsMediaMIMEType(type, type_codecs);
414 bool HTMLMediaElement::IsHLSURL(const KURL& url) {
415 // Keep the same logic as in media_codec_util.h.
416 if (url.IsNull() || url.IsEmpty())
419 if (!url.IsLocalFile() && !url.ProtocolIs("http") && !url.ProtocolIs("https"))
422 return url.GetPath().EndsWith(".m3u8");
426 void HTMLMediaElement::OnMediaControlsEnabledChange(Document* document) {
427 auto it = DocumentToElementSetMap().find(document);
428 if (it == DocumentToElementSetMap().end())
431 WeakMediaElementSet& elements = *it->value;
432 for (const auto& element : elements) {
433 element->UpdateControlsVisibility();
434 if (element->GetMediaControls())
435 element->GetMediaControls()->OnMediaControlsEnabledChange();
439 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tag_name,
441 : HTMLElement(tag_name, document),
442 ActiveScriptWrappable<HTMLMediaElement>({}),
443 ExecutionContextLifecycleStateObserver(GetExecutionContext()),
444 load_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
446 &HTMLMediaElement::LoadTimerFired),
447 audio_tracks_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
449 &HTMLMediaElement::AudioTracksTimerFired),
450 removed_from_document_timer_(
451 document.GetTaskRunner(TaskType::kInternalMedia),
453 &HTMLMediaElement::OnRemovedFromDocumentTimerFired),
454 progress_event_timer_(
455 document.GetTaskRunner(TaskType::kInternalMedia),
456 WTF::BindRepeating(&HTMLMediaElement::ProgressEventTimerFired,
457 WrapWeakPersistent(this))),
458 playback_progress_timer_(
459 document.GetTaskRunner(TaskType::kInternalMedia),
460 WTF::BindRepeating(&HTMLMediaElement::PlaybackProgressTimerFired,
461 WrapWeakPersistent(this))),
463 MakeGarbageCollected<EventQueue>(GetExecutionContext(),
464 TaskType::kMediaElementEvent)),
465 playback_rate_(1.0f),
466 default_playback_rate_(1.0f),
467 network_state_(kNetworkEmpty),
468 ready_state_(kHaveNothing),
469 ready_state_maximum_(kHaveNothing),
472 duration_(std::numeric_limits<double>::quiet_NaN()),
473 last_time_update_event_media_time_(
474 std::numeric_limits<double>::quiet_NaN()),
475 default_playback_start_position_(0),
476 load_state_(kWaitingForSource),
477 deferred_load_state_(kNotDeferred),
478 deferred_load_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
480 &HTMLMediaElement::DeferredLoadTimerFired),
482 official_playback_position_(0),
483 official_playback_position_needs_update_(true),
484 fragment_end_time_(std::numeric_limits<double>::quiet_NaN()),
485 pending_action_flags_(0),
487 should_delay_load_event_(false),
488 have_fired_loaded_data_(false),
493 paused_by_context_paused_(false),
494 show_poster_flag_(true),
495 sent_stalled_event_(false),
496 ignore_preload_none_(false),
497 text_tracks_visible_(false),
498 should_perform_automatic_track_selection_(true),
499 tracks_are_ready_(true),
500 processing_preference_change_(false),
501 was_always_muted_(true),
502 #if BUILDFLAG(IS_TIZEN_TV)
503 is_deactivate_(false),
504 is_translated_url_(false),
506 #if defined(TIZEN_MULTIMEDIA)
507 live_playback_complete_(false),
509 audio_tracks_(MakeGarbageCollected<AudioTrackList>(*this)),
510 video_tracks_(MakeGarbageCollected<VideoTrackList>(*this)),
511 audio_source_node_(nullptr),
512 speech_synthesis_(nullptr),
513 autoplay_policy_(MakeGarbageCollected<AutoplayPolicy>(this)),
514 remote_playback_client_(nullptr),
515 media_controls_(nullptr),
516 controls_list_(MakeGarbageCollected<HTMLMediaElementControlsList>(this)),
517 lazy_load_intersection_observer_(nullptr) {
518 DVLOG(1) << "HTMLMediaElement(" << *this << ")";
522 LocalFrame* frame = document.GetFrame();
524 remote_playback_client_ =
525 frame->Client()->CreateWebRemotePlaybackClient(*this);
528 SetHasCustomStyleCallbacks();
529 AddElementToDocumentMap(this, &document);
531 UseCounter::Count(document, WebFeature::kHTMLMediaElement);
534 HTMLMediaElement::~HTMLMediaElement() {
535 DVLOG(1) << "~HTMLMediaElement(" << *this << ")";
538 void HTMLMediaElement::Dispose() {
539 // Destroying the player may cause a resource load to be canceled,
540 // which could result in LocalDOMWindow::dispatchWindowLoadEvent() being
541 // called via ResourceFetch::didLoadResource(), then
542 // FrameLoader::checkCompleted(). But it's guaranteed that the load event
543 // doesn't get dispatched during the object destruction.
544 // See Document::isDelayingLoadEvent().
545 // Also see http://crbug.com/275223 for more details.
546 ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
548 progress_event_timer_.Shutdown();
549 playback_progress_timer_.Shutdown();
552 void HTMLMediaElement::DidMoveToNewDocument(Document& old_document) {
553 DVLOG(3) << "didMoveToNewDocument(" << *this << ")";
555 load_timer_.MoveToNewTaskRunner(
556 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
557 progress_event_timer_.MoveToNewTaskRunner(
558 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
559 playback_progress_timer_.MoveToNewTaskRunner(
560 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
561 audio_tracks_timer_.MoveToNewTaskRunner(
562 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
563 deferred_load_timer_.MoveToNewTaskRunner(
564 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
565 removed_from_document_timer_.MoveToNewTaskRunner(
566 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
568 autoplay_policy_->DidMoveToNewDocument(old_document);
571 cue_timeline_->DidMoveToNewDocument(old_document);
574 // Stop speaking and set speech_synthesis_ to nullptr so that it is
575 // re-created on-demand when SpeechSynthesis() is called.
576 if (speech_synthesis_) {
577 speech_synthesis_->Cancel();
578 speech_synthesis_.Clear();
581 if (should_delay_load_event_) {
582 GetDocument().IncrementLoadEventDelayCount();
583 // Note: Keeping the load event delay count increment on oldDocument that
584 // was added when should_delay_load_event_ was set so that destruction of
585 // web_media_player_ can not cause load event dispatching in oldDocument.
587 // Incrementing the load event delay count so that destruction of
588 // web_media_player_ can not cause load event dispatching in oldDocument.
589 old_document.IncrementLoadEventDelayCount();
592 RemoveElementFromDocumentMap(this, &old_document);
593 AddElementToDocumentMap(this, &GetDocument());
594 SetExecutionContext(GetExecutionContext());
596 // FIXME: This is a temporary fix to prevent this object from causing the
597 // MediaPlayer to dereference LocalFrame and FrameLoader pointers from the
598 // previous document. This restarts the load, as if the src attribute had been
599 // set. A proper fix would provide a mechanism to allow this object to
600 // refresh the MediaPlayer's LocalFrame and FrameLoader references on document
601 // changes so that playback can be resumed properly.
602 // TODO(liberato): Consider checking that the new document's opener is the old
603 // document: GetDocument().GetFrame()->Opener() == old_document.GetFrame().
604 ignore_preload_none_ = false;
606 // Experimental: Try to avoid destroying the media player when transferring a
607 // media element to a new document. This is a work in progress, and may cause
608 // security and/or stability issues.
609 // Normally, moving a player between documents requires destroying the
610 // media player because web media player cannot outlive the render frame that
611 // holds the element which creates the player. However, when transferring a
612 // media player to a same-origin picture-in-picture window opened by this
613 // document, it is safe to reuse because a picture-in-picture window is
614 // guaranteed not to outlive its opener document because
615 // DocumentPictureInPictureController watches the destruction and navigation
616 // of the opener's WebContents.
617 if (!ShouldReusePlayer(old_document, GetDocument())) {
618 // Don't worry about notifications from any previous document if we're not
619 // re-using the player.
620 if (opener_context_observer_)
621 opener_context_observer_->SetContextLifecycleNotifier(nullptr);
623 } else if (opener_document_ == GetDocument()) {
624 // The element is moving back to the player's opener, so stop worrying.
625 DCHECK(opener_context_observer_);
626 opener_context_observer_->SetContextLifecycleNotifier(
627 opener_document_->GetExecutionContext());
628 opener_context_observer_ = nullptr;
629 opener_document_ = nullptr;
631 // Moving to a new document, so make sure that the player's opener is not
632 // closed while we're still using it.
633 if (!opener_context_observer_) {
634 DCHECK(!opener_document_);
635 // Only set this when we're going from "original opener" to "elsewhere",
636 // in case we're moved from one same-origin window to another.
637 opener_document_ = old_document;
638 opener_context_observer_ =
639 MakeGarbageCollected<OpenerContextObserver>(this);
641 opener_context_observer_->SetContextLifecycleNotifier(
642 opener_document_->GetExecutionContext());
645 // Decrement the load event delay count on oldDocument now that
646 // web_media_player_ has been destroyed and there is no risk of dispatching a
647 // load event from within the destructor.
648 old_document.DecrementLoadEventDelayCount();
650 HTMLElement::DidMoveToNewDocument(old_document);
653 bool HTMLMediaElement::ShouldReusePlayer(Document& old_document,
654 Document& new_document) const {
655 // A NULL frame implies a NULL domWindow, so just check one of them
656 if (!old_document.GetFrame() || !new_document.GetFrame()) {
660 // Don't reuse player if the Document Picture-in-Picture API is disabled for
662 if (!RuntimeEnabledFeatures::DocumentPictureInPictureAPIEnabled(
663 old_document.domWindow()->GetExecutionContext()) &&
664 !RuntimeEnabledFeatures::DocumentPictureInPictureAPIEnabled(
665 new_document.domWindow()->GetExecutionContext())) {
669 auto* new_origin = new_document.GetFrame()
671 .GetSecurityContext()
672 ->GetSecurityOrigin();
673 auto* old_origin = old_document.GetFrame()
675 .GetSecurityContext()
676 ->GetSecurityOrigin();
678 if (!old_origin || !new_origin || !old_origin->IsSameOriginWith(new_origin)) {
682 // Reuse player if the two documents have opener-pip relationship (for either
684 return (new_document.domWindow()->IsPictureInPictureWindow() &&
685 new_document.GetFrame()->Opener() == old_document.GetFrame()) ||
686 (old_document.domWindow()->IsPictureInPictureWindow() &&
687 old_document.GetFrame()->Opener() == new_document.GetFrame());
690 void HTMLMediaElement::AttachToNewFrame() {
691 // The opener has closed, so definitely nothing else should use this.
692 opener_document_ = nullptr;
693 // Do not ask it to stop notifying us -- if this is a callback from the
694 // listener, then it's ExecutionContext has been destroyed and it's not
695 // allowed to unregister.
696 opener_context_observer_ = nullptr;
697 // Reset mojo state that is coupled to |old_document|'s execution context.
698 // NOTE: |media_player_host_remote_| is also coupled to |old_document|'s
701 InvokeLoadAlgorithm();
704 void HTMLMediaElement::ResetMojoState() {
705 if (media_player_host_remote_)
706 media_player_host_remote_->Value().reset();
707 media_player_host_remote_ = MakeGarbageCollected<DisallowNewWrapper<
708 HeapMojoAssociatedRemote<media::mojom::blink::MediaPlayerHost>>>(
709 GetExecutionContext());
710 if (media_player_observer_remote_set_)
711 media_player_observer_remote_set_->Value().Clear();
712 media_player_observer_remote_set_ = MakeGarbageCollected<DisallowNewWrapper<
713 HeapMojoAssociatedRemoteSet<media::mojom::blink::MediaPlayerObserver>>>(
714 GetExecutionContext());
715 if (media_player_receiver_set_)
716 media_player_receiver_set_->Value().Clear();
717 media_player_receiver_set_ =
718 MakeGarbageCollected<DisallowNewWrapper<HeapMojoAssociatedReceiverSet<
719 media::mojom::blink::MediaPlayer, HTMLMediaElement>>>(
720 this, GetExecutionContext());
723 bool HTMLMediaElement::SupportsFocus() const {
724 // TODO(https://crbug.com/911882): Depending on result of discussion, remove.
725 if (ownerDocument()->IsMediaDocument())
728 // If no controls specified, we should still be able to focus the element if
730 return ShouldShowControls() || HTMLElement::SupportsFocus();
733 bool HTMLMediaElement::IsFocusable(
734 bool disallow_layout_updates_for_accessibility_only) const {
735 if (!SupportsFocus()) {
738 return !IsFullscreen() || HTMLElement::IsFocusable(
739 disallow_layout_updates_for_accessibility_only);
742 bool HTMLMediaElement::IsKeyboardFocusable() const {
743 // Media elements are keyboard focusable if they are focusable at all,
744 // and don't have a negative tabindex set.
745 return IsFocusable() && tabIndex() >= 0;
748 int HTMLMediaElement::DefaultTabIndex() const {
752 void HTMLMediaElement::ParseAttribute(
753 const AttributeModificationParams& params) {
754 const QualifiedName& name = params.name;
755 if (name == html_names::kSrcAttr) {
756 DVLOG(2) << "parseAttribute(" << *this
757 << ", kSrcAttr, old=" << params.old_value
758 << ", new=" << params.new_value << ")";
759 // A change to the src attribute can affect intrinsic size, which in turn
760 // requires a style recalc.
761 SetNeedsStyleRecalc(kLocalStyleChange,
762 StyleChangeReasonForTracing::FromAttribute(name));
763 // Trigger a reload, as long as the 'src' attribute is present.
764 if (!params.new_value.IsNull()) {
765 ignore_preload_none_ = false;
766 InvokeLoadAlgorithm();
768 } else if (name == html_names::kControlsAttr) {
769 UseCounter::Count(GetDocument(),
770 WebFeature::kHTMLMediaElementControlsAttribute);
771 UpdateControlsVisibility();
772 } else if (name == html_names::kControlslistAttr) {
773 UseCounter::Count(GetDocument(),
774 WebFeature::kHTMLMediaElementControlsListAttribute);
775 if (params.old_value != params.new_value) {
776 controls_list_->DidUpdateAttributeValue(params.old_value,
778 if (GetMediaControls())
779 GetMediaControls()->OnControlsListUpdated();
781 } else if (name == html_names::kPreloadAttr) {
783 } else if (name == html_names::kDisableremoteplaybackAttr) {
784 // This attribute is an extension described in the Remote Playback API spec.
785 // Please see: https://w3c.github.io/remote-playback
786 UseCounter::Count(GetDocument(),
787 WebFeature::kDisableRemotePlaybackAttribute);
788 if (params.old_value != params.new_value) {
789 if (web_media_player_) {
790 web_media_player_->RequestRemotePlaybackDisabled(
791 !params.new_value.IsNull());
794 } else if (name == html_names::kLatencyhintAttr &&
795 RuntimeEnabledFeatures::MediaLatencyHintEnabled()) {
796 if (web_media_player_)
797 web_media_player_->SetLatencyHint(latencyHint());
799 HTMLElement::ParseAttribute(params);
803 void HTMLMediaElement::ParserDidSetAttributes() {
804 HTMLElement::ParserDidSetAttributes();
806 if (FastHasAttribute(html_names::kMutedAttr))
810 // This method is being used as a way to know that cloneNode finished cloning
811 // attribute as there is no callback notifying about the end of a cloning
812 // operation. Indeed, it is required per spec to set the muted state based on
813 // the content attribute when the object is created.
814 void HTMLMediaElement::CloneNonAttributePropertiesFrom(const Element& other,
815 NodeCloningData& data) {
816 HTMLElement::CloneNonAttributePropertiesFrom(other, data);
818 if (FastHasAttribute(html_names::kMutedAttr))
822 void HTMLMediaElement::FinishParsingChildren() {
823 HTMLElement::FinishParsingChildren();
825 if (Traversal<HTMLTrackElement>::FirstChild(*this))
826 ScheduleTextTrackResourceLoad();
829 bool HTMLMediaElement::LayoutObjectIsNeeded(const DisplayStyle& style) const {
830 return ShouldShowControls() && HTMLElement::LayoutObjectIsNeeded(style);
833 LayoutObject* HTMLMediaElement::CreateLayoutObject(const ComputedStyle&) {
834 return MakeGarbageCollected<LayoutMedia>(this);
837 Node::InsertionNotificationRequest HTMLMediaElement::InsertedInto(
838 ContainerNode& insertion_point) {
839 DVLOG(3) << "insertedInto(" << *this << ", " << insertion_point << ")";
841 HTMLElement::InsertedInto(insertion_point);
842 if (insertion_point.isConnected()) {
843 UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementInDocument);
844 if ((!FastGetAttribute(html_names::kSrcAttr).empty() ||
845 src_object_stream_descriptor_ || src_object_media_source_handle_) &&
846 network_state_ == kNetworkEmpty) {
847 ignore_preload_none_ = false;
848 InvokeLoadAlgorithm();
852 return kInsertionShouldCallDidNotifySubtreeInsertions;
855 void HTMLMediaElement::DidNotifySubtreeInsertionsToDocument() {
856 UpdateControlsVisibility();
859 void HTMLMediaElement::RemovedFrom(ContainerNode& insertion_point) {
860 DVLOG(3) << "removedFrom(" << *this << ", " << insertion_point << ")";
862 removed_from_document_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
864 HTMLElement::RemovedFrom(insertion_point);
867 void HTMLMediaElement::AttachLayoutTree(AttachContext& context) {
868 HTMLElement::AttachLayoutTree(context);
870 UpdateLayoutObject();
873 void HTMLMediaElement::DidRecalcStyle(const StyleRecalcChange change) {
874 if (!change.ReattachLayoutTree())
875 UpdateLayoutObject();
878 void HTMLMediaElement::ScheduleTextTrackResourceLoad() {
879 DVLOG(3) << "scheduleTextTrackResourceLoad(" << *this << ")";
881 pending_action_flags_ |= kLoadTextTrackResource;
883 if (!load_timer_.IsActive())
884 load_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
887 void HTMLMediaElement::ScheduleNextSourceChild() {
888 // Schedule the timer to try the next <source> element WITHOUT resetting state
889 // ala invokeLoadAlgorithm.
890 pending_action_flags_ |= kLoadMediaResource;
891 load_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
894 void HTMLMediaElement::ScheduleEvent(const AtomicString& event_name) {
895 Event* event = Event::CreateCancelable(event_name);
896 event->SetTarget(this);
897 ScheduleEvent(event);
900 void HTMLMediaElement::ScheduleEvent(Event* event) {
902 DVLOG(3) << "ScheduleEvent(" << (void*)this << ")"
903 << " - scheduling '" << event->type() << "'";
905 async_event_queue_->EnqueueEvent(FROM_HERE, *event);
908 void HTMLMediaElement::LoadTimerFired(TimerBase*) {
909 if (pending_action_flags_ & kLoadTextTrackResource)
910 HonorUserPreferencesForAutomaticTextTrackSelection();
912 if (pending_action_flags_ & kLoadMediaResource) {
913 if (load_state_ == kLoadingFromSourceElement)
914 LoadNextSourceChild();
919 pending_action_flags_ = 0;
922 MediaError* HTMLMediaElement::error() const {
926 void HTMLMediaElement::SetSrc(const AtomicString& url) {
927 setAttribute(html_names::kSrcAttr, url);
930 void HTMLMediaElement::SetSrcObjectVariant(
931 SrcObjectVariant src_object_variant) {
932 DVLOG(1) << __func__ << "(" << *this << ")";
933 src_object_stream_descriptor_ = nullptr;
934 src_object_media_source_handle_ = nullptr;
935 if (auto** desc = absl::get_if<MediaStreamDescriptor*>(&src_object_variant)) {
936 src_object_stream_descriptor_ = *desc;
937 } else if (auto** handle =
938 absl::get_if<MediaSourceHandle*>(&src_object_variant)) {
939 src_object_media_source_handle_ = *handle;
943 << ": stream_descriptor=" << src_object_stream_descriptor_
944 << ", media_source_handle=" << src_object_media_source_handle_;
946 InvokeLoadAlgorithm();
949 HTMLMediaElement::SrcObjectVariant HTMLMediaElement::GetSrcObjectVariant()
951 DVLOG(1) << __func__ << "(" << *this << ")"
952 << ": stream_descriptor=" << src_object_stream_descriptor_
953 << ", media_source_handle=" << src_object_media_source_handle_;
955 // At most one is set.
956 DCHECK(!(src_object_stream_descriptor_ && src_object_media_source_handle_));
958 if (src_object_media_source_handle_)
959 return SrcObjectVariant(src_object_media_source_handle_.Get());
961 return SrcObjectVariant(src_object_stream_descriptor_.Get());
964 HTMLMediaElement::NetworkState HTMLMediaElement::getNetworkState() const {
965 return network_state_;
968 String HTMLMediaElement::canPlayType(const String& mime_type) const {
969 MIMETypeRegistry::SupportsType support =
970 GetSupportsType(ContentType(mime_type));
972 if (IdentifiabilityStudySettings::Get()->ShouldSampleType(
973 blink::IdentifiableSurface::Type::kHTMLMediaElement_CanPlayType)) {
974 blink::IdentifiabilityMetricBuilder(GetDocument().UkmSourceID())
976 blink::IdentifiableSurface::FromTypeAndToken(
977 blink::IdentifiableSurface::Type::kHTMLMediaElement_CanPlayType,
978 IdentifiabilityBenignStringToken(mime_type)),
979 static_cast<uint64_t>(support))
980 .Record(GetDocument().UkmRecorder());
986 case MIMETypeRegistry::kNotSupported:
987 can_play = g_empty_string;
989 case MIMETypeRegistry::kMaybeSupported:
992 case MIMETypeRegistry::kSupported:
993 can_play = "probably";
997 DVLOG(2) << "canPlayType(" << *this << ", " << mime_type << ") -> "
1003 void HTMLMediaElement::load() {
1004 DVLOG(1) << "load(" << *this << ")";
1006 autoplay_policy_->TryUnlockingUserGesture();
1008 ignore_preload_none_ = true;
1009 InvokeLoadAlgorithm();
1012 // Implements the "media element load algorithm" as defined by
1013 // https://html.spec.whatwg.org/multipage/media.html#media-element-load-algorithm
1014 // TODO(srirama.m): Currently ignore_preload_none_ is reset before calling
1015 // invokeLoadAlgorithm() in all places except load(). Move it inside here
1016 // once microtask is implemented for "Await a stable state" step
1017 // in resource selection algorithm.
1018 void HTMLMediaElement::InvokeLoadAlgorithm() {
1019 DVLOG(3) << "invokeLoadAlgorithm(" << *this << ")";
1021 // Perform the cleanup required for the resource load algorithm to run.
1022 StopPeriodicTimers();
1024 CancelDeferredLoad();
1025 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if
1026 // necessary and set pending_action_flags_ to 0 here.
1027 pending_action_flags_ &= ~kLoadMediaResource;
1028 sent_stalled_event_ = false;
1029 have_fired_loaded_data_ = false;
1031 autoplay_policy_->StopAutoplayMutedWhenVisible();
1033 // 1 - Abort any already-running instance of the resource selection algorithm
1034 // for this element.
1035 load_state_ = kWaitingForSource;
1036 current_source_node_ = nullptr;
1038 // 2 - Let pending tasks be a list of tasks from the media element's media
1039 // element task source in one of the task queues.
1041 // 3 - For each task in the pending tasks that would run resolve pending
1042 // play promises or project pending play prmoises algorithms, immediately
1043 // resolve or reject those promises in the order the corresponding tasks
1046 // TODO(mlamouri): the promises are first resolved then rejected but the
1047 // order between resolved/rejected promises isn't respected. This could be
1048 // improved when the same task is used for both cases.
1050 // TODO(mlamouri): don't run the callback synchronously if we are not allowed
1051 // to run scripts. It can happen in some edge cases. https://crbug.com/660382
1052 if (play_promise_resolve_task_handle_.IsActive() &&
1053 !ScriptForbiddenScope::IsScriptForbidden()) {
1054 play_promise_resolve_task_handle_.Cancel();
1055 ResolveScheduledPlayPromises();
1057 if (play_promise_reject_task_handle_.IsActive() &&
1058 !ScriptForbiddenScope::IsScriptForbidden()) {
1059 play_promise_reject_task_handle_.Cancel();
1060 RejectScheduledPlayPromises();
1063 // 4 - Remove each task in pending tasks from its task queue.
1064 CancelPendingEventsAndCallbacks();
1066 // 5 - If the media element's networkState is set to NETWORK_LOADING or
1067 // NETWORK_IDLE, queue a task to fire a simple event named abort at the media
1069 if (network_state_ == kNetworkLoading || network_state_ == kNetworkIdle)
1070 ScheduleEvent(event_type_names::kAbort);
1072 ResetMediaPlayerAndMediaSource();
1074 // 6 - If the media element's networkState is not set to NETWORK_EMPTY, then
1075 // run these substeps
1076 if (network_state_ != kNetworkEmpty) {
1077 // 4.1 - Queue a task to fire a simple event named emptied at the media
1079 ScheduleEvent(event_type_names::kEmptied);
1081 // 4.2 - If a fetching process is in progress for the media element, the
1082 // user agent should stop it.
1083 SetNetworkState(kNetworkEmpty);
1085 // 4.4 - Forget the media element's media-resource-specific tracks.
1086 ForgetResourceSpecificTracks();
1088 // 4.5 - If readyState is not set to kHaveNothing, then set it to that
1090 ready_state_ = kHaveNothing;
1091 ready_state_maximum_ = kHaveNothing;
1093 DCHECK(!paused_ || play_promise_resolvers_.empty());
1095 // 4.6 - If the paused attribute is false, then run these substeps
1097 // 4.6.1 - Set the paused attribute to true.
1100 // 4.6.2 - Take pending play promises and reject pending play promises
1101 // with the result and an "AbortError" DOMException.
1102 RejectPlayPromises(DOMExceptionCode::kAbortError,
1103 "The play() request was interrupted by a new load "
1104 "request. https://goo.gl/LdLk22");
1107 // 4.7 - If seeking is true, set it to false.
1110 // 4.8 - Set the current playback position to 0.
1111 // Set the official playback position to 0.
1112 // If this changed the official playback position, then queue a task
1113 // to fire a simple event named timeupdate at the media element.
1114 // 4.9 - Set the initial playback position to 0.
1115 SetOfficialPlaybackPosition(0);
1116 ScheduleTimeupdateEvent(false);
1117 GetCueTimeline().OnReadyStateReset();
1119 // 4.10 - Set the timeline offset to Not-a-Number (NaN).
1120 // 4.11 - Update the duration attribute to Not-a-Number (NaN).
1121 } else if (!paused_) {
1122 // TODO(foolip): There is a proposal to always reset the paused state
1123 // in the media element load algorithm, to avoid a bogus play() promise
1124 // rejection: https://github.com/whatwg/html/issues/869
1125 // This is where that change would have an effect, and it is measured to
1126 // verify the assumption that it's a very rare situation.
1127 UseCounter::Count(GetDocument(),
1128 WebFeature::kHTMLMediaElementLoadNetworkEmptyNotPaused);
1131 // 7 - Set the playbackRate attribute to the value of the defaultPlaybackRate
1133 setPlaybackRate(defaultPlaybackRate());
1135 // 8 - Set the error attribute to null and the can autoplay flag to true.
1137 can_autoplay_ = true;
1139 // 9 - Invoke the media element's resource selection algorithm.
1140 InvokeResourceSelectionAlgorithm();
1142 // 10 - Note: Playback of any previously playing media resource for this
1146 void HTMLMediaElement::InvokeResourceSelectionAlgorithm() {
1147 DVLOG(3) << "invokeResourceSelectionAlgorithm(" << *this << ")";
1148 // The resource selection algorithm
1149 // 1 - Set the networkState to NETWORK_NO_SOURCE
1150 SetNetworkState(kNetworkNoSource);
1152 // 2 - Set the element's show poster flag to true
1153 SetShowPosterFlag(true);
1155 played_time_ranges_ = MakeGarbageCollected<TimeRanges>();
1157 // FIXME: Investigate whether these can be moved into network_state_ !=
1158 // kNetworkEmpty block above
1159 // so they are closer to the relevant spec steps.
1160 last_seek_time_ = 0;
1161 duration_ = std::numeric_limits<double>::quiet_NaN();
1163 // 3 - Set the media element's delaying-the-load-event flag to true (this
1164 // delays the load event)
1165 SetShouldDelayLoadEvent(true);
1166 if (GetMediaControls() && isConnected())
1167 GetMediaControls()->Reset();
1169 // 4 - Await a stable state, allowing the task that invoked this algorithm to
1171 // TODO(srirama.m): Remove scheduleNextSourceChild() and post a microtask
1172 // instead. See http://crbug.com/593289 for more details.
1173 ScheduleNextSourceChild();
1176 void HTMLMediaElement::LoadInternal() {
1177 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose
1178 // mode was not in the disabled state when the element's resource selection
1179 // algorithm last started".
1180 text_tracks_when_resource_selection_began_.clear();
1182 for (unsigned i = 0; i < text_tracks_->length(); ++i) {
1183 TextTrack* track = text_tracks_->AnonymousIndexedGetter(i);
1184 if (track->mode() != TextTrackMode::kDisabled)
1185 text_tracks_when_resource_selection_began_.push_back(track);
1189 SelectMediaResource();
1192 void HTMLMediaElement::SelectMediaResource() {
1193 DVLOG(3) << "selectMediaResource(" << *this << ")";
1195 enum Mode { kObject, kAttribute, kChildren, kNothing };
1196 Mode mode = kNothing;
1198 // 6 - If the media element has an assigned media provider object, then let
1200 if (src_object_stream_descriptor_ || src_object_media_source_handle_) {
1202 } else if (FastHasAttribute(html_names::kSrcAttr)) {
1203 // Otherwise, if the media element has no assigned media provider object
1204 // but has a src attribute, then let mode be attribute.
1206 } else if (HTMLSourceElement* element =
1207 Traversal<HTMLSourceElement>::FirstChild(*this)) {
1208 // Otherwise, if the media element does not have an assigned media
1209 // provider object and does not have a src attribute, but does have a
1210 // source element child, then let mode be children and let candidate be
1211 // the first such source element child in tree order.
1213 next_child_node_to_consider_ = element;
1214 current_source_node_ = nullptr;
1216 // Otherwise the media element has no assigned media provider object and
1217 // has neither a src attribute nor a source element child: set the
1218 // networkState to kNetworkEmpty, and abort these steps; the synchronous
1220 // TODO(mlamouri): Setting the network state to empty implies that there
1221 // should be no |web_media_player_|. However, if a previous playback ended
1222 // due to an error, we can get here and still have one. Decide on a plan
1223 // to deal with this properly. https://crbug.com/789737
1224 load_state_ = kWaitingForSource;
1225 SetShouldDelayLoadEvent(false);
1226 if (!web_media_player_ || (ready_state_ < kHaveFutureData &&
1227 ready_state_maximum_ < kHaveFutureData)) {
1228 SetNetworkState(kNetworkEmpty);
1230 UseCounter::Count(GetDocument(),
1231 WebFeature::kHTMLMediaElementEmptyLoadWithFutureData);
1233 UpdateLayoutObject();
1235 DVLOG(3) << "selectMediaResource(" << *this << "), nothing to load";
1239 // 7 - Set the media element's networkState to NETWORK_LOADING.
1240 SetNetworkState(kNetworkLoading);
1242 // 8 - Queue a task to fire a simple event named loadstart at the media
1244 ScheduleEvent(event_type_names::kLoadstart);
1246 // 9 - Run the appropriate steps...
1249 LoadSourceFromObject();
1250 DVLOG(3) << "selectMediaResource(" << *this
1251 << ", using 'srcObject' attribute";
1254 LoadSourceFromAttribute();
1255 DVLOG(3) << "selectMediaResource(" << *this
1256 << "), using 'src' attribute url";
1259 LoadNextSourceChild();
1260 DVLOG(3) << "selectMediaResource(" << *this << "), using source element";
1267 void HTMLMediaElement::LoadSourceFromObject() {
1268 DCHECK(src_object_stream_descriptor_ || src_object_media_source_handle_);
1269 load_state_ = kLoadingFromSrcObject;
1271 if (src_object_media_source_handle_) {
1272 DCHECK(!src_object_stream_descriptor_);
1274 // Retrieve the internal blob URL from the handle that was created in the
1275 // context where the referenced MediaSource is owned, for the purposes of
1276 // using existing security and logging logic for loading media from a
1277 // MediaSource with a blob URL.
1278 const String media_source_handle_url_ =
1279 src_object_media_source_handle_->GetInternalBlobURL();
1280 DCHECK(!media_source_handle_url_.empty());
1282 KURL media_url = GetDocument().CompleteURL(media_source_handle_url_);
1283 if (!IsSafeToLoadURL(media_url, kComplain)) {
1285 WebMediaPlayer::kNetworkStateFormatError,
1286 BuildElementErrorMessage(
1287 "Media load from MediaSourceHandle rejected by safety check"));
1291 // No type is available when loading from a MediaSourceHandle, via
1292 // srcObject, even with an internal MediaSource blob URL.
1293 LoadResource(WebMediaPlayerSource(WebURL(media_url)), String());
1297 // No type is available when the resource comes from the 'srcObject'
1300 WebMediaPlayerSource(WebMediaStream(src_object_stream_descriptor_)),
1304 void HTMLMediaElement::LoadSourceFromAttribute() {
1305 load_state_ = kLoadingFromSrcAttr;
1306 const AtomicString& src_value = FastGetAttribute(html_names::kSrcAttr);
1308 // If the src attribute's value is the empty string ... jump down to the
1309 // failed step below
1310 if (src_value.empty()) {
1311 DVLOG(3) << "LoadSourceFromAttribute(" << *this << "), empty 'src'";
1312 MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
1313 BuildElementErrorMessage("Empty src attribute"));
1317 KURL media_url = GetDocument().CompleteURL(src_value);
1318 if (!IsSafeToLoadURL(media_url, kComplain)) {
1320 WebMediaPlayer::kNetworkStateFormatError,
1321 BuildElementErrorMessage("Media load rejected by URL safety check"));
1325 // No type is available when the url comes from the 'src' attribute so
1326 // MediaPlayer will have to pick a media engine based on the file extension.
1327 LoadResource(WebMediaPlayerSource(WebURL(media_url)), String());
1330 void HTMLMediaElement::LoadNextSourceChild() {
1331 String content_type;
1332 KURL media_url = SelectNextSourceChild(&content_type, kComplain);
1333 if (!media_url.IsValid()) {
1334 WaitForSourceChange();
1338 // Reset the MediaPlayer and MediaSource if any
1339 ResetMediaPlayerAndMediaSource();
1341 load_state_ = kLoadingFromSourceElement;
1342 LoadResource(WebMediaPlayerSource(WebURL(media_url)), content_type);
1345 void HTMLMediaElement::LoadResource(const WebMediaPlayerSource& source,
1346 const String& content_type) {
1347 DCHECK(IsMainThread());
1349 if (source.IsURL()) {
1350 url = source.GetAsURL();
1351 DCHECK(IsSafeToLoadURL(url, kComplain));
1352 DVLOG(3) << "loadResource(" << *this << ", " << UrlForLoggingMedia(url)
1353 << ", " << content_type << ")";
1356 LocalFrame* frame = GetDocument().GetFrame();
1358 MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
1359 BuildElementErrorMessage(
1360 "Resource load failure: document has no frame"));
1364 #if BUILDFLAG(IS_TIZEN_TV)
1365 ContentType cont_type(content_type);
1366 content_mime_type_ = cont_type.GetType().DeprecatedLower();
1369 // The resource fetch algorithm
1370 SetNetworkState(kNetworkLoading);
1372 // Set |current_src_| *before* changing to the cache url, the fact that we are
1373 // loading from the app cache is an internal detail not exposed through the
1374 // media element API. If loading from an internal MediaSourceHandle object
1375 // URL, then do not expose that URL to app, but instead hold it for use later
1376 // in StartPlayerLoad and elsewhere (for origin, security etc checks normally
1377 // done on |current_src_|.)
1378 if (src_object_media_source_handle_) {
1379 DCHECK(!url.IsEmpty());
1380 current_src_.SetSource(url,
1381 SourceMetadata::SourceVisibility::kInvisibleToApp);
1383 current_src_.SetSource(url,
1384 SourceMetadata::SourceVisibility::kVisibleToApp);
1387 // Default this to empty, so that we use |current_src_| unless the player
1388 // provides one later.
1389 current_src_after_redirects_ = KURL();
1391 if (audio_source_node_)
1392 audio_source_node_->OnCurrentSrcChanged(current_src_.GetSourceIfVisible());
1394 // Update remote playback client with the new src and consider it incompatible
1395 // until proved otherwise.
1396 RemotePlaybackCompatibilityChanged(current_src_.GetSourceIfVisible(), false);
1398 DVLOG(3) << "loadResource(" << *this << ") - current src if visible="
1399 << UrlForLoggingMedia(current_src_.GetSourceIfVisible())
1400 << ", current src =" << UrlForLoggingMedia(current_src_.GetSource())
1401 << ", src_object_media_source_handle_="
1402 << src_object_media_source_handle_
1403 << ", src_object_stream_descriptor_="
1404 << src_object_stream_descriptor_;
1406 StartProgressEventTimer();
1410 DCHECK(!media_source_attachment_);
1411 DCHECK(!media_source_tracer_);
1414 bool attempt_load = true;
1416 if (src_object_media_source_handle_) {
1417 media_source_attachment_ =
1418 src_object_media_source_handle_->TakeAttachment();
1420 // If the attachment is nullptr, then fail the load.
1421 if (!media_source_attachment_) {
1422 attempt_load = false;
1425 media_source_attachment_ =
1426 MediaSourceAttachment::LookupMediaSource(url.GetString());
1428 if (media_source_attachment_) {
1429 bool start_result = false;
1430 media_source_tracer_ =
1431 media_source_attachment_->StartAttachingToMediaElement(this,
1434 // If the associated feature is enabled, auto-revoke the MediaSource
1435 // object URL that was used for attachment on successful (start of)
1436 // attachment. This can help reduce memory bloat later if the app does not
1437 // revoke the object URL explicitly and the object URL was the only
1438 // remaining strong reference to an attached HTMLMediaElement+MediaSource
1439 // cycle of objects that could otherwise be garbage-collectable. Don't
1440 // auto-revoke the internal, unregistered, object URL used to attach via
1441 // srcObject with a MediaSourceHandle, though.
1442 if (base::FeatureList::IsEnabled(
1443 media::kRevokeMediaSourceObjectURLOnAttach) &&
1444 !src_object_media_source_handle_) {
1445 URLFileAPI::revokeObjectURL(GetExecutionContext(), url.GetString());
1448 // Forget our reference to the MediaSourceAttachment, so we leave it alone
1449 // while processing remainder of load failure.
1450 media_source_attachment_.reset();
1451 media_source_tracer_ = nullptr;
1452 attempt_load = false;
1456 bool can_load_resource =
1457 source.IsMediaStream() || CanLoadURL(url, content_type);
1458 if (attempt_load && can_load_resource) {
1459 DCHECK(!web_media_player_);
1461 // Conditionally defer the load if effective preload is 'none'.
1462 // Skip this optional deferral for MediaStream sources or any blob URL,
1463 // including MediaSource blob URLs.
1464 if (!source.IsMediaStream() && !url.ProtocolIs("blob") &&
1465 EffectivePreloadType() == WebMediaPlayer::kPreloadNone) {
1466 DVLOG(3) << "loadResource(" << *this
1467 << ") : Delaying load because preload == 'none'";
1474 WebMediaPlayer::kNetworkStateFormatError,
1475 BuildElementErrorMessage(attempt_load
1476 ? "Unable to load URL due to content type"
1477 : "Unable to attach MediaSource"));
1481 LocalFrame* HTMLMediaElement::LocalFrameForPlayer() {
1482 return opener_document_ ? opener_document_->GetFrame()
1483 : GetDocument().GetFrame();
1486 void HTMLMediaElement::StartPlayerLoad() {
1487 DCHECK(!web_media_player_);
1489 WebMediaPlayerSource source;
1490 if (src_object_stream_descriptor_) {
1492 WebMediaPlayerSource(WebMediaStream(src_object_stream_descriptor_));
1493 } else if (src_object_media_source_handle_) {
1494 DCHECK(current_src_.GetSourceIfVisible().IsEmpty());
1495 const KURL& internal_url = current_src_.GetSource();
1496 DCHECK(!internal_url.IsEmpty());
1498 source = WebMediaPlayerSource(WebURL(internal_url));
1500 // Filter out user:pass as those two URL components aren't
1501 // considered for media resource fetches (including for the CORS
1502 // use-credentials mode.) That behavior aligns with Gecko, with IE
1503 // being more restrictive and not allowing fetches to such URLs.
1505 // Spec reference: http://whatwg.org/c/#concept-media-load-resource
1507 // FIXME: when the HTML spec switches to specifying resource
1508 // fetches in terms of Fetch (http://fetch.spec.whatwg.org), and
1509 // along with that potentially also specifying a setting for its
1510 // 'authentication flag' to control how user:pass embedded in a
1511 // media resource URL should be treated, then update the handling
1513 KURL request_url = current_src_.GetSourceIfVisible();
1514 if (!request_url.User().empty())
1515 request_url.SetUser(String());
1516 if (!request_url.Pass().empty())
1517 request_url.SetPass(String());
1519 KURL kurl(request_url);
1520 source = WebMediaPlayerSource(WebURL(kurl));
1523 LocalFrame* frame = LocalFrameForPlayer();
1524 if (!frame || !GetExecutionContext()) {
1526 WebMediaPlayer::kNetworkStateFormatError,
1527 BuildElementErrorMessage("Player load failure: document has no frame"));
1532 frame->Client()->CreateWebMediaPlayer(*this, source, this);
1534 if (!web_media_player_) {
1535 MediaLoadingFailed(WebMediaPlayer::kNetworkStateFormatError,
1536 BuildElementErrorMessage(
1537 "Player load failure: error creating media player"));
1541 OnWebMediaPlayerCreated();
1543 // Setup the communication channels between the renderer and browser processes
1544 // via the MediaPlayer and MediaPlayerObserver mojo interfaces.
1545 DCHECK(media_player_receiver_set_->Value().empty());
1546 mojo::PendingAssociatedRemote<media::mojom::blink::MediaPlayer>
1547 media_player_remote;
1548 BindMediaPlayerReceiver(
1549 media_player_remote.InitWithNewEndpointAndPassReceiver());
1551 GetMediaPlayerHostRemote().OnMediaPlayerAdded(
1552 std::move(media_player_remote), AddMediaPlayerObserverAndPassReceiver(),
1553 web_media_player_->GetDelegateId());
1555 if (GetLayoutObject())
1556 GetLayoutObject()->SetShouldDoFullPaintInvalidation();
1557 // Make sure if we create/re-create the WebMediaPlayer that we update our
1559 audio_source_provider_.Wrap(web_media_player_->GetAudioSourceProvider());
1560 web_media_player_->SetVolume(EffectiveMediaVolume());
1562 web_media_player_->SetPoster(PosterImageURL());
1564 const auto preload = EffectivePreloadType();
1565 web_media_player_->SetPreload(preload);
1567 web_media_player_->RequestRemotePlaybackDisabled(
1568 FastHasAttribute(html_names::kDisableremoteplaybackAttr));
1570 bool is_cache_disabled = false;
1571 probe::IsCacheDisabled(GetDocument().GetExecutionContext(),
1572 &is_cache_disabled);
1573 auto load_timing = web_media_player_->Load(GetLoadType(), source, CorsMode(),
1575 if (load_timing == WebMediaPlayer::LoadTiming::kDeferred) {
1576 // Deferred media loading is not part of the spec, but intuition is that
1577 // this should not hold up the Window's "load" event (similar to user
1578 // gesture requirements).
1579 SetShouldDelayLoadEvent(false);
1583 web_media_player_->EnteredFullscreen();
1585 web_media_player_->SetLatencyHint(latencyHint());
1587 web_media_player_->SetPreservesPitch(preservesPitch());
1592 void HTMLMediaElement::SetPlayerPreload() {
1593 if (web_media_player_)
1594 web_media_player_->SetPreload(EffectivePreloadType());
1596 if (LoadIsDeferred() &&
1597 EffectivePreloadType() != WebMediaPlayer::kPreloadNone)
1598 StartDeferredLoad();
1601 bool HTMLMediaElement::LoadIsDeferred() const {
1602 return deferred_load_state_ != kNotDeferred;
1605 void HTMLMediaElement::DeferLoad() {
1606 // This implements the "optional" step 4 from the resource fetch algorithm
1607 // "If mode is remote".
1608 DCHECK(!deferred_load_timer_.IsActive());
1609 DCHECK_EQ(deferred_load_state_, kNotDeferred);
1610 // 1. Set the networkState to NETWORK_IDLE.
1611 // 2. Queue a task to fire a simple event named suspend at the element.
1612 ChangeNetworkStateFromLoadingToIdle();
1613 // 3. Queue a task to set the element's delaying-the-load-event
1614 // flag to false. This stops delaying the load event.
1615 deferred_load_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
1616 // 4. Wait for the task to be run.
1617 deferred_load_state_ = kWaitingForStopDelayingLoadEventTask;
1618 // Continued in executeDeferredLoad().
1621 void HTMLMediaElement::CancelDeferredLoad() {
1622 deferred_load_timer_.Stop();
1623 deferred_load_state_ = kNotDeferred;
1626 void HTMLMediaElement::ExecuteDeferredLoad() {
1627 DCHECK_GE(deferred_load_state_, kWaitingForTrigger);
1629 // resource fetch algorithm step 4 - continued from deferLoad().
1631 // 5. Wait for an implementation-defined event (e.g. the user requesting that
1632 // the media element begin playback). This is assumed to be whatever 'event'
1633 // ended up calling this method.
1634 CancelDeferredLoad();
1635 // 6. Set the element's delaying-the-load-event flag back to true (this
1636 // delays the load event again, in case it hasn't been fired yet).
1637 SetShouldDelayLoadEvent(true);
1638 // 7. Set the networkState to NETWORK_LOADING.
1639 SetNetworkState(kNetworkLoading);
1641 StartProgressEventTimer();
1646 void HTMLMediaElement::StartDeferredLoad() {
1647 if (deferred_load_state_ == kWaitingForTrigger) {
1648 ExecuteDeferredLoad();
1651 if (deferred_load_state_ == kExecuteOnStopDelayingLoadEventTask)
1653 DCHECK_EQ(deferred_load_state_, kWaitingForStopDelayingLoadEventTask);
1654 deferred_load_state_ = kExecuteOnStopDelayingLoadEventTask;
1657 void HTMLMediaElement::DeferredLoadTimerFired(TimerBase*) {
1658 SetShouldDelayLoadEvent(false);
1660 if (deferred_load_state_ == kExecuteOnStopDelayingLoadEventTask) {
1661 ExecuteDeferredLoad();
1664 DCHECK_EQ(deferred_load_state_, kWaitingForStopDelayingLoadEventTask);
1665 deferred_load_state_ = kWaitingForTrigger;
1668 WebMediaPlayer::LoadType HTMLMediaElement::GetLoadType() const {
1669 if (media_source_attachment_)
1670 return WebMediaPlayer::kLoadTypeMediaSource; // Either via src or srcObject
1672 if (src_object_stream_descriptor_)
1673 return WebMediaPlayer::kLoadTypeMediaStream;
1675 return WebMediaPlayer::kLoadTypeURL;
1678 bool HTMLMediaElement::PausedWhenVisible() const {
1679 return paused_ && web_media_player_ && !web_media_player_->PausedWhenHidden();
1682 void HTMLMediaElement::DidAudioOutputSinkChanged(
1683 const String& hashed_device_id) {
1684 for (auto& observer : media_player_observer_remote_set_->Value())
1685 observer->OnAudioOutputSinkChanged(hashed_device_id);
1688 void HTMLMediaElement::SetMediaPlayerHostForTesting(
1689 mojo::PendingAssociatedRemote<media::mojom::blink::MediaPlayerHost> host) {
1690 media_player_host_remote_->Value().Bind(
1691 std::move(host), GetDocument().GetTaskRunner(TaskType::kInternalMedia));
1694 bool HTMLMediaElement::TextTracksAreReady() const {
1695 // 4.8.12.11.1 Text track model
1697 // The text tracks of a media element are ready if all the text tracks whose
1698 // mode was not in the disabled state when the element's resource selection
1699 // algorithm last started now have a text track readiness state of loaded or
1701 for (const auto& text_track : text_tracks_when_resource_selection_began_) {
1702 if (text_track->GetReadinessState() == TextTrack::kLoading ||
1703 text_track->GetReadinessState() == TextTrack::kNotLoaded)
1710 void HTMLMediaElement::TextTrackReadyStateChanged(TextTrack* track) {
1711 if (web_media_player_ &&
1712 text_tracks_when_resource_selection_began_.Contains(track)) {
1713 if (track->GetReadinessState() != TextTrack::kLoading) {
1715 static_cast<ReadyState>(web_media_player_->GetReadyState()));
1718 // The track readiness state might have changed as a result of the user
1719 // clicking the captions button. In this case, a check whether all the
1720 // resources have failed loading should be done in order to hide the CC
1722 // TODO(mlamouri): when an HTMLTrackElement fails to load, it is not
1723 // propagated to the TextTrack object in a web exposed fashion. We have to
1724 // keep relying on a custom glue to the controls while this is taken care
1725 // of on the web side. See https://crbug.com/669977
1726 if (GetMediaControls() &&
1727 track->GetReadinessState() == TextTrack::kFailedToLoad) {
1728 GetMediaControls()->OnTrackElementFailedToLoad();
1733 void HTMLMediaElement::TextTrackModeChanged(TextTrack* track) {
1734 // Mark this track as "configured" so configureTextTracks won't change the
1736 if (IsA<LoadableTextTrack>(track))
1737 track->SetHasBeenConfigured(true);
1739 if (track->IsRendered()) {
1740 GetDocument().GetStyleEngine().AddTextTrack(track);
1742 GetDocument().GetStyleEngine().RemoveTextTrack(track);
1745 ConfigureTextTrackDisplay();
1747 DCHECK(textTracks()->Contains(track));
1748 textTracks()->ScheduleChangeEvent();
1751 void HTMLMediaElement::DisableAutomaticTextTrackSelection() {
1752 should_perform_automatic_track_selection_ = false;
1755 bool HTMLMediaElement::IsSafeToLoadURL(const KURL& url,
1756 InvalidURLAction action_if_invalid) {
1757 if (!url.IsValid()) {
1758 DVLOG(3) << "isSafeToLoadURL(" << *this << ", " << UrlForLoggingMedia(url)
1759 << ") -> FALSE because url is invalid";
1763 LocalDOMWindow* window = GetDocument().domWindow();
1764 if (!window || !window->GetSecurityOrigin()->CanDisplay(url)) {
1765 if (action_if_invalid == kComplain) {
1766 GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
1767 mojom::ConsoleMessageSource::kSecurity,
1768 mojom::ConsoleMessageLevel::kError,
1769 "Not allowed to load local resource: " + url.ElidedString()));
1771 DVLOG(3) << "isSafeToLoadURL(" << *this << ", " << UrlForLoggingMedia(url)
1772 << ") -> FALSE rejected by SecurityOrigin";
1776 if (!GetExecutionContext()->GetContentSecurityPolicy()->AllowMediaFromSource(
1778 DVLOG(3) << "isSafeToLoadURL(" << *this << ", " << UrlForLoggingMedia(url)
1779 << ") -> rejected by Content Security Policy";
1786 bool HTMLMediaElement::IsMediaDataCorsSameOrigin() const {
1787 if (!web_media_player_)
1790 const auto network_state = web_media_player_->GetNetworkState();
1791 if (network_state == WebMediaPlayer::kNetworkStateNetworkError)
1794 return !web_media_player_->WouldTaintOrigin();
1797 void HTMLMediaElement::StartProgressEventTimer() {
1798 if (progress_event_timer_.IsActive())
1801 previous_progress_time_ = base::ElapsedTimer();
1802 // 350ms is not magic, it is in the spec!
1803 progress_event_timer_.StartRepeating(base::Milliseconds(350));
1806 void HTMLMediaElement::WaitForSourceChange() {
1807 DVLOG(3) << "waitForSourceChange(" << *this << ")";
1809 StopPeriodicTimers();
1810 load_state_ = kWaitingForSource;
1812 // 17 - Waiting: Set the element's networkState attribute to the
1813 // NETWORK_NO_SOURCE value
1814 SetNetworkState(kNetworkNoSource);
1816 // 18 - Set the element's show poster flag to true.
1817 SetShowPosterFlag(true);
1819 // 19 - Set the element's delaying-the-load-event flag to false. This stops
1820 // delaying the load event.
1821 SetShouldDelayLoadEvent(false);
1823 UpdateLayoutObject();
1826 void HTMLMediaElement::NoneSupported(const String& input_message) {
1827 DVLOG(3) << "NoneSupported(" << *this << ", message='" << input_message
1830 StopPeriodicTimers();
1831 load_state_ = kWaitingForSource;
1832 current_source_node_ = nullptr;
1834 String empty_string;
1835 const String& message = MediaShouldBeOpaque() ? empty_string : input_message;
1838 // The dedicated media source failure steps are the following steps:
1840 // 1 - Set the error attribute to a new MediaError object whose code attribute
1841 // is set to MEDIA_ERR_SRC_NOT_SUPPORTED.
1842 SetError(MakeGarbageCollected<MediaError>(
1843 MediaError::kMediaErrSrcNotSupported, message));
1845 // 2 - Forget the media element's media-resource-specific text tracks.
1846 ForgetResourceSpecificTracks();
1848 // 3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE
1850 SetNetworkState(kNetworkNoSource);
1852 // 4 - Set the element's show poster flag to true.
1853 SetShowPosterFlag(true);
1855 // 5 - Fire a simple event named error at the media element.
1856 ScheduleEvent(event_type_names::kError);
1858 // 6 - Reject pending play promises with NotSupportedError.
1859 ScheduleRejectPlayPromises(PlayPromiseError::kNotSupported);
1863 // 7 - Set the element's delaying-the-load-event flag to false. This stops
1864 // delaying the load event.
1865 SetShouldDelayLoadEvent(false);
1867 UpdateLayoutObject();
1870 void HTMLMediaElement::MediaEngineError(MediaError* err) {
1871 DCHECK_GE(ready_state_, kHaveMetadata);
1872 DVLOG(3) << "mediaEngineError(" << *this << ", "
1873 << static_cast<int>(err->code()) << ")";
1875 // 1 - The user agent should cancel the fetching process.
1876 StopPeriodicTimers();
1877 load_state_ = kWaitingForSource;
1879 // 2 - Set the error attribute to a new MediaError object whose code attribute
1880 // is set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1883 // 3 - Queue a task to fire a simple event named error at the media element.
1884 ScheduleEvent(event_type_names::kError);
1886 // 4 - Set the element's networkState attribute to the NETWORK_IDLE value.
1887 SetNetworkState(kNetworkIdle);
1889 // 5 - Set the element's delaying-the-load-event flag to false. This stops
1890 // delaying the load event.
1891 SetShouldDelayLoadEvent(false);
1893 // 6 - Abort the overall resource selection algorithm.
1894 current_source_node_ = nullptr;
1897 void HTMLMediaElement::CancelPendingEventsAndCallbacks() {
1898 DVLOG(3) << "cancelPendingEventsAndCallbacks(" << *this << ")";
1899 async_event_queue_->CancelAllEvents();
1901 for (HTMLSourceElement* source =
1902 Traversal<HTMLSourceElement>::FirstChild(*this);
1903 source; source = Traversal<HTMLSourceElement>::NextSibling(*source))
1904 source->CancelPendingErrorEvent();
1907 void HTMLMediaElement::NetworkStateChanged() {
1908 SetNetworkState(web_media_player_->GetNetworkState());
1911 void HTMLMediaElement::MediaLoadingFailed(WebMediaPlayer::NetworkState error,
1912 const String& input_message) {
1913 DVLOG(3) << "MediaLoadingFailed(" << *this << ", " << int{error}
1914 << ", message='" << input_message << "')";
1916 bool should_be_opaque = MediaShouldBeOpaque();
1917 if (should_be_opaque)
1918 error = WebMediaPlayer::kNetworkStateNetworkError;
1919 String empty_string;
1920 const String& message = should_be_opaque ? empty_string : input_message;
1922 StopPeriodicTimers();
1924 // If we failed while trying to load a <source> element, the movie was never
1925 // parsed, and there are more <source> children, schedule the next one
1926 if (ready_state_ < kHaveMetadata &&
1927 load_state_ == kLoadingFromSourceElement) {
1928 // resource selection algorithm
1929 // Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM
1930 // manipulation task source, to fire a simple event named error at the
1931 // candidate element.
1932 if (current_source_node_) {
1933 current_source_node_->ScheduleErrorEvent();
1935 DVLOG(3) << "mediaLoadingFailed(" << *this
1936 << ") - error event not sent, <source> was removed";
1939 // 9.Otherwise.10 - Asynchronously await a stable state. The synchronous
1940 // section consists of all the remaining steps of this algorithm until the
1941 // algorithm says the synchronous section has ended.
1943 // 9.Otherwise.11 - Forget the media element's media-resource-specific
1945 ForgetResourceSpecificTracks();
1947 if (HavePotentialSourceChild()) {
1948 DVLOG(3) << "mediaLoadingFailed(" << *this
1949 << ") - scheduling next <source>";
1950 ScheduleNextSourceChild();
1952 DVLOG(3) << "mediaLoadingFailed(" << *this
1953 << ") - no more <source> elements, waiting";
1954 WaitForSourceChange();
1960 if (error == WebMediaPlayer::kNetworkStateNetworkError &&
1961 ready_state_ >= kHaveMetadata) {
1962 MediaEngineError(MakeGarbageCollected<MediaError>(
1963 MediaError::kMediaErrNetwork, message));
1964 } else if (error == WebMediaPlayer::kNetworkStateDecodeError) {
1966 MakeGarbageCollected<MediaError>(MediaError::kMediaErrDecode, message));
1967 } else if ((error == WebMediaPlayer::kNetworkStateFormatError ||
1968 error == WebMediaPlayer::kNetworkStateNetworkError) &&
1969 (load_state_ == kLoadingFromSrcAttr ||
1970 (load_state_ == kLoadingFromSrcObject &&
1971 src_object_media_source_handle_))) {
1972 if (message.empty()) {
1973 // Generate a more meaningful error message to differentiate the two types
1974 // of MEDIA_SRC_ERR_NOT_SUPPORTED.
1975 NoneSupported(BuildElementErrorMessage(
1976 error == WebMediaPlayer::kNetworkStateFormatError ? "Format error"
1977 : "Network error"));
1979 NoneSupported(message);
1983 UpdateLayoutObject();
1986 void HTMLMediaElement::SetNetworkState(WebMediaPlayer::NetworkState state) {
1987 DVLOG(3) << "setNetworkState(" << *this << ", " << static_cast<int>(state)
1988 << ") - current state is " << int{network_state_};
1990 if (state == WebMediaPlayer::kNetworkStateEmpty) {
1991 // Just update the cached state and leave, we can't do anything.
1992 SetNetworkState(kNetworkEmpty);
1996 if (state == WebMediaPlayer::kNetworkStateFormatError ||
1997 state == WebMediaPlayer::kNetworkStateNetworkError ||
1998 state == WebMediaPlayer::kNetworkStateDecodeError) {
1999 MediaLoadingFailed(state, web_media_player_->GetErrorMessage());
2003 if (state == WebMediaPlayer::kNetworkStateIdle) {
2004 if (network_state_ > kNetworkIdle) {
2005 ChangeNetworkStateFromLoadingToIdle();
2007 SetNetworkState(kNetworkIdle);
2011 if (state == WebMediaPlayer::kNetworkStateLoading) {
2012 if (network_state_ < kNetworkLoading || network_state_ == kNetworkNoSource)
2013 StartProgressEventTimer();
2014 SetNetworkState(kNetworkLoading);
2017 if (state == WebMediaPlayer::kNetworkStateLoaded) {
2018 if (network_state_ != kNetworkIdle)
2019 ChangeNetworkStateFromLoadingToIdle();
2023 void HTMLMediaElement::ChangeNetworkStateFromLoadingToIdle() {
2024 progress_event_timer_.Stop();
2026 if (!MediaShouldBeOpaque()) {
2027 // Schedule one last progress event so we guarantee that at least one is
2028 // fired for files that load very quickly.
2029 if (web_media_player_ && web_media_player_->DidLoadingProgress())
2030 ScheduleEvent(event_type_names::kProgress);
2031 ScheduleEvent(event_type_names::kSuspend);
2032 SetNetworkState(kNetworkIdle);
2034 // TODO(dalecurtis): Replace c-style casts in follow up patch.
2035 DVLOG(1) << __func__ << "(" << *this
2036 << ") - Deferred network state change to idle for opaque media";
2040 void HTMLMediaElement::ReadyStateChanged() {
2041 SetReadyState(static_cast<ReadyState>(web_media_player_->GetReadyState()));
2044 void HTMLMediaElement::SetReadyState(ReadyState state) {
2045 DVLOG(3) << "setReadyState(" << *this << ", " << int{state}
2046 << ") - current state is " << int{ready_state_};
2048 // Set "wasPotentiallyPlaying" BEFORE updating ready_state_,
2049 // potentiallyPlaying() uses it
2050 bool was_potentially_playing = PotentiallyPlaying();
2052 ReadyState old_state = ready_state_;
2053 ReadyState new_state = state;
2055 bool tracks_are_ready = TextTracksAreReady();
2057 if (new_state == old_state && tracks_are_ready_ == tracks_are_ready)
2060 tracks_are_ready_ = tracks_are_ready;
2062 if (tracks_are_ready) {
2063 ready_state_ = new_state;
2065 // If a media file has text tracks the readyState may not progress beyond
2066 // kHaveFutureData until the text tracks are ready, regardless of the state
2067 // of the media file.
2068 if (new_state <= kHaveMetadata)
2069 ready_state_ = new_state;
2071 ready_state_ = kHaveCurrentData;
2074 // If we're transitioning to / past kHaveMetadata, then cache the final URL.
2075 if (old_state < kHaveMetadata && new_state >= kHaveMetadata &&
2076 web_media_player_) {
2077 current_src_after_redirects_ =
2078 KURL(web_media_player_->GetSrcAfterRedirects());
2080 // Sometimes WebMediaPlayer may load a URL from an in memory cache, which
2081 // skips notification of insecure content. Ensure we always notify the
2082 // MixedContentChecker of what happened, even if the load was skipped.
2083 if (LocalFrame* frame = GetDocument().GetFrame()) {
2084 const KURL& current_src_for_check = current_src_.GetSource();
2085 // We don't care about the return value here. The MixedContentChecker will
2086 // internally notify for insecure content if it needs to regardless of
2087 // what the return value ends up being for this call.
2088 MixedContentChecker::ShouldBlockFetch(
2090 HasVideo() ? mojom::blink::RequestContextType::VIDEO
2091 : mojom::blink::RequestContextType::AUDIO,
2092 network::mojom::blink::IPAddressSpace::kUnknown,
2093 current_src_for_check,
2094 // Strictly speaking, this check is an approximation; a request could
2095 // have have redirected back to its original URL, for example.
2096 // However, the redirect status is only used to prevent leaking
2097 // information cross-origin via CSP reports, so comparing URLs is
2098 // sufficient for that purpose.
2099 current_src_after_redirects_ == current_src_for_check
2100 ? ResourceRequest::RedirectStatus::kNoRedirect
2101 : ResourceRequest::RedirectStatus::kFollowedRedirect,
2102 current_src_after_redirects_, /* devtools_id= */ String(),
2103 ReportingDisposition::kReport,
2104 GetDocument().Loader()->GetContentSecurityNotifier());
2107 // Prior to kHaveMetadata |network_state_| may be inaccurate to avoid side
2108 // channel leaks. This be a no-op if nothing has changed.
2109 NetworkStateChanged();
2112 if (new_state > ready_state_maximum_)
2113 ready_state_maximum_ = new_state;
2115 if (network_state_ == kNetworkEmpty)
2119 // 4.8.12.9, step 9 note: If the media element was potentially playing
2120 // immediately before it started seeking, but seeking caused its readyState
2121 // attribute to change to a value lower than kHaveFutureData, then a waiting
2122 // will be fired at the element.
2123 if (was_potentially_playing && ready_state_ < kHaveFutureData)
2124 ScheduleEvent(event_type_names::kWaiting);
2126 // 4.8.12.9 steps 12-14
2127 if (ready_state_ >= kHaveCurrentData)
2130 if (was_potentially_playing && ready_state_ < kHaveFutureData) {
2131 // Force an update to official playback position. Automatic updates from
2132 // currentPlaybackPosition() will be blocked while ready_state_ remains
2133 // < kHaveFutureData. This blocking is desired after 'waiting' has been
2134 // fired, but its good to update it one final time to accurately reflect
2135 // media time at the moment we ran out of data to play.
2136 SetOfficialPlaybackPosition(CurrentPlaybackPosition());
2139 ScheduleTimeupdateEvent(false);
2140 ScheduleEvent(event_type_names::kWaiting);
2144 // Once enough of the media data has been fetched to determine the duration of
2145 // the media resource, its dimensions, and other metadata...
2146 if (ready_state_ >= kHaveMetadata && old_state < kHaveMetadata) {
2147 CreatePlaceholderTracksIfNecessary();
2149 MediaFragmentURIParser fragment_parser(current_src_.GetSource());
2150 fragment_end_time_ = fragment_parser.EndTime();
2152 // Set the current playback position and the official playback position to
2153 // the earliest possible position.
2154 SetOfficialPlaybackPosition(EarliestPossiblePosition());
2156 duration_ = web_media_player_->Duration();
2157 ScheduleEvent(event_type_names::kDurationchange);
2159 if (IsHTMLVideoElement())
2160 ScheduleEvent(event_type_names::kResize);
2161 ScheduleEvent(event_type_names::kLoadedmetadata);
2163 bool jumped = false;
2164 if (default_playback_start_position_ > 0) {
2165 Seek(default_playback_start_position_);
2168 default_playback_start_position_ = 0;
2170 double initial_playback_position = fragment_parser.StartTime();
2171 if (std::isnan(initial_playback_position))
2172 initial_playback_position = 0;
2174 if (!jumped && initial_playback_position > 0) {
2175 UseCounter::Count(GetDocument(),
2176 WebFeature::kHTMLMediaElementSeekToFragmentStart);
2177 Seek(initial_playback_position);
2181 UpdateLayoutObject();
2184 bool is_potentially_playing = PotentiallyPlaying();
2185 if (ready_state_ >= kHaveCurrentData && old_state < kHaveCurrentData &&
2186 !have_fired_loaded_data_) {
2187 // Force an update to official playback position to catch non-zero start
2188 // times that were not known at kHaveMetadata, but are known now that the
2189 // first packets have been demuxed.
2190 SetOfficialPlaybackPosition(CurrentPlaybackPosition());
2192 have_fired_loaded_data_ = true;
2193 ScheduleEvent(event_type_names::kLoadeddata);
2194 SetShouldDelayLoadEvent(false);
2199 if (ready_state_ == kHaveFutureData && old_state <= kHaveCurrentData &&
2201 ScheduleEvent(event_type_names::kCanplay);
2202 if (is_potentially_playing)
2203 ScheduleNotifyPlaying();
2205 // for CASD url, when play, Webmedia pass the CASD url to hbbtv and
2206 // waiting for hbbtv parse and notify Webmedia the translated url when
2207 // translated url was set, replace the video tag src and cause the
2208 // previous MediaPlayerBridgeCapi destroy and then reload the new url So
2209 // when the translated url preload complete, we should call "play"
2210 // manually to make play continue
2211 #if BUILDFLAG(IS_TIZEN_TV)
2212 if (IsHbbTV() && is_translated_url_) {
2213 LOG(INFO) << "this is translated url,call play manually";
2214 is_translated_url_ = false;
2220 if (ready_state_ == kHaveEnoughData && old_state < kHaveEnoughData &&
2222 if (old_state <= kHaveCurrentData) {
2223 ScheduleEvent(event_type_names::kCanplay);
2224 if (is_potentially_playing)
2225 ScheduleNotifyPlaying();
2228 if (autoplay_policy_->RequestAutoplayByAttribute()) {
2230 SetShowPosterFlag(false);
2231 GetCueTimeline().InvokeTimeMarchesOn();
2232 ScheduleEvent(event_type_names::kPlay);
2233 ScheduleNotifyPlaying();
2234 can_autoplay_ = false;
2237 ScheduleEvent(event_type_names::kCanplaythrough);
2243 void HTMLMediaElement::SetShowPosterFlag(bool value) {
2244 DVLOG(3) << "SetShowPosterFlag(" << *this << ", " << value
2245 << ") - current state is " << show_poster_flag_;
2247 if (value == show_poster_flag_)
2250 show_poster_flag_ = value;
2251 UpdateLayoutObject();
2254 void HTMLMediaElement::UpdateLayoutObject() {
2255 if (GetLayoutObject())
2256 GetLayoutObject()->UpdateFromElement();
2259 void HTMLMediaElement::ProgressEventTimerFired() {
2260 // The spec doesn't require to dispatch the "progress" or "stalled" events
2261 // when the resource fetch mode is "local".
2262 // https://html.spec.whatwg.org/multipage/media.html#concept-media-load-resource
2263 // The mode is "local" for these sources:
2265 // MediaStream: The timer is stopped below to prevent the "progress" event
2266 // from being dispatched more than once. It is dispatched once to match
2267 // Safari's behavior, even though that's not required by the spec.
2269 // MediaSource: The "stalled" event is not dispatched but a conscious decision
2270 // was made to periodically dispatch the "progress" event to allow updates to
2271 // buffering UIs. Therefore, the timer is not stopped below.
2272 // https://groups.google.com/a/chromium.org/g/media-dev/c/Y8ITyIFmUC0/m/avBYOy_UFwAJ
2273 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream) {
2274 progress_event_timer_.Stop();
2277 if (network_state_ != kNetworkLoading) {
2278 RecordProgressEventTimerState(ProgressEventTimerState::kNotLoading);
2282 // If this is an cross-origin request, and we haven't discovered whether
2283 // the media is actually playable yet, don't fire any progress events as
2284 // those may let the page know information about the resource that it's
2285 // not supposed to know.
2286 if (MediaShouldBeOpaque()) {
2287 RecordProgressEventTimerState(
2288 ProgressEventTimerState::kMediaShouldBeOpaque);
2292 DCHECK(previous_progress_time_);
2294 if (web_media_player_ && web_media_player_->DidLoadingProgress()) {
2295 ScheduleEvent(event_type_names::kProgress);
2296 previous_progress_time_ = base::ElapsedTimer();
2297 sent_stalled_event_ = false;
2298 UpdateLayoutObject();
2299 RecordProgressEventTimerState(ProgressEventTimerState::kProgress);
2300 } else if (media_source_attachment_) {
2301 RecordProgressEventTimerState(
2302 ProgressEventTimerState::kHasMediaSourceAttachment);
2303 } else if (previous_progress_time_->Elapsed() <=
2304 kStalledNotificationInterval) {
2305 RecordProgressEventTimerState(ProgressEventTimerState::kRecentProgress);
2306 } else if (sent_stalled_event_) {
2307 RecordProgressEventTimerState(
2308 ProgressEventTimerState::kStalledEventAlreadyScheduled);
2310 // Note the !media_source_attachment_ condition above. The 'stalled' event
2311 // is not fired when using MSE. MSE's resource is considered 'local' (we
2312 // don't manage the download - the app does), so the HTML5 spec text around
2313 // 'stalled' does not apply. See discussion in https://crbug.com/517240 We
2314 // also don't need to take any action wrt delaying-the-load-event.
2315 // MediaSource disables the delayed load when first attached.
2316 ScheduleEvent(event_type_names::kStalled);
2317 sent_stalled_event_ = true;
2318 SetShouldDelayLoadEvent(false);
2319 RecordProgressEventTimerState(ProgressEventTimerState::kStalled);
2323 void HTMLMediaElement::AddPlayedRange(double start, double end) {
2324 DVLOG(3) << "addPlayedRange(" << *this << ", " << start << ", " << end << ")";
2325 if (!played_time_ranges_)
2326 played_time_ranges_ = MakeGarbageCollected<TimeRanges>();
2327 played_time_ranges_->Add(start, end);
2330 bool HTMLMediaElement::SupportsSave() const {
2331 // Check if download is disabled per settings.
2332 if (GetDocument().GetSettings() &&
2333 GetDocument().GetSettings()->GetHideDownloadUI()) {
2337 // Get the URL that we'll use for downloading.
2338 const KURL url = downloadURL();
2340 // URLs that lead to nowhere are ignored.
2341 if (url.IsNull() || url.IsEmpty())
2344 // If we have no source, we can't download.
2345 if (network_state_ == kNetworkEmpty || network_state_ == kNetworkNoSource)
2348 // It is not useful to offer a save feature on local files.
2349 if (url.IsLocalFile())
2352 // MediaStream can't be downloaded.
2353 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2356 // MediaSource can't be downloaded.
2357 if (HasMediaSource())
2360 // HLS stream shouldn't have a download button.
2364 // Infinite streams don't have a clear end at which to finish the download.
2365 if (duration() == std::numeric_limits<double>::infinity())
2371 bool HTMLMediaElement::SupportsLoop() const {
2372 // MediaStream can't be looped.
2373 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2376 // Infinite streams don't have a clear end at which to loop.
2377 if (duration() == std::numeric_limits<double>::infinity())
2383 void HTMLMediaElement::SetIgnorePreloadNone() {
2384 DVLOG(3) << "setIgnorePreloadNone(" << *this << ")";
2385 ignore_preload_none_ = true;
2389 void HTMLMediaElement::Seek(double time) {
2390 DVLOG(2) << "seek(" << *this << ", " << time << ")";
2392 // 1 - Set the media element's show poster flag to false.
2393 SetShowPosterFlag(false);
2395 // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
2396 // FIXME: remove web_media_player_ check once we figure out how
2397 // web_media_player_ is going out of sync with readystate.
2398 // web_media_player_ is cleared but readystate is not set to HAVE_NOTHING.
2399 if (!web_media_player_ || ready_state_ == kHaveNothing)
2402 // Ignore preload none and start load if necessary.
2403 SetIgnorePreloadNone();
2405 // Get the current time before setting seeking_, last_seek_time_ is returned
2407 double now = currentTime();
2409 // 3 - If the element's seeking IDL attribute is true, then another instance
2410 // of this algorithm is already running. Abort that other instance of the
2411 // algorithm without waiting for the step that it is running to complete.
2412 // Nothing specific to be done here.
2414 // 4 - Set the seeking IDL attribute to true.
2415 // The flag will be cleared when the engine tells us the time has actually
2419 // 6 - If the new playback position is later than the end of the media
2420 // resource, then let it be the end of the media resource instead.
2421 time = std::min(time, duration());
2423 // 7 - If the new playback position is less than the earliest possible
2424 // position, let it be that position instead.
2425 time = std::max(time, EarliestPossiblePosition());
2427 // Ask the media engine for the time value in the movie's time scale before
2428 // comparing with current time. This is necessary because if the seek time is
2429 // not equal to currentTime but the delta is less than the movie's time scale,
2430 // we will ask the media engine to "seek" to the current movie time, which may
2431 // be a noop and not generate a timechanged callback. This means seeking_
2432 // will never be cleared and we will never fire a 'seeked' event.
2433 double media_time = web_media_player_->MediaTimeForTimeValue(time);
2434 if (time != media_time) {
2435 DVLOG(3) << "seek(" << *this << ", " << time
2436 << ") - media timeline equivalent is " << media_time;
2440 // 8 - If the (possibly now changed) new playback position is not in one of
2441 // the ranges given in the seekable attribute, then let it be the position in
2442 // one of the ranges given in the seekable attribute that is the nearest to
2443 // the new playback position. ... If there are no ranges given in the seekable
2444 // attribute then set the seeking IDL attribute to false and abort these
2446 WebTimeRanges seekable_ranges = SeekableInternal();
2448 if (seekable_ranges.empty()) {
2452 time = seekable_ranges.Nearest(time, now);
2454 if (playing_ && last_seek_time_ < now)
2455 AddPlayedRange(last_seek_time_, now);
2457 last_seek_time_ = time;
2459 // 10 - Queue a task to fire a simple event named seeking at the element.
2460 ScheduleEvent(event_type_names::kSeeking);
2462 // 11 - Set the current playback position to the given new playback position.
2463 web_media_player_->Seek(time);
2464 web_media_player_->OnTimeUpdate();
2466 // 14-17 are handled, if necessary, when the engine signals a readystate
2467 // change or otherwise satisfies seek completion and signals a time change.
2470 void HTMLMediaElement::FinishSeek() {
2471 DVLOG(3) << "finishSeek(" << *this << ")";
2473 // 14 - Set the seeking IDL attribute to false.
2476 // Force an update to officialPlaybackPosition. Periodic updates generally
2477 // handle this, but may be skipped paused or waiting for data.
2478 SetOfficialPlaybackPosition(CurrentPlaybackPosition());
2480 // 15 - Run the time marches on steps.
2481 GetCueTimeline().InvokeTimeMarchesOn();
2483 // 16 - Queue a task to fire a simple event named timeupdate at the element.
2484 ScheduleTimeupdateEvent(false);
2486 // 17 - Queue a task to fire a simple event named seeked at the element.
2487 ScheduleEvent(event_type_names::kSeeked);
2490 HTMLMediaElement::ReadyState HTMLMediaElement::getReadyState() const {
2491 return ready_state_;
2494 bool HTMLMediaElement::HasVideo() const {
2495 return web_media_player_ && web_media_player_->HasVideo();
2498 bool HTMLMediaElement::HasAudio() const {
2499 return web_media_player_ && web_media_player_->HasAudio();
2502 bool HTMLMediaElement::IsEncrypted() const {
2503 return is_encrypted_media_;
2506 bool HTMLMediaElement::seeking() const {
2510 // https://www.w3.org/TR/html51/semantics-embedded-content.html#earliest-possible-position
2511 // The earliest possible position is not explicitly exposed in the API; it
2512 // corresponds to the start time of the first range in the seekable attribute’s
2513 // TimeRanges object, if any, or the current playback position otherwise.
2514 double HTMLMediaElement::EarliestPossiblePosition() const {
2515 WebTimeRanges seekable_ranges = SeekableInternal();
2516 if (!seekable_ranges.empty())
2517 return seekable_ranges.front().start;
2519 return CurrentPlaybackPosition();
2522 double HTMLMediaElement::CurrentPlaybackPosition() const {
2523 // "Official" playback position won't take updates from "current" playback
2524 // position until ready_state_ > kHaveMetadata, but other callers (e.g.
2525 // pauseInternal) may still request currentPlaybackPosition at any time.
2526 // From spec: "Media elements have a current playback position, which must
2527 // initially (i.e., in the absence of media data) be zero seconds."
2528 if (ready_state_ == kHaveNothing)
2531 if (web_media_player_)
2532 return web_media_player_->CurrentTime();
2534 if (ready_state_ >= kHaveMetadata) {
2535 DVLOG(3) << __func__ << " readyState = " << ready_state_
2536 << " but no webMediaPlayer to provide currentPlaybackPosition";
2542 double HTMLMediaElement::OfficialPlaybackPosition() const {
2543 // Hold updates to official playback position while paused or waiting for more
2544 // data. The underlying media player may continue to make small advances in
2545 // currentTime (e.g. as samples in the last rendered audio buffer are played
2546 // played out), but advancing currentTime while paused/waiting sends a mixed
2547 // signal about the state of playback.
2548 bool waiting_for_data = ready_state_ <= kHaveCurrentData;
2549 if (official_playback_position_needs_update_ && !paused_ &&
2550 !waiting_for_data) {
2551 SetOfficialPlaybackPosition(CurrentPlaybackPosition());
2554 #if LOG_OFFICIAL_TIME_STATUS
2555 static const double kMinCachedDeltaForWarning = 0.01;
2557 std::abs(official_playback_position_ - CurrentPlaybackPosition());
2558 if (delta > kMinCachedDeltaForWarning) {
2559 DVLOG(3) << "CurrentTime(" << (void*)this << ") - WARNING, cached time is "
2560 << delta << "seconds off of media time when paused/waiting";
2564 return official_playback_position_;
2567 void HTMLMediaElement::SetOfficialPlaybackPosition(double position) const {
2568 #if LOG_OFFICIAL_TIME_STATUS
2569 DVLOG(3) << "SetOfficialPlaybackPosition(" << (void*)this
2570 << ") was:" << official_playback_position_ << " now:" << position;
2573 // Internal player position may advance slightly beyond duration because
2574 // many files use imprecise duration. Clamp official position to duration when
2575 // known. Duration may be unknown when readyState < HAVE_METADATA.
2576 official_playback_position_ =
2577 std::isnan(duration()) ? position : std::min(duration(), position);
2579 if (official_playback_position_ != position) {
2580 DVLOG(3) << "setOfficialPlaybackPosition(" << *this
2581 << ") position:" << position
2582 << " truncated to duration:" << official_playback_position_;
2585 // Once set, official playback position should hold steady until the next
2586 // stable state. We approximate this by using a microtask to mark the
2587 // need for an update after the current (micro)task has completed. When
2588 // needed, the update is applied in the next call to
2589 // officialPlaybackPosition().
2590 official_playback_position_needs_update_ = false;
2591 GetDocument().GetAgent().event_loop()->EnqueueMicrotask(
2592 WTF::BindOnce(&HTMLMediaElement::RequireOfficialPlaybackPositionUpdate,
2593 WrapWeakPersistent(this)));
2596 void HTMLMediaElement::RequireOfficialPlaybackPositionUpdate() const {
2597 official_playback_position_needs_update_ = true;
2600 double HTMLMediaElement::currentTime() const {
2601 if (default_playback_start_position_)
2602 return default_playback_start_position_;
2605 DVLOG(3) << "currentTime(" << *this << ") - seeking, returning "
2607 return last_seek_time_;
2610 return OfficialPlaybackPosition();
2613 void HTMLMediaElement::setCurrentTime(double time) {
2614 // If the media element's readyState is kHaveNothing, then set the default
2615 // playback start position to that time.
2616 if (ready_state_ == kHaveNothing) {
2617 default_playback_start_position_ = time;
2622 ReportCurrentTimeToMediaSource();
2625 double HTMLMediaElement::duration() const {
2629 bool HTMLMediaElement::paused() const {
2633 double HTMLMediaElement::defaultPlaybackRate() const {
2634 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2636 return default_playback_rate_;
2639 void HTMLMediaElement::setDefaultPlaybackRate(double rate) {
2640 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2643 if (default_playback_rate_ == rate || !IsValidPlaybackRate(rate))
2646 default_playback_rate_ = rate;
2647 ScheduleEvent(event_type_names::kRatechange);
2650 double HTMLMediaElement::playbackRate() const {
2651 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2653 return playback_rate_;
2656 void HTMLMediaElement::setPlaybackRate(double rate,
2657 ExceptionState& exception_state) {
2658 DVLOG(3) << "setPlaybackRate(" << *this << ", " << rate << ")";
2659 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2662 if (!IsValidPlaybackRate(rate)) {
2663 UseCounter::Count(GetDocument(),
2664 WebFeature::kHTMLMediaElementMediaPlaybackRateOutOfRange);
2666 // When the proposed playbackRate is unsupported, throw a NotSupportedError
2667 // DOMException and don't update the value.
2668 exception_state.ThrowDOMException(
2669 DOMExceptionCode::kNotSupportedError,
2670 "The provided playback rate (" + String::Number(rate) +
2671 ") is not in the " + "supported playback range.");
2673 // Do not update |playback_rate_|.
2677 if (playback_rate_ != rate) {
2678 playback_rate_ = rate;
2679 ScheduleEvent(event_type_names::kRatechange);
2682 // FIXME: remove web_media_player_ check once we figure out how
2683 // web_media_player_ is going out of sync with readystate.
2684 // web_media_player_ is cleared but readystate is not set to kHaveNothing.
2685 if (web_media_player_) {
2686 if (PotentiallyPlaying())
2687 web_media_player_->SetRate(playbackRate());
2689 web_media_player_->OnTimeUpdate();
2692 if (cue_timeline_ && PotentiallyPlaying())
2693 cue_timeline_->OnPlaybackRateUpdated();
2696 HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::GetDirectionOfPlayback()
2698 return playback_rate_ >= 0 ? kForward : kBackward;
2701 bool HTMLMediaElement::ended() const {
2702 // 4.8.12.8 Playing the media resource
2703 // The ended attribute must return true if the media element has ended
2704 // playback and the direction of playback is forwards, and false otherwise.
2705 return EndedPlayback() && GetDirectionOfPlayback() == kForward;
2708 bool HTMLMediaElement::Autoplay() const {
2709 return FastHasAttribute(html_names::kAutoplayAttr);
2712 String HTMLMediaElement::preload() const {
2713 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2714 return PreloadTypeToString(WebMediaPlayer::kPreloadNone);
2715 return PreloadTypeToString(PreloadType());
2718 void HTMLMediaElement::setPreload(const AtomicString& preload) {
2719 DVLOG(2) << "setPreload(" << *this << ", " << preload << ")";
2720 if (GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
2722 setAttribute(html_names::kPreloadAttr, preload);
2725 WebMediaPlayer::Preload HTMLMediaElement::PreloadType() const {
2726 const AtomicString& preload = FastGetAttribute(html_names::kPreloadAttr);
2727 if (EqualIgnoringASCIICase(preload, "none")) {
2728 UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadNone);
2729 return WebMediaPlayer::kPreloadNone;
2732 if (EqualIgnoringASCIICase(preload, "metadata")) {
2733 UseCounter::Count(GetDocument(),
2734 WebFeature::kHTMLMediaElementPreloadMetadata);
2735 return WebMediaPlayer::kPreloadMetaData;
2738 // Force preload to 'metadata' on cellular connections.
2739 if (GetNetworkStateNotifier().IsCellularConnectionType()) {
2740 UseCounter::Count(GetDocument(),
2741 WebFeature::kHTMLMediaElementPreloadForcedMetadata);
2742 return WebMediaPlayer::kPreloadMetaData;
2745 // Per HTML spec, "The empty string ... maps to the Automatic state."
2746 // https://html.spec.whatwg.org/C/#attr-media-preload
2747 if (EqualIgnoringASCIICase(preload, "auto") ||
2748 EqualIgnoringASCIICase(preload, "")) {
2749 UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadAuto);
2750 return WebMediaPlayer::kPreloadAuto;
2753 // "The attribute's missing value default is user-agent defined, though the
2754 // Metadata state is suggested as a compromise between reducing server load
2755 // and providing an optimal user experience."
2757 // The spec does not define an invalid value default:
2758 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28950
2759 UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadDefault);
2760 return WebMediaPlayer::kPreloadMetaData;
2763 String HTMLMediaElement::EffectivePreload() const {
2764 return PreloadTypeToString(EffectivePreloadType());
2767 WebMediaPlayer::Preload HTMLMediaElement::EffectivePreloadType() const {
2768 if (Autoplay() && !autoplay_policy_->IsGestureNeededForPlayback())
2769 return WebMediaPlayer::kPreloadAuto;
2771 WebMediaPlayer::Preload preload = PreloadType();
2772 if (ignore_preload_none_ && preload == WebMediaPlayer::kPreloadNone)
2773 return WebMediaPlayer::kPreloadMetaData;
2778 ScriptPromise HTMLMediaElement::playForBindings(ScriptState* script_state) {
2779 // We have to share the same logic for internal and external callers. The
2780 // internal callers do not want to receive a Promise back but when ::play()
2781 // is called, |play_promise_resolvers_| needs to be populated. What this code
2782 // does is to populate |play_promise_resolvers_| before calling ::play() and
2783 // remove the Promise if ::play() failed.
2784 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
2785 ScriptPromise promise = resolver->Promise();
2786 play_promise_resolvers_.push_back(resolver);
2788 absl::optional<DOMExceptionCode> code = Play();
2790 DCHECK(!play_promise_resolvers_.empty());
2791 play_promise_resolvers_.pop_back();
2794 switch (code.value()) {
2795 case DOMExceptionCode::kNotAllowedError:
2796 message = autoplay_policy_->GetPlayErrorMessage();
2798 case DOMExceptionCode::kNotSupportedError:
2799 message = "The element has no supported sources.";
2804 resolver->Reject(MakeGarbageCollected<DOMException>(code.value(), message));
2811 absl::optional<DOMExceptionCode> HTMLMediaElement::Play() {
2812 DVLOG(2) << "play(" << *this << ")";
2814 absl::optional<DOMExceptionCode> exception_code =
2815 autoplay_policy_->RequestPlay();
2816 #if BUILDFLAG(IS_TIZEN_TV)
2817 is_deactivate_ = false;
2820 if (exception_code == DOMExceptionCode::kNotAllowedError) {
2821 // If we're already playing, then this play would do nothing anyway.
2822 // Call playInternal to handle scheduling the promise resolution.
2825 return absl::nullopt;
2827 return exception_code;
2830 autoplay_policy_->StopAutoplayMutedWhenVisible();
2832 if (error_ && error_->code() == MediaError::kMediaErrSrcNotSupported)
2833 return DOMExceptionCode::kNotSupportedError;
2835 DCHECK(!exception_code.has_value());
2839 return absl::nullopt;
2842 void HTMLMediaElement::PlayInternal() {
2843 DVLOG(3) << "playInternal(" << *this << ")";
2845 if (web_media_player_) {
2846 web_media_player_->SetWasPlayedWithUserActivation(
2847 LocalFrame::HasTransientUserActivation(GetDocument().GetFrame()));
2850 // Playback aborts any lazy loading.
2851 if (lazy_load_intersection_observer_) {
2852 lazy_load_intersection_observer_->disconnect();
2853 lazy_load_intersection_observer_ = nullptr;
2856 // 4.8.12.8. Playing the media resource
2857 if (network_state_ == kNetworkEmpty)
2858 InvokeResourceSelectionAlgorithm();
2860 // Generally "ended" and "looping" are exclusive. Here, the loop attribute
2861 // is ignored to seek back to start in case loop was set after playback
2862 // ended. See http://crbug.com/364442
2863 if (EndedPlayback(LoopCondition::kIgnored))
2868 SetShowPosterFlag(false);
2869 GetCueTimeline().InvokeTimeMarchesOn();
2870 ScheduleEvent(event_type_names::kPlay);
2872 if (ready_state_ <= kHaveCurrentData)
2873 ScheduleEvent(event_type_names::kWaiting);
2874 else if (ready_state_ >= kHaveFutureData)
2875 ScheduleNotifyPlaying();
2876 } else if (ready_state_ >= kHaveFutureData) {
2877 ScheduleResolvePlayPromises();
2880 can_autoplay_ = false;
2884 SetIgnorePreloadNone();
2888 void HTMLMediaElement::pause() {
2889 DVLOG(2) << "pause(" << *this << ")";
2891 // When updating pause, be sure to update PauseToLetDescriptionFinish().
2892 autoplay_policy_->StopAutoplayMutedWhenVisible();
2893 PauseInternal(PlayPromiseError::kPaused_PauseCalled);
2896 void HTMLMediaElement::PauseToLetDescriptionFinish() {
2897 DVLOG(2) << "pauseExceptSpeech(" << *this << ")";
2899 autoplay_policy_->StopAutoplayMutedWhenVisible();
2901 // Passing in pause_speech as false to pause everything except the speech.
2902 PauseInternal(PlayPromiseError::kPaused_PauseCalled, false);
2905 void HTMLMediaElement::PauseInternal(PlayPromiseError code,
2906 bool pause_speech /* = true */) {
2907 DVLOG(3) << "pauseInternal(" << *this << ")";
2909 if (network_state_ == kNetworkEmpty)
2910 InvokeResourceSelectionAlgorithm();
2912 can_autoplay_ = false;
2916 ScheduleTimeupdateEvent(false);
2917 ScheduleEvent(event_type_names::kPause);
2919 // Force an update to official playback position. Automatic updates from
2920 // currentPlaybackPosition() will be blocked while paused_ = true. This
2921 // blocking is desired while paused, but its good to update it one final
2922 // time to accurately reflect movie time at the moment we paused.
2923 SetOfficialPlaybackPosition(CurrentPlaybackPosition());
2925 ScheduleRejectPlayPromises(code);
2928 UpdatePlayState(pause_speech);
2931 bool HTMLMediaElement::preservesPitch() const {
2932 return preserves_pitch_;
2935 void HTMLMediaElement::setPreservesPitch(bool preserves_pitch) {
2936 preserves_pitch_ = preserves_pitch;
2938 if (web_media_player_)
2939 web_media_player_->SetPreservesPitch(preserves_pitch_);
2942 double HTMLMediaElement::latencyHint() const {
2943 // Parse error will fallback to std::numeric_limits<double>::quiet_NaN()
2944 double seconds = GetFloatingPointAttribute(html_names::kLatencyhintAttr);
2946 // Return NaN for invalid values.
2947 if (!std::isfinite(seconds) || seconds < 0)
2948 return std::numeric_limits<double>::quiet_NaN();
2953 void HTMLMediaElement::setLatencyHint(double seconds) {
2954 SetFloatingPointAttribute(html_names::kLatencyhintAttr, seconds);
2957 void HTMLMediaElement::FlingingStarted() {
2958 if (web_media_player_)
2959 web_media_player_->FlingingStarted();
2962 void HTMLMediaElement::FlingingStopped() {
2963 if (web_media_player_)
2964 web_media_player_->FlingingStopped();
2967 void HTMLMediaElement::CloseMediaSource() {
2968 if (!media_source_attachment_)
2971 media_source_attachment_->Close(media_source_tracer_);
2972 media_source_attachment_.reset();
2973 media_source_tracer_ = nullptr;
2976 bool HTMLMediaElement::Loop() const {
2977 return FastHasAttribute(html_names::kLoopAttr);
2980 void HTMLMediaElement::SetLoop(bool b) {
2981 DVLOG(3) << "setLoop(" << *this << ", " << BoolString(b) << ")";
2982 SetBooleanAttribute(html_names::kLoopAttr, b);
2985 bool HTMLMediaElement::ShouldShowControls(
2986 const RecordMetricsBehavior record_metrics) const {
2987 Settings* settings = GetDocument().GetSettings();
2988 if (settings && !settings->GetMediaControlsEnabled()) {
2989 if (record_metrics == RecordMetricsBehavior::kDoRecord)
2990 RecordShowControlsUsage(this, MediaControlsShow::kDisabledSettings);
2994 // If the user has explicitly shown or hidden the controls, then force that
2996 if (user_wants_controls_visible_.has_value()) {
2997 if (record_metrics == RecordMetricsBehavior::kDoRecord) {
2998 RecordShowControlsUsage(this,
2999 *user_wants_controls_visible_
3000 ? MediaControlsShow::kUserExplicitlyEnabled
3001 : MediaControlsShow::kUserExplicitlyDisabled);
3003 return *user_wants_controls_visible_;
3006 if (FastHasAttribute(html_names::kControlsAttr)) {
3007 if (record_metrics == RecordMetricsBehavior::kDoRecord)
3008 RecordShowControlsUsage(this, MediaControlsShow::kAttribute);
3012 if (IsFullscreen()) {
3013 if (record_metrics == RecordMetricsBehavior::kDoRecord)
3014 RecordShowControlsUsage(this, MediaControlsShow::kFullscreen);
3018 ExecutionContext* context = GetExecutionContext();
3019 if (context && !context->CanExecuteScripts(kNotAboutToExecuteScript)) {
3020 if (record_metrics == RecordMetricsBehavior::kDoRecord)
3021 RecordShowControlsUsage(this, MediaControlsShow::kNoScript);
3025 if (record_metrics == RecordMetricsBehavior::kDoRecord)
3026 RecordShowControlsUsage(this, MediaControlsShow::kNotShown);
3030 bool HTMLMediaElement::ShouldShowAllControls() const {
3031 // If the user has explicitly shown or hidden the controls, then force that
3032 // choice. Otherwise returns whether controls should be shown and no controls
3033 // are meant to be hidden.
3034 return user_wants_controls_visible_.value_or(
3035 ShouldShowControls() && !ControlsListInternal()->CanShowAllControls());
3038 DOMTokenList* HTMLMediaElement::controlsList() const {
3039 return controls_list_.Get();
3042 HTMLMediaElementControlsList* HTMLMediaElement::ControlsListInternal() const {
3043 return controls_list_.Get();
3046 double HTMLMediaElement::volume() const {
3050 void HTMLMediaElement::setVolume(double vol, ExceptionState& exception_state) {
3051 DVLOG(2) << "setVolume(" << *this << ", " << vol << ")";
3056 if (RuntimeEnabledFeatures::MediaElementVolumeGreaterThanOneEnabled()) {
3058 exception_state.ThrowDOMException(
3059 DOMExceptionCode::kIndexSizeError,
3060 ExceptionMessages::IndexExceedsMinimumBound("volume", vol, 0.0));
3063 } else if (vol < 0.0f || vol > 1.0f) {
3064 exception_state.ThrowDOMException(
3065 DOMExceptionCode::kIndexSizeError,
3066 ExceptionMessages::IndexOutsideRange(
3067 "volume", vol, 0.0, ExceptionMessages::kInclusiveBound, 1.0,
3068 ExceptionMessages::kInclusiveBound));
3074 ScheduleEvent(event_type_names::kVolumechange);
3076 // If it setting volume to audible and AutoplayPolicy doesn't want the
3077 // playback to continue, pause the playback.
3078 if (EffectiveMediaVolume() && !autoplay_policy_->RequestAutoplayUnmute())
3081 // If playback was not paused by the autoplay policy and got audible, the
3082 // element is marked as being allowed to play unmuted.
3083 if (EffectiveMediaVolume() && PotentiallyPlaying())
3084 was_always_muted_ = false;
3086 if (web_media_player_)
3087 web_media_player_->SetVolume(EffectiveMediaVolume());
3089 autoplay_policy_->StopAutoplayMutedWhenVisible();
3092 bool HTMLMediaElement::muted() const {
3096 void HTMLMediaElement::setMuted(bool muted) {
3097 DVLOG(2) << "setMuted(" << *this << ", " << BoolString(muted) << ")";
3099 if (muted_ == muted)
3104 ScheduleEvent(event_type_names::kVolumechange);
3106 // If it is unmute and AutoplayPolicy doesn't want the playback to continue,
3107 // pause the playback.
3108 if (EffectiveMediaVolume() && !autoplay_policy_->RequestAutoplayUnmute())
3111 // If playback was not paused by the autoplay policy and got unmuted, the
3112 // element is marked as being allowed to play unmuted.
3113 if (EffectiveMediaVolume() && PotentiallyPlaying())
3114 was_always_muted_ = false;
3116 // This is called at the end to make sure the WebMediaPlayer has the right
3118 if (web_media_player_)
3119 web_media_player_->SetVolume(EffectiveMediaVolume());
3121 autoplay_policy_->StopAutoplayMutedWhenVisible();
3124 void HTMLMediaElement::SetUserWantsControlsVisible(bool visible) {
3125 user_wants_controls_visible_ = visible;
3126 UpdateControlsVisibility();
3129 bool HTMLMediaElement::UserWantsControlsVisible() const {
3130 return user_wants_controls_visible_.value_or(false);
3133 double HTMLMediaElement::EffectiveMediaVolume() const {
3140 // The spec says to fire periodic timeupdate events (those sent while playing)
3141 // every "15 to 250ms", we choose the slowest frequency
3142 static const base::TimeDelta kMaxTimeupdateEventFrequency =
3143 base::Milliseconds(250);
3145 void HTMLMediaElement::StartPlaybackProgressTimer() {
3146 if (playback_progress_timer_.IsActive())
3149 previous_progress_time_ = base::ElapsedTimer();
3150 playback_progress_timer_.StartRepeating(kMaxTimeupdateEventFrequency);
3153 void HTMLMediaElement::PlaybackProgressTimerFired() {
3154 if (!std::isnan(fragment_end_time_) && currentTime() >= fragment_end_time_ &&
3155 GetDirectionOfPlayback() == kForward) {
3156 fragment_end_time_ = std::numeric_limits<double>::quiet_NaN();
3158 UseCounter::Count(GetDocument(),
3159 WebFeature::kHTMLMediaElementPauseAtFragmentEnd);
3160 // changes paused to true and fires a simple event named pause at the
3162 PauseInternal(PlayPromiseError::kPaused_EndOfPlayback);
3167 ScheduleTimeupdateEvent(true);
3169 // Playback progress is chosen here for simplicity as a proxy for a good
3170 // periodic time to also update the attached MediaSource, if any, with our
3171 // currentTime so that it can continue to have a "recent media time".
3172 ReportCurrentTimeToMediaSource();
3175 void HTMLMediaElement::ScheduleTimeupdateEvent(bool periodic_event) {
3176 if (web_media_player_)
3177 web_media_player_->OnTimeUpdate();
3179 // Per spec, consult current playback position to check for changing time.
3180 double media_time = CurrentPlaybackPosition();
3181 bool media_time_has_progressed =
3182 std::isnan(last_time_update_event_media_time_)
3184 : media_time != last_time_update_event_media_time_;
3186 if (periodic_event && !media_time_has_progressed)
3189 ScheduleEvent(event_type_names::kTimeupdate);
3191 last_time_update_event_media_time_ = media_time;
3193 // Restart the timer to ensure periodic event fires 250ms from _this_ event.
3194 if (!periodic_event && playback_progress_timer_.IsActive()) {
3195 playback_progress_timer_.Stop();
3196 playback_progress_timer_.StartRepeating(kMaxTimeupdateEventFrequency);
3200 void HTMLMediaElement::TogglePlayState() {
3207 AudioTrackList& HTMLMediaElement::audioTracks() {
3208 return *audio_tracks_;
3211 void HTMLMediaElement::AudioTrackChanged(AudioTrack* track) {
3212 DVLOG(3) << "audioTrackChanged(" << *this
3213 << ") trackId= " << String(track->id())
3214 << " enabled=" << BoolString(track->enabled());
3216 audioTracks().ScheduleChangeEvent();
3218 if (media_source_attachment_)
3219 media_source_attachment_->OnTrackChanged(media_source_tracer_, track);
3221 if (!audio_tracks_timer_.IsActive())
3222 audio_tracks_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
3225 void HTMLMediaElement::AudioTracksTimerFired(TimerBase*) {
3226 Vector<WebMediaPlayer::TrackId> enabled_track_ids;
3227 for (unsigned i = 0; i < audioTracks().length(); ++i) {
3228 AudioTrack* track = audioTracks().AnonymousIndexedGetter(i);
3229 if (track->enabled())
3230 enabled_track_ids.push_back(track->id());
3233 web_media_player_->EnabledAudioTracksChanged(enabled_track_ids);
3236 WebMediaPlayer::TrackId HTMLMediaElement::AddAudioTrack(
3237 const WebString& id,
3238 WebMediaPlayerClient::AudioTrackKind kind,
3239 const WebString& label,
3240 const WebString& language,
3242 AtomicString kind_string = AudioKindToString(kind);
3243 DVLOG(3) << "addAudioTrack(" << *this << ", '" << String(id) << "', ' "
3244 << kind_string << "', '" << String(label) << "', '"
3245 << String(language) << "', " << BoolString(enabled) << ")";
3247 auto* audio_track = MakeGarbageCollected<AudioTrack>(id, kind_string, label,
3249 audioTracks().Add(audio_track);
3251 return audio_track->id();
3254 void HTMLMediaElement::RemoveAudioTrack(WebMediaPlayer::TrackId track_id) {
3255 DVLOG(3) << "removeAudioTrack(" << *this << ")";
3257 audioTracks().Remove(track_id);
3260 VideoTrackList& HTMLMediaElement::videoTracks() {
3261 return *video_tracks_;
3264 void HTMLMediaElement::SelectedVideoTrackChanged(VideoTrack* track) {
3265 DVLOG(3) << "selectedVideoTrackChanged(" << *this << ") selectedTrackId="
3266 << (track->selected() ? String(track->id()) : "none");
3268 if (track->selected())
3269 videoTracks().TrackSelected(track->id());
3271 videoTracks().ScheduleChangeEvent();
3273 if (media_source_attachment_)
3274 media_source_attachment_->OnTrackChanged(media_source_tracer_, track);
3276 WebMediaPlayer::TrackId id = track->id();
3277 web_media_player_->SelectedVideoTrackChanged(track->selected() ? &id
3281 WebMediaPlayer::TrackId HTMLMediaElement::AddVideoTrack(
3282 const WebString& id,
3283 WebMediaPlayerClient::VideoTrackKind kind,
3284 const WebString& label,
3285 const WebString& language,
3287 AtomicString kind_string = VideoKindToString(kind);
3288 DVLOG(3) << "addVideoTrack(" << *this << ", '" << String(id) << "', '"
3289 << kind_string << "', '" << String(label) << "', '"
3290 << String(language) << "', " << BoolString(selected) << ")";
3292 // If another track was selected (potentially by the user), leave it selected.
3293 if (selected && videoTracks().selectedIndex() != -1)
3296 auto* video_track = MakeGarbageCollected<VideoTrack>(id, kind_string, label,
3297 language, selected);
3298 videoTracks().Add(video_track);
3300 return video_track->id();
3303 void HTMLMediaElement::RemoveVideoTrack(WebMediaPlayer::TrackId track_id) {
3304 DVLOG(3) << "removeVideoTrack(" << *this << ")";
3306 videoTracks().Remove(track_id);
3309 void HTMLMediaElement::ForgetResourceSpecificTracks() {
3310 audio_tracks_->RemoveAll();
3311 video_tracks_->RemoveAll();
3313 audio_tracks_timer_.Stop();
3316 TextTrack* HTMLMediaElement::addTextTrack(const AtomicString& kind,
3317 const AtomicString& label,
3318 const AtomicString& language,
3319 ExceptionState& exception_state) {
3320 // https://html.spec.whatwg.org/C/#dom-media-addtexttrack
3322 // The addTextTrack(kind, label, language) method of media elements, when
3323 // invoked, must run the following steps:
3325 // 1. Create a new TextTrack object.
3326 // 2. Create a new text track corresponding to the new object, and set its
3327 // text track kind to kind, its text track label to label, its text
3328 // track language to language, ..., and its text track list of cues to
3331 MakeGarbageCollected<TextTrack>(kind, label, language, *this);
3332 // ..., its text track readiness state to the text track loaded state, ...
3333 text_track->SetReadinessState(TextTrack::kLoaded);
3335 // 3. Add the new text track to the media element's list of text tracks.
3336 // 4. Queue a task to fire a trusted event with the name addtrack, that
3337 // does not bubble and is not cancelable, and that uses the TrackEvent
3338 // interface, with the track attribute initialised to the new text
3339 // track's TextTrack object, at the media element's textTracks
3340 // attribute's TextTrackList object.
3341 textTracks()->Append(text_track);
3343 // Note: Due to side effects when changing track parameters, we have to
3344 // first append the track to the text track list.
3345 // FIXME: Since setMode() will cause a 'change' event to be queued on the
3346 // same task source as the 'addtrack' event (see above), the order is
3347 // wrong. (The 'change' event shouldn't be fired at all in this case...)
3349 // ..., its text track mode to the text track hidden mode, ...
3350 text_track->SetModeEnum(TextTrackMode::kHidden);
3352 // 5. Return the new TextTrack object.
3356 TextTrackList* HTMLMediaElement::textTracks() {
3357 if (!text_tracks_) {
3358 UseCounter::Count(GetDocument(), WebFeature::kMediaElementTextTrackList);
3359 text_tracks_ = MakeGarbageCollected<TextTrackList>(this);
3362 return text_tracks_.Get();
3365 void HTMLMediaElement::DidAddTrackElement(HTMLTrackElement* track_element) {
3366 // 4.8.12.11.3 Sourcing out-of-band text tracks
3367 // When a track element's parent element changes and the new parent is a media
3368 // element, then the user agent must add the track element's corresponding
3369 // text track to the media element's list of text tracks ... [continues in
3370 // TextTrackList::append]
3371 TextTrack* text_track = track_element->track();
3375 textTracks()->Append(text_track);
3377 // Do not schedule the track loading until parsing finishes so we don't start
3378 // before all tracks in the markup have been added.
3379 if (IsFinishedParsingChildren())
3380 ScheduleTextTrackResourceLoad();
3383 void HTMLMediaElement::DidRemoveTrackElement(HTMLTrackElement* track_element) {
3384 KURL url = track_element->GetNonEmptyURLAttribute(html_names::kSrcAttr);
3385 DVLOG(3) << "didRemoveTrackElement(" << *this << ") - 'src' is "
3386 << UrlForLoggingMedia(url);
3388 TextTrack* text_track = track_element->track();
3392 text_track->SetHasBeenConfigured(false);
3397 // 4.8.12.11.3 Sourcing out-of-band text tracks
3398 // When a track element's parent element changes and the old parent was a
3399 // media element, then the user agent must remove the track element's
3400 // corresponding text track from the media element's list of text tracks.
3401 text_tracks_->Remove(text_track);
3404 text_tracks_when_resource_selection_began_.Find(text_track);
3405 if (index != kNotFound)
3406 text_tracks_when_resource_selection_began_.EraseAt(index);
3409 void HTMLMediaElement::HonorUserPreferencesForAutomaticTextTrackSelection() {
3410 if (!text_tracks_ || !text_tracks_->length())
3413 if (!should_perform_automatic_track_selection_)
3416 AutomaticTrackSelection::Configuration configuration;
3417 if (processing_preference_change_)
3418 configuration.disable_currently_enabled_tracks = true;
3419 if (text_tracks_visible_)
3420 configuration.force_enable_subtitle_or_caption_track = true;
3422 Settings* settings = GetDocument().GetSettings();
3424 configuration.text_track_kind_user_preference =
3425 settings->GetTextTrackKindUserPreference();
3428 AutomaticTrackSelection track_selection(configuration);
3429 track_selection.Perform(*text_tracks_);
3432 bool HTMLMediaElement::HavePotentialSourceChild() {
3433 // Stash the current <source> node and next nodes so we can restore them after
3434 // checking to see there is another potential.
3435 HTMLSourceElement* current_source_node = current_source_node_;
3436 Node* next_node = next_child_node_to_consider_;
3438 KURL next_url = SelectNextSourceChild(nullptr, kDoNothing);
3440 current_source_node_ = current_source_node;
3441 next_child_node_to_consider_ = next_node;
3443 return next_url.IsValid();
3446 KURL HTMLMediaElement::SelectNextSourceChild(
3447 String* content_type,
3448 InvalidURLAction action_if_invalid) {
3449 // Don't log if this was just called to find out if there are any valid
3450 // <source> elements.
3451 bool should_log = action_if_invalid != kDoNothing;
3453 DVLOG(3) << "selectNextSourceChild(" << *this << ")";
3455 if (!next_child_node_to_consider_) {
3457 DVLOG(3) << "selectNextSourceChild(" << *this << ") -> 0x0000, \"\"";
3464 HTMLSourceElement* source = nullptr;
3466 bool looking_for_start_node = next_child_node_to_consider_ != nullptr;
3467 bool can_use_source_element = false;
3469 NodeVector potential_source_nodes;
3470 GetChildNodes(*this, potential_source_nodes);
3472 for (unsigned i = 0;
3473 !can_use_source_element && i < potential_source_nodes.size(); ++i) {
3474 node = potential_source_nodes[i].Get();
3475 if (looking_for_start_node && next_child_node_to_consider_ != node)
3477 looking_for_start_node = false;
3479 source = DynamicTo<HTMLSourceElement>(node);
3480 if (!source || node->parentNode() != this) {
3484 // 2. If candidate does not have a src attribute, or if its src
3485 // attribute's value is the empty string ... jump down to the failed
3487 const AtomicString& src_value =
3488 source->FastGetAttribute(html_names::kSrcAttr);
3490 DVLOG(3) << "selectNextSourceChild(" << *this << ") - 'src' is "
3491 << UrlForLoggingMedia(media_url);
3493 if (src_value.empty()) {
3497 // 3. If candidate has a media attribute whose value does not match the
3498 // environment, then end the synchronous section, and jump down to the
3499 // failed with elements step below.
3500 if (!source->MediaQueryMatches() &&
3501 base::FeatureList::IsEnabled(kVideoSourceMediaQuerySupport)) {
3505 // 4. Let urlRecord be the result of encoding-parsing a URL given
3506 // candidate's src attribute's value, relative to candidate's node document
3507 // when the src attribute was last changed.
3508 media_url = source->GetDocument().CompleteURL(src_value);
3510 // 5. If urlRecord is failure, then end the synchronous section, and jump
3511 // down to the failed with elements step below.
3512 if (!IsSafeToLoadURL(media_url, action_if_invalid)) {
3516 // 6. If candidate has a type attribute whose value, when parsed as a
3518 type = source->type();
3519 if (type.empty() && media_url.ProtocolIsData())
3520 type = MimeTypeFromDataURL(media_url);
3521 if (!type.empty()) {
3523 DVLOG(3) << "selectNextSourceChild(" << *this << ") - 'type' is '"
3526 if (!GetSupportsType(ContentType(type)))
3530 // Making it this far means the <source> looks reasonable.
3531 can_use_source_element = true;
3534 if (!can_use_source_element && action_if_invalid == kComplain && source)
3535 source->ScheduleErrorEvent();
3538 if (can_use_source_element) {
3540 *content_type = type;
3541 current_source_node_ = source;
3542 next_child_node_to_consider_ = source->nextSibling();
3544 current_source_node_ = nullptr;
3545 next_child_node_to_consider_ = nullptr;
3549 DVLOG(3) << "selectNextSourceChild(" << *this << ") -> "
3550 << current_source_node_.Get() << ", "
3551 << (can_use_source_element ? UrlForLoggingMedia(media_url) : "");
3554 return can_use_source_element ? media_url : KURL();
3557 void HTMLMediaElement::SourceWasAdded(HTMLSourceElement* source) {
3558 DVLOG(3) << "sourceWasAdded(" << *this << ", " << source << ")";
3560 KURL url = source->GetNonEmptyURLAttribute(html_names::kSrcAttr);
3561 DVLOG(3) << "sourceWasAdded(" << *this << ") - 'src' is "
3562 << UrlForLoggingMedia(url);
3564 // We should only consider a <source> element when there is not src attribute
3566 if (FastHasAttribute(html_names::kSrcAttr))
3569 // 4.8.8 - If a source element is inserted as a child of a media element that
3570 // has no src attribute and whose networkState has the value NETWORK_EMPTY,
3571 // the user agent must invoke the media element's resource selection
3573 if (getNetworkState() == HTMLMediaElement::kNetworkEmpty) {
3574 InvokeResourceSelectionAlgorithm();
3575 // Ignore current |next_child_node_to_consider_| and consider |source|.
3576 next_child_node_to_consider_ = source;
3580 if (current_source_node_ && source == current_source_node_->nextSibling()) {
3581 DVLOG(3) << "sourceWasAdded(" << *this
3582 << ") - <source> inserted immediately after current source";
3583 // Ignore current |next_child_node_to_consider_| and consider |source|.
3584 next_child_node_to_consider_ = source;
3588 // Consider current |next_child_node_to_consider_| as it is already in the
3589 // middle of processing.
3590 if (next_child_node_to_consider_)
3593 if (load_state_ != kWaitingForSource)
3596 // 4.8.9.5, resource selection algorithm, source elements section:
3597 // 21. Wait until the node after pointer is a node other than the end of the
3598 // list. (This step might wait forever.)
3599 // 22. Asynchronously await a stable state...
3600 // 23. Set the element's delaying-the-load-event flag back to true (this
3601 // delays the load event again, in case it hasn't been fired yet).
3602 SetShouldDelayLoadEvent(true);
3604 // 24. Set the networkState back to NETWORK_LOADING.
3605 // Changing the network state might trigger media controls to add new nodes
3606 // to the DOM which is forbidden while source is being inserted into this
3607 // node. This is a problem as ContainerNode::NotifyNodeInsertedInternal,
3608 // which is always indirectly triggering this function, prohibits event
3609 // dispatch and adding new nodes will run
3610 // blink::DispatchChildInsertionEvents.
3612 // We still need to update the media controls. This will be done after
3613 // load_timer_ fires a new event - which is setup in ScheduleNextSourceChild
3614 // below so skipping that step here should be OK.
3615 SetNetworkState(kNetworkLoading, false /* update_media_controls */);
3617 // 25. Jump back to the find next candidate step above.
3618 next_child_node_to_consider_ = source;
3619 ScheduleNextSourceChild();
3622 void HTMLMediaElement::SourceWasRemoved(HTMLSourceElement* source) {
3623 DVLOG(3) << "sourceWasRemoved(" << *this << ", " << source << ")";
3625 KURL url = source->GetNonEmptyURLAttribute(html_names::kSrcAttr);
3626 DVLOG(3) << "sourceWasRemoved(" << *this << ") - 'src' is "
3627 << UrlForLoggingMedia(url);
3629 if (source != current_source_node_ && source != next_child_node_to_consider_)
3632 if (source == next_child_node_to_consider_) {
3633 if (current_source_node_)
3634 next_child_node_to_consider_ = current_source_node_->nextSibling();
3635 DVLOG(3) << "sourceWasRemoved(" << *this
3636 << ") - next_child_node_to_consider_ set to "
3637 << next_child_node_to_consider_.Get();
3638 } else if (source == current_source_node_) {
3639 // Clear the current source node pointer, but don't change the movie as the
3641 // 4.8.8 - Dynamically modifying a source element and its attribute when the
3642 // element is already inserted in a video or audio element will have no
3644 current_source_node_ = nullptr;
3645 DVLOG(3) << "SourceWasRemoved(" << *this
3646 << ") - current_source_node_ set to 0";
3650 void HTMLMediaElement::TimeChanged() {
3651 DVLOG(3) << "timeChanged(" << *this << ")";
3653 // 4.8.12.9 steps 12-14. Needed if no ReadyState change is associated with the
3655 if (seeking_ && ready_state_ >= kHaveCurrentData &&
3656 !web_media_player_->Seeking()) {
3660 // When the current playback position reaches the end of the media resource
3661 // when the direction of playback is forwards, then the user agent must follow
3663 #if defined(TIZEN_MULTIMEDIA)
3664 // mmplayer return duration = 0 for some normal stream. Media will set
3665 // duration = max for this case. And now = max also, this case is also
3666 // regarded as playback complete.
3667 const double now = CurrentPlaybackPosition();
3668 const double dur = duration();
3669 bool is_duration_exception_case =
3670 (now == std::numeric_limits<double>::infinity()) &&
3671 (dur == std::numeric_limits<double>::infinity());
3673 if (live_playback_complete_ || is_duration_exception_case ||
3674 (EndedPlayback(LoopCondition::kIgnored))) {
3676 if (EndedPlayback(LoopCondition::kIgnored)) {
3678 // If the media element has a loop attribute specified
3679 if (Loop() && EarliestPossiblePosition() != CurrentPlaybackPosition()) {
3680 // then seek to the earliest possible position of the media resource and
3681 // abort these steps.
3682 Seek(EarliestPossiblePosition());
3684 // Queue a task to fire a simple event named timeupdate at the media
3686 ScheduleTimeupdateEvent(false);
3688 // If the media element has still ended playback, and the direction of
3689 // playback is still forwards, and paused is false,
3691 // Trigger an update to `official_playback_position_` (if necessary)
3692 // BEFORE setting `paused_ = false`, to ensure a final sync with
3693 // `WebMediaPlayer()->CurrentPlaybackPosition()`.
3694 OfficialPlaybackPosition();
3696 // changes paused to true and fires a simple event named pause at the
3699 ScheduleEvent(event_type_names::kPause);
3700 ScheduleRejectPlayPromises(PlayPromiseError::kPaused_EndOfPlayback);
3702 // Queue a task to fire a simple event named ended at the media element.
3703 ScheduleEvent(event_type_names::kEnded);
3708 #if defined(TIZEN_MULTIMEDIA)
3709 live_playback_complete_ = false;
3713 void HTMLMediaElement::DurationChanged() {
3714 DVLOG(3) << "durationChanged(" << *this << ")";
3716 // durationChanged() is triggered by media player.
3717 CHECK(web_media_player_);
3718 double new_duration = web_media_player_->Duration();
3720 // If the duration is changed such that the *current playback position* ends
3721 // up being greater than the time of the end of the media resource, then the
3722 // user agent must also seek to the time of the end of the media resource.
3723 DurationChanged(new_duration, CurrentPlaybackPosition() > new_duration);
3726 void HTMLMediaElement::DurationChanged(double duration, bool request_seek) {
3727 DVLOG(3) << "durationChanged(" << *this << ", " << duration << ", "
3728 << BoolString(request_seek) << ")";
3730 // Abort if duration unchanged.
3731 if (duration_ == duration)
3734 DVLOG(3) << "durationChanged(" << *this << ") : " << duration_ << " -> "
3736 duration_ = duration;
3737 ScheduleEvent(event_type_names::kDurationchange);
3739 if (web_media_player_)
3740 web_media_player_->OnTimeUpdate();
3742 UpdateLayoutObject();
3748 void HTMLMediaElement::RemotePlaybackCompatibilityChanged(const WebURL& url,
3749 bool is_compatible) {
3750 if (RemotePlaybackClient())
3751 RemotePlaybackClient()->SourceChanged(url, is_compatible);
3754 bool HTMLMediaElement::HasSelectedVideoTrack() {
3755 return video_tracks_ && video_tracks_->selectedIndex() != -1;
3758 WebMediaPlayer::TrackId HTMLMediaElement::GetSelectedVideoTrackId() {
3759 DCHECK(HasSelectedVideoTrack());
3761 int selected_track_index = video_tracks_->selectedIndex();
3763 video_tracks_->AnonymousIndexedGetter(selected_track_index);
3767 bool HTMLMediaElement::WasAlwaysMuted() {
3768 return was_always_muted_;
3771 // MediaPlayerPresentation methods
3772 void HTMLMediaElement::Repaint() {
3774 cc_layer_->SetNeedsDisplay();
3776 UpdateLayoutObject();
3777 if (GetLayoutObject())
3778 GetLayoutObject()->SetShouldDoFullPaintInvalidation();
3781 void HTMLMediaElement::SizeChanged() {
3782 DVLOG(3) << "sizeChanged(" << *this << ")";
3784 DCHECK(HasVideo()); // "resize" makes no sense in absence of video.
3785 if (ready_state_ > kHaveNothing && IsHTMLVideoElement())
3786 ScheduleEvent(event_type_names::kResize);
3788 UpdateLayoutObject();
3791 WebTimeRanges HTMLMediaElement::BufferedInternal() const {
3792 if (media_source_attachment_) {
3793 return media_source_attachment_->BufferedInternal(
3794 media_source_tracer_.Get());
3797 if (!web_media_player_)
3800 return web_media_player_->Buffered();
3803 TimeRanges* HTMLMediaElement::buffered() const {
3804 return MakeGarbageCollected<TimeRanges>(BufferedInternal());
3807 TimeRanges* HTMLMediaElement::played() {
3809 double time = currentTime();
3810 if (time > last_seek_time_)
3811 AddPlayedRange(last_seek_time_, time);
3814 if (!played_time_ranges_)
3815 played_time_ranges_ = MakeGarbageCollected<TimeRanges>();
3817 return played_time_ranges_->Copy();
3820 WebTimeRanges HTMLMediaElement::SeekableInternal() const {
3821 if (!web_media_player_)
3824 if (media_source_attachment_) {
3825 return media_source_attachment_->SeekableInternal(
3826 media_source_tracer_.Get());
3829 return web_media_player_->Seekable();
3832 TimeRanges* HTMLMediaElement::seekable() const {
3833 return MakeGarbageCollected<TimeRanges>(SeekableInternal());
3836 bool HTMLMediaElement::PotentiallyPlaying() const {
3837 // Once we've reached the metadata state the WebMediaPlayer is ready to accept
3838 // play state changes.
3839 return ready_state_ >= kHaveMetadata && CouldPlayIfEnoughData();
3842 bool HTMLMediaElement::CouldPlayIfEnoughData() const {
3843 return !paused() && !EndedPlayback() && !StoppedDueToErrors();
3846 bool HTMLMediaElement::EndedPlayback(LoopCondition loop_condition) const {
3847 // If we have infinite duration, we'll never have played for long enough to
3848 // have ended playback.
3849 const double dur = duration();
3850 if (std::isnan(dur) || dur == std::numeric_limits<double>::infinity())
3853 // 4.8.12.8 Playing the media resource
3855 // A media element is said to have ended playback when the element's
3856 // readyState attribute is HAVE_METADATA or greater,
3857 if (ready_state_ < kHaveMetadata)
3860 DCHECK_EQ(GetDirectionOfPlayback(), kForward);
3861 if (web_media_player_) {
3862 return web_media_player_->IsEnded() && dur > 0 &&
3863 (loop_condition == LoopCondition::kIgnored || !Loop() ||
3864 dur <= std::numeric_limits<double>::epsilon());
3870 bool HTMLMediaElement::StoppedDueToErrors() const {
3871 if (ready_state_ >= kHaveMetadata && error_) {
3872 WebTimeRanges seekable_ranges = SeekableInternal();
3873 if (!seekable_ranges.Contain(currentTime()))
3880 void HTMLMediaElement::UpdatePlayState(bool pause_speech /* = true */) {
3881 bool is_playing = web_media_player_ && !web_media_player_->Paused();
3882 bool should_be_playing = PotentiallyPlaying();
3884 DVLOG(3) << "updatePlayState(" << *this
3885 << ") - shouldBePlaying = " << BoolString(should_be_playing)
3886 << ", isPlaying = " << BoolString(is_playing);
3888 if (should_be_playing && !muted_)
3889 was_always_muted_ = false;
3891 if (should_be_playing) {
3893 // Set rate, muted before calling play in case they were set before the
3894 // media engine was setup. The media engine should just stash the rate
3895 // and muted values since it isn't already playing.
3896 web_media_player_->SetRate(playbackRate());
3897 web_media_player_->SetVolume(EffectiveMediaVolume());
3898 web_media_player_->Play();
3899 if (::features::IsTextBasedAudioDescriptionEnabled())
3900 SpeechSynthesis()->Resume();
3902 // These steps should not be necessary, but if `play()` is called before
3903 // a source change, we may get into a state where `paused_ == false` and
3904 // `show_poster_flag_ == true`. My (cassew@google.com) interpretation of
3905 // the spec is that we should not be playing in this scenario.
3906 // https://crbug.com/633591
3907 SetShowPosterFlag(false);
3908 GetCueTimeline().InvokeTimeMarchesOn();
3911 StartPlaybackProgressTimer();
3913 } else { // Should not be playing right now
3915 web_media_player_->Pause();
3917 if (pause_speech && ::features::IsTextBasedAudioDescriptionEnabled())
3918 SpeechSynthesis()->Pause();
3921 playback_progress_timer_.Stop();
3923 double time = currentTime();
3924 if (time > last_seek_time_)
3925 AddPlayedRange(last_seek_time_, time);
3927 GetCueTimeline().OnPause();
3930 UpdateLayoutObject();
3932 if (web_media_player_)
3933 web_media_player_->OnTimeUpdate();
3935 ReportCurrentTimeToMediaSource();
3936 PseudoStateChanged(CSSSelector::kPseudoPaused);
3937 PseudoStateChanged(CSSSelector::kPseudoPlaying);
3940 void HTMLMediaElement::StopPeriodicTimers() {
3941 progress_event_timer_.Stop();
3942 playback_progress_timer_.Stop();
3943 if (lazy_load_intersection_observer_) {
3944 lazy_load_intersection_observer_->disconnect();
3945 lazy_load_intersection_observer_ = nullptr;
3949 void HTMLMediaElement::
3950 ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking() {
3951 GetAudioSourceProvider().SetClient(nullptr);
3952 if (web_media_player_) {
3953 audio_source_provider_.Wrap(nullptr);
3954 web_media_player_.reset();
3955 // Do not clear `opener_document_` here; new players might still use it.
3957 // The lifetime of the mojo endpoints are tied to the WebMediaPlayer's, so
3958 // we need to reset those as well.
3959 media_player_receiver_set_->Value().Clear();
3960 media_player_observer_remote_set_->Value().Clear();
3963 OnWebMediaPlayerCleared();
3966 void HTMLMediaElement::ClearMediaPlayer() {
3967 ForgetResourceSpecificTracks();
3971 CancelDeferredLoad();
3974 AudioSourceProviderClientLockScope scope(*this);
3975 ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
3978 StopPeriodicTimers();
3981 pending_action_flags_ = 0;
3982 load_state_ = kWaitingForSource;
3984 if (GetLayoutObject())
3985 GetLayoutObject()->SetShouldDoFullPaintInvalidation();
3988 void HTMLMediaElement::ContextLifecycleStateChanged(
3989 mojom::FrameLifecycleState state) {
3990 if (state == mojom::FrameLifecycleState::kFrozenAutoResumeMedia && playing_) {
3991 paused_by_context_paused_ = true;
3993 if (web_media_player_) {
3994 web_media_player_->OnFrozen();
3996 } else if (state == mojom::FrameLifecycleState::kFrozen && playing_) {
3998 if (web_media_player_) {
3999 web_media_player_->OnFrozen();
4002 #if defined(TIZEN_MULTIMEDIA)
4003 else if (state == mojom::FrameLifecycleState::kPaused) {
4004 // Suspend if a browser tab is switched.
4005 if (GetWebMediaPlayer())
4006 GetWebMediaPlayer()->Suspend();
4009 paused_by_context_paused_ = true;
4012 } else if (state == mojom::FrameLifecycleState::kRunning) {
4013 // Do not resume if suspended by player except the resource conflict case.
4014 if (GetWebMediaPlayer() && GetWebMediaPlayer()->SuspendedByPlayer())
4017 if (GetWebMediaPlayer())
4018 GetWebMediaPlayer()->Resume();
4020 if (paused_by_context_paused_) {
4021 paused_by_context_paused_ = false;
4026 else if (state == mojom::FrameLifecycleState::kRunning &&
4027 paused_by_context_paused_) {
4029 paused_by_context_paused_ = false;
4035 void HTMLMediaElement::ContextDestroyed() {
4036 DVLOG(3) << "contextDestroyed(" << static_cast<void*>(this) << ")";
4038 // Close the async event queue so that no events are enqueued.
4039 CancelPendingEventsAndCallbacks();
4041 // Clear everything in the Media Element
4042 if (media_source_attachment_)
4043 media_source_attachment_->OnElementContextDestroyed();
4045 ready_state_ = kHaveNothing;
4046 ready_state_maximum_ = kHaveNothing;
4047 SetNetworkState(kNetworkEmpty);
4048 SetShouldDelayLoadEvent(false);
4049 current_source_node_ = nullptr;
4050 official_playback_position_ = 0;
4051 official_playback_position_needs_update_ = true;
4055 GetCueTimeline().OnReadyStateReset();
4057 UpdateLayoutObject();
4059 StopPeriodicTimers();
4060 removed_from_document_timer_.Stop();
4063 #if BUILDFLAG(IS_TIZEN_TV)
4064 WebString HTMLMediaElement::GetContentMIMEType() {
4065 // If the MIME type is missing or is not meaningful, try to figure it out from
4067 if (content_mime_type_.empty() ||
4068 content_mime_type_ == "application/octet-stream" ||
4069 content_mime_type_ == "text/plain") {
4070 if (current_src_.GetSourceIfVisible().ProtocolIsData())
4071 content_mime_type_ =
4072 MimeTypeFromDataURL(current_src_.GetSourceIfVisible().GetString());
4074 String last_path_component =
4075 current_src_.GetSourceIfVisible().LastPathComponent();
4076 size_t pos = last_path_component.ReverseFind('.');
4077 if (pos != kNotFound) {
4078 String extension = last_path_component.Substring(pos + 1);
4080 MIMETypeRegistry::GetMIMETypeForExtension(extension);
4081 if (!media_type.empty())
4082 content_mime_type_ = media_type;
4086 return WebString(content_mime_type_);
4089 void HTMLMediaElement::RequestReload(const KURL& new_url) {
4090 DCHECK(GetWebMediaPlayer());
4091 DCHECK(new_url.IsValid());
4092 DCHECK(IsSafeToLoadURL(new_url, kComplain));
4093 ResetMediaPlayerAndMediaSource();
4097 void HTMLMediaElement::SetTranslatedURL(const String& url) {
4098 LOG(INFO) << "translated url:" << url;
4099 is_translated_url_ = true;
4100 content_mime_type_ = "";
4101 if (src_object_media_source_handle_) {
4102 DCHECK(!KURL(url).IsEmpty());
4103 current_src_.SetSource(KURL(url),
4104 SourceMetadata::SourceVisibility::kInvisibleToApp);
4106 current_src_.SetSource(KURL(url),
4107 SourceMetadata::SourceVisibility::kVisibleToApp);
4109 RequestReload(current_src_.GetSource());
4112 void HTMLMediaElement::SetParentalRatingResult(bool is_pass) {
4113 LOG(INFO) << "setParentalRatingResult:" << BoolString(is_pass);
4114 if (!GetWebMediaPlayer()) {
4115 LOG(ERROR) << "setParentalRatingResult: mediaplayer not created";
4118 GetWebMediaPlayer()->SetParentalRatingResult(is_pass);
4122 bool HTMLMediaElement::HasPendingActivity() const {
4123 const auto result = HasPendingActivityInternal();
4124 // TODO(dalecurtis): Replace c-style casts in followup patch.
4125 DVLOG(3) << "HasPendingActivity(" << *this << ") = " << result;
4129 bool HTMLMediaElement::HasPendingActivityInternal() const {
4130 // The delaying-the-load-event flag is set by resource selection algorithm
4131 // when looking for a resource to load, before networkState has reached to
4133 if (should_delay_load_event_)
4136 // When networkState is kNetworkLoading, progress and stalled events may be
4139 // When connected to a MediaSource, ignore |network_state_|. The rest
4140 // of this method's logic and the HasPendingActivity() of the various
4141 // MediaSource API objects more precisely indicate whether or not any pending
4142 // activity is expected on the group of connected HTMLMediaElement +
4143 // MediaSource API objects. This lets the group of objects be garbage
4144 // collected if there is no pending activity nor reachability from a GC root,
4145 // even while in kNetworkLoading.
4147 // We use the WebMediaPlayer's network state instead of |network_state_| since
4148 // it's value is unreliable prior to ready state kHaveMetadata.
4149 if (!media_source_attachment_) {
4150 if (!web_media_player_) {
4151 if (network_state_ == kNetworkLoading)
4153 } else if (web_media_player_->GetNetworkState() ==
4154 WebMediaPlayer::kNetworkStateLoading) {
4160 // Disable potential updating of playback position, as that will
4161 // require v8 allocations; not allowed while GCing
4162 // (hasPendingActivity() is called during a v8 GC.)
4163 base::AutoReset<bool> scope(&official_playback_position_needs_update_,
4166 // When playing or if playback may continue, timeupdate events may be fired.
4167 if (CouldPlayIfEnoughData())
4171 // When the seek finishes timeupdate and seeked events will be fired.
4175 // Wait for any pending events to be fired.
4176 if (async_event_queue_->HasPendingEvents())
4182 bool HTMLMediaElement::IsFullscreen() const {
4183 return Fullscreen::IsFullscreenElement(*this);
4186 cc::Layer* HTMLMediaElement::CcLayer() const {
4190 bool HTMLMediaElement::HasClosedCaptions() const {
4194 for (unsigned i = 0; i < text_tracks_->length(); ++i) {
4195 if (text_tracks_->AnonymousIndexedGetter(i)->CanBeRendered())
4202 bool HTMLMediaElement::TextTracksVisible() const {
4203 return text_tracks_visible_;
4207 void HTMLMediaElement::AssertShadowRootChildren(ShadowRoot& shadow_root) {
4209 // There can be up to three children: an interstitial (media remoting or
4210 // picture in picture), text track container, and media controls. The media
4211 // controls has to be the last child if present, and has to be the next
4212 // sibling of the text track container if both present. When present, media
4213 // remoting interstitial has to be the first child.
4214 unsigned number_of_children = shadow_root.CountChildren();
4215 DCHECK_LE(number_of_children, 3u);
4216 Node* first_child = shadow_root.firstChild();
4217 Node* last_child = shadow_root.lastChild();
4218 if (number_of_children == 1) {
4219 DCHECK(first_child->IsTextTrackContainer() ||
4220 first_child->IsMediaControls() ||
4221 first_child->IsMediaRemotingInterstitial() ||
4222 first_child->IsPictureInPictureInterstitial());
4223 } else if (number_of_children == 2) {
4224 DCHECK(first_child->IsTextTrackContainer() ||
4225 first_child->IsMediaRemotingInterstitial() ||
4226 first_child->IsPictureInPictureInterstitial());
4227 DCHECK(last_child->IsTextTrackContainer() || last_child->IsMediaControls());
4228 if (first_child->IsTextTrackContainer())
4229 DCHECK(last_child->IsMediaControls());
4230 } else if (number_of_children == 3) {
4231 Node* second_child = first_child->nextSibling();
4232 DCHECK(first_child->IsMediaRemotingInterstitial() ||
4233 first_child->IsPictureInPictureInterstitial());
4234 DCHECK(second_child->IsTextTrackContainer());
4235 DCHECK(last_child->IsMediaControls());
4240 TextTrackContainer& HTMLMediaElement::EnsureTextTrackContainer() {
4241 UseCounter::Count(GetDocument(), WebFeature::kMediaElementTextTrackContainer);
4243 ShadowRoot& shadow_root = EnsureUserAgentShadowRoot();
4244 AssertShadowRootChildren(shadow_root);
4246 Node* first_child = shadow_root.firstChild();
4247 if (auto* first_child_text_track = DynamicTo<TextTrackContainer>(first_child))
4248 return *first_child_text_track;
4249 Node* to_be_inserted = first_child;
4251 if (first_child && (first_child->IsMediaRemotingInterstitial() ||
4252 first_child->IsPictureInPictureInterstitial())) {
4253 Node* second_child = first_child->nextSibling();
4254 if (auto* second_child_text_track =
4255 DynamicTo<TextTrackContainer>(second_child))
4256 return *second_child_text_track;
4257 to_be_inserted = second_child;
4260 auto* text_track_container = MakeGarbageCollected<TextTrackContainer>(*this);
4262 // The text track container should be inserted before the media controls,
4263 // so that they are rendered behind them.
4264 shadow_root.InsertBefore(text_track_container, to_be_inserted);
4266 AssertShadowRootChildren(shadow_root);
4268 return *text_track_container;
4271 void HTMLMediaElement::UpdateTextTrackDisplay() {
4272 DVLOG(3) << "updateTextTrackDisplay(" << *this << ")";
4274 EnsureTextTrackContainer().UpdateDisplay(
4275 *this, TextTrackContainer::kDidNotStartExposingControls);
4278 SpeechSynthesisBase* HTMLMediaElement::SpeechSynthesis() {
4279 if (!speech_synthesis_) {
4281 SpeechSynthesisBase::Create(*(GetDocument().domWindow()));
4282 speech_synthesis_->SetOnSpeakingCompletedCallback(WTF::BindRepeating(
4283 &HTMLMediaElement::OnSpeakingCompleted, WrapWeakPersistent(this)));
4285 return speech_synthesis_.Get();
4288 void HTMLMediaElement::MediaControlsDidBecomeVisible() {
4289 DVLOG(3) << "mediaControlsDidBecomeVisible(" << *this << ")";
4291 // When the user agent starts exposing a user interface for a video element,
4292 // the user agent should run the rules for updating the text track rendering
4293 // of each of the text tracks in the video element's list of text tracks ...
4294 if (IsHTMLVideoElement() && TextTracksVisible()) {
4295 EnsureTextTrackContainer().UpdateDisplay(
4296 *this, TextTrackContainer::kDidStartExposingControls);
4300 void HTMLMediaElement::SetTextTrackKindUserPreferenceForAllMediaElements(
4301 Document* document) {
4302 auto it = DocumentToElementSetMap().find(document);
4303 if (it == DocumentToElementSetMap().end())
4306 WeakMediaElementSet& elements = *it->value;
4307 for (const auto& element : elements)
4308 element->AutomaticTrackSelectionForUpdatedUserPreference();
4311 void HTMLMediaElement::AutomaticTrackSelectionForUpdatedUserPreference() {
4312 if (!text_tracks_ || !text_tracks_->length())
4315 MarkCaptionAndSubtitleTracksAsUnconfigured();
4316 processing_preference_change_ = true;
4317 text_tracks_visible_ = false;
4318 HonorUserPreferencesForAutomaticTextTrackSelection();
4319 processing_preference_change_ = false;
4321 // If a track is set to 'showing' post performing automatic track selection,
4322 // set text tracks state to visible to update the CC button and display the
4324 text_tracks_visible_ = text_tracks_->HasShowingTracks();
4325 UpdateTextTrackDisplay();
4328 void HTMLMediaElement::MarkCaptionAndSubtitleTracksAsUnconfigured() {
4332 // Mark all tracks as not "configured" so that
4333 // honorUserPreferencesForAutomaticTextTrackSelection() will reconsider
4334 // which tracks to display in light of new user preferences (e.g. default
4335 // tracks should not be displayed if the user has turned off captions and
4336 // non-default tracks should be displayed based on language preferences if
4337 // the user has turned captions on).
4338 for (unsigned i = 0; i < text_tracks_->length(); ++i) {
4339 TextTrack* text_track = text_tracks_->AnonymousIndexedGetter(i);
4340 if (text_track->IsVisualKind())
4341 text_track->SetHasBeenConfigured(false);
4345 uint64_t HTMLMediaElement::webkitAudioDecodedByteCount() const {
4346 if (!web_media_player_)
4348 return web_media_player_->AudioDecodedByteCount();
4351 uint64_t HTMLMediaElement::webkitVideoDecodedByteCount() const {
4352 if (!web_media_player_)
4354 return web_media_player_->VideoDecodedByteCount();
4357 bool HTMLMediaElement::IsURLAttribute(const Attribute& attribute) const {
4358 return attribute.GetName() == html_names::kSrcAttr ||
4359 HTMLElement::IsURLAttribute(attribute);
4362 void HTMLMediaElement::SetShouldDelayLoadEvent(bool should_delay) {
4363 if (should_delay_load_event_ == should_delay)
4366 DVLOG(3) << "setShouldDelayLoadEvent(" << *this << ", "
4367 << BoolString(should_delay) << ")";
4369 should_delay_load_event_ = should_delay;
4371 GetDocument().IncrementLoadEventDelayCount();
4373 GetDocument().DecrementLoadEventDelayCount();
4376 MediaControls* HTMLMediaElement::GetMediaControls() const {
4377 return media_controls_.Get();
4380 void HTMLMediaElement::EnsureMediaControls() {
4381 if (GetMediaControls())
4384 ShadowRoot& shadow_root = EnsureUserAgentShadowRoot();
4385 UseCounterMuteScope scope(*this);
4387 CoreInitializer::GetInstance().CreateMediaControls(*this, shadow_root);
4389 // The media controls should be inserted after the text track container,
4390 // so that they are rendered in front of captions and subtitles. This check
4391 // is verifying the contract.
4392 AssertShadowRootChildren(shadow_root);
4395 void HTMLMediaElement::UpdateControlsVisibility() {
4399 bool native_controls = ShouldShowControls(RecordMetricsBehavior::kDoRecord);
4401 // When LazyInitializeMediaControls is enabled, initialize the controls only
4402 // if native controls should be used or if using the cast overlay.
4403 if (!RuntimeEnabledFeatures::LazyInitializeMediaControlsEnabled() ||
4404 RuntimeEnabledFeatures::MediaCastOverlayButtonEnabled() ||
4406 EnsureMediaControls();
4408 // TODO(mlamouri): this doesn't sound needed but the following tests, on
4409 // Android fails when removed:
4410 // fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html
4411 GetMediaControls()->Reset();
4414 if (native_controls)
4415 GetMediaControls()->MaybeShow();
4416 else if (GetMediaControls())
4417 GetMediaControls()->Hide();
4419 if (web_media_player_)
4420 web_media_player_->OnHasNativeControlsChanged(native_controls);
4423 CueTimeline& HTMLMediaElement::GetCueTimeline() {
4425 cue_timeline_ = MakeGarbageCollected<CueTimeline>(*this);
4426 return *cue_timeline_;
4429 void HTMLMediaElement::ConfigureTextTrackDisplay() {
4430 DCHECK(text_tracks_);
4431 DVLOG(3) << "configureTextTrackDisplay(" << *this << ")";
4433 if (processing_preference_change_)
4436 bool have_visible_text_track = text_tracks_->HasShowingTracks();
4437 text_tracks_visible_ = have_visible_text_track;
4439 if (!have_visible_text_track && !GetMediaControls())
4442 // Note: The "time marches on" algorithm |CueTimeline::TimeMarchesOn| runs
4443 // the "rules for updating the text track rendering" (updateTextTrackDisplay)
4444 // only for "affected tracks", i.e. tracks where the the active cues have
4445 // changed. This misses cues in tracks that changed mode between hidden and
4446 // showing. This appears to be a spec bug, which we work around here:
4447 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28236
4448 UpdateTextTrackDisplay();
4451 // TODO(srirama.m): Merge it to resetMediaElement if possible and remove it.
4452 void HTMLMediaElement::ResetMediaPlayerAndMediaSource() {
4456 AudioSourceProviderClientLockScope scope(*this);
4457 ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
4460 if (audio_source_node_)
4461 GetAudioSourceProvider().SetClient(audio_source_node_);
4464 void HTMLMediaElement::SetAudioSourceNode(
4465 AudioSourceProviderClient* source_node) {
4466 DCHECK(IsMainThread());
4467 audio_source_node_ = source_node;
4469 // No need to lock the |audio_source_node| because it locks itself when
4470 // setFormat() is invoked.
4471 GetAudioSourceProvider().SetClient(audio_source_node_);
4474 WebMediaPlayer::CorsMode HTMLMediaElement::CorsMode() const {
4475 const AtomicString& cross_origin_mode =
4476 FastGetAttribute(html_names::kCrossoriginAttr);
4477 if (cross_origin_mode.IsNull())
4478 return WebMediaPlayer::kCorsModeUnspecified;
4479 if (EqualIgnoringASCIICase(cross_origin_mode, "use-credentials"))
4480 return WebMediaPlayer::kCorsModeUseCredentials;
4481 return WebMediaPlayer::kCorsModeAnonymous;
4484 void HTMLMediaElement::SetCcLayer(cc::Layer* cc_layer) {
4485 if (cc_layer == cc_layer_)
4488 SetNeedsCompositingUpdate();
4489 cc_layer_ = cc_layer;
4492 void HTMLMediaElement::MediaSourceOpened(WebMediaSource* web_media_source) {
4493 SetShouldDelayLoadEvent(false);
4494 media_source_attachment_->CompleteAttachingToMediaElement(
4495 media_source_tracer_, base::WrapUnique(web_media_source));
4498 bool HTMLMediaElement::IsInteractiveContent() const {
4499 return FastHasAttribute(html_names::kControlsAttr);
4502 void HTMLMediaElement::BindMediaPlayerReceiver(
4503 mojo::PendingAssociatedReceiver<media::mojom::blink::MediaPlayer>
4505 media_player_receiver_set_->Value().Add(
4506 std::move(receiver),
4507 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
4510 void HTMLMediaElement::OnSpeakingCompleted() {
4515 void HTMLMediaElement::Trace(Visitor* visitor) const {
4516 visitor->Trace(audio_source_node_);
4517 visitor->Trace(speech_synthesis_);
4518 visitor->Trace(load_timer_);
4519 visitor->Trace(audio_tracks_timer_);
4520 visitor->Trace(removed_from_document_timer_);
4521 visitor->Trace(played_time_ranges_);
4522 visitor->Trace(async_event_queue_);
4523 visitor->Trace(error_);
4524 visitor->Trace(current_source_node_);
4525 visitor->Trace(next_child_node_to_consider_);
4526 visitor->Trace(deferred_load_timer_);
4527 visitor->Trace(media_source_tracer_);
4528 visitor->Trace(audio_tracks_);
4529 visitor->Trace(video_tracks_);
4530 visitor->Trace(cue_timeline_);
4531 visitor->Trace(text_tracks_);
4532 visitor->Trace(text_tracks_when_resource_selection_began_);
4533 visitor->Trace(play_promise_resolvers_);
4534 visitor->Trace(play_promise_resolve_list_);
4535 visitor->Trace(play_promise_reject_list_);
4536 visitor->Trace(audio_source_provider_);
4537 visitor->Trace(src_object_stream_descriptor_);
4538 visitor->Trace(src_object_media_source_handle_);
4539 visitor->Trace(autoplay_policy_);
4540 visitor->Trace(media_controls_);
4541 visitor->Trace(controls_list_);
4542 visitor->Trace(lazy_load_intersection_observer_);
4543 visitor->Trace(media_player_host_remote_);
4544 visitor->Trace(media_player_observer_remote_set_);
4545 visitor->Trace(media_player_receiver_set_);
4546 visitor->Trace(opener_document_);
4547 visitor->Trace(opener_context_observer_);
4548 Supplementable<HTMLMediaElement>::Trace(visitor);
4549 HTMLElement::Trace(visitor);
4550 ExecutionContextLifecycleStateObserver::Trace(visitor);
4553 void HTMLMediaElement::CreatePlaceholderTracksIfNecessary() {
4554 // Create a placeholder audio track if the player says it has audio but it
4555 // didn't explicitly announce the tracks.
4556 if (HasAudio() && !audioTracks().length()) {
4557 AddAudioTrack("audio", WebMediaPlayerClient::kAudioTrackKindMain,
4558 "Audio Track", "", true);
4561 // Create a placeholder video track if the player says it has video but it
4562 // didn't explicitly announce the tracks.
4563 if (HasVideo() && !videoTracks().length()) {
4564 AddVideoTrack("video", WebMediaPlayerClient::kVideoTrackKindMain,
4565 "Video Track", "", true);
4569 void HTMLMediaElement::SetNetworkState(NetworkState state,
4570 bool update_media_controls) {
4571 if (network_state_ == state)
4574 network_state_ = state;
4575 if (update_media_controls && GetMediaControls())
4576 GetMediaControls()->NetworkStateChanged();
4579 void HTMLMediaElement::VideoWillBeDrawnToCanvas() const {
4580 DCHECK(IsHTMLVideoElement());
4581 UseCounter::Count(GetDocument(), WebFeature::kVideoInCanvas);
4582 autoplay_policy_->VideoWillBeDrawnToCanvas();
4585 void HTMLMediaElement::ScheduleResolvePlayPromises() {
4586 // TODO(mlamouri): per spec, we should create a new task but we can't create
4587 // a new cancellable task without cancelling the previous one. There are two
4588 // approaches then: cancel the previous task and create a new one with the
4589 // appended promise list or append the new promise to the current list. The
4590 // latter approach is preferred because it might be the less observable
4592 DCHECK(play_promise_resolve_list_.empty() ||
4593 play_promise_resolve_task_handle_.IsActive());
4594 if (play_promise_resolvers_.empty())
4597 play_promise_resolve_list_.AppendVector(play_promise_resolvers_);
4598 play_promise_resolvers_.clear();
4600 if (play_promise_resolve_task_handle_.IsActive())
4603 play_promise_resolve_task_handle_ = PostCancellableTask(
4604 *GetDocument().GetTaskRunner(TaskType::kMediaElementEvent), FROM_HERE,
4605 WTF::BindOnce(&HTMLMediaElement::ResolveScheduledPlayPromises,
4606 WrapWeakPersistent(this)));
4609 void HTMLMediaElement::ScheduleRejectPlayPromises(PlayPromiseError code) {
4610 // TODO(mlamouri): per spec, we should create a new task but we can't create
4611 // a new cancellable task without cancelling the previous one. There are two
4612 // approaches then: cancel the previous task and create a new one with the
4613 // appended promise list or append the new promise to the current list. The
4614 // latter approach is preferred because it might be the less observable
4616 DCHECK(play_promise_reject_list_.empty() ||
4617 play_promise_reject_task_handle_.IsActive());
4618 if (play_promise_resolvers_.empty())
4621 play_promise_reject_list_.AppendVector(play_promise_resolvers_);
4622 play_promise_resolvers_.clear();
4624 if (play_promise_reject_task_handle_.IsActive())
4627 // TODO(nhiroki): Bind this error code to a cancellable task instead of a
4629 play_promise_error_code_ = code;
4630 play_promise_reject_task_handle_ = PostCancellableTask(
4631 *GetDocument().GetTaskRunner(TaskType::kMediaElementEvent), FROM_HERE,
4632 WTF::BindOnce(&HTMLMediaElement::RejectScheduledPlayPromises,
4633 WrapWeakPersistent(this)));
4636 void HTMLMediaElement::ScheduleNotifyPlaying() {
4637 ScheduleEvent(event_type_names::kPlaying);
4638 ScheduleResolvePlayPromises();
4641 void HTMLMediaElement::ResolveScheduledPlayPromises() {
4642 for (auto& resolver : play_promise_resolve_list_)
4643 resolver->Resolve();
4645 play_promise_resolve_list_.clear();
4648 void HTMLMediaElement::RejectScheduledPlayPromises() {
4649 if (play_promise_error_code_ == PlayPromiseError::kNotSupported) {
4650 RejectPlayPromisesInternal(
4651 DOMExceptionCode::kNotSupportedError,
4652 "Failed to load because no supported source was found.");
4656 const char* reason = "";
4657 switch (play_promise_error_code_) {
4658 case PlayPromiseError::kPaused_Unknown:
4659 reason = " because the media paused";
4661 case PlayPromiseError::kPaused_PauseCalled:
4662 reason = " by a call to pause()";
4664 case PlayPromiseError::kPaused_EndOfPlayback:
4665 reason = " by end of playback";
4667 case PlayPromiseError::kPaused_RemovedFromDocument:
4668 reason = " because the media was removed from the document";
4670 case PlayPromiseError::kPaused_AutoplayAutoPause:
4671 reason = " because autoplaying background media was paused to save power";
4673 case PlayPromiseError::kPaused_BackgroundVideoOptimization:
4674 reason = " because video-only background media was paused to save power";
4676 case PlayPromiseError::kPaused_SuspendedPlayerIdleTimeout:
4677 reason = " because the player was been suspended and became idle";
4679 case PlayPromiseError::kPaused_RemotePlayStateChange:
4680 reason = " by a pause request from a remote media player";
4682 case PlayPromiseError::kPaused_PauseRequestedByUser:
4683 reason = " because a pause was requested by the user";
4685 case PlayPromiseError::kPaused_PauseRequestedInternally:
4686 reason = " because a pause was requested by the browser";
4688 case PlayPromiseError::kNotSupported:
4691 RejectPlayPromisesInternal(
4692 DOMExceptionCode::kAbortError,
4694 "The play() request was interrupted%s. https://goo.gl/LdLk22",
4698 void HTMLMediaElement::RejectPlayPromises(DOMExceptionCode code,
4699 const String& message) {
4700 play_promise_reject_list_.AppendVector(play_promise_resolvers_);
4701 play_promise_resolvers_.clear();
4702 RejectPlayPromisesInternal(code, message);
4705 void HTMLMediaElement::RejectPlayPromisesInternal(DOMExceptionCode code,
4706 const String& message) {
4707 DCHECK(code == DOMExceptionCode::kAbortError ||
4708 code == DOMExceptionCode::kNotSupportedError);
4709 for (auto& resolver : play_promise_reject_list_)
4710 resolver->Reject(MakeGarbageCollected<DOMException>(code, message));
4712 play_promise_reject_list_.clear();
4715 void HTMLMediaElement::OnRemovedFromDocumentTimerFired(TimerBase*) {
4716 if (InActiveDocument())
4719 // Video should not pause when playing in Picture-in-Picture and subsequently
4720 // removed from the Document.
4721 if (!PictureInPictureController::IsElementInPictureInPicture(this))
4722 PauseInternal(PlayPromiseError::kPaused_RemovedFromDocument);
4723 #if BUILDFLAG(IS_TIZEN_TV)
4724 if (!media_source_attachment_) {
4725 LOG(INFO) << "(" << (void*)this << ")"
4726 << "HTMLMediaElement::removedFrom(clear)";
4728 network_state_ = kNetworkEmpty;
4729 ready_state_ = kHaveNothing;
4730 ready_state_maximum_ = kHaveNothing;
4732 // Clear any pending events
4733 SetShouldDelayLoadEvent(false);
4734 async_event_queue_->CancelAllEvents();
4739 void HTMLMediaElement::AudioSourceProviderImpl::Wrap(
4740 scoped_refptr<WebAudioSourceProviderImpl> provider) {
4741 base::AutoLock locker(provide_input_lock);
4743 if (web_audio_source_provider_ && provider != web_audio_source_provider_)
4744 web_audio_source_provider_->SetClient(nullptr);
4746 web_audio_source_provider_ = std::move(provider);
4747 if (web_audio_source_provider_)
4748 web_audio_source_provider_->SetClient(client_.Get());
4751 void HTMLMediaElement::AudioSourceProviderImpl::SetClient(
4752 AudioSourceProviderClient* client) {
4753 base::AutoLock locker(provide_input_lock);
4756 client_ = MakeGarbageCollected<HTMLMediaElement::AudioClientImpl>(client);
4760 if (web_audio_source_provider_)
4761 web_audio_source_provider_->SetClient(client_.Get());
4764 void HTMLMediaElement::AudioSourceProviderImpl::ProvideInput(
4766 int frames_to_process) {
4769 base::AutoTryLock try_locker(provide_input_lock);
4770 if (!try_locker.is_acquired() || !web_audio_source_provider_ ||
4776 // Wrap the AudioBus channel data using WebVector.
4777 unsigned n = bus->NumberOfChannels();
4778 WebVector<float*> web_audio_data(n);
4779 for (unsigned i = 0; i < n; ++i)
4780 web_audio_data[i] = bus->Channel(i)->MutableData();
4782 web_audio_source_provider_->ProvideInput(web_audio_data, frames_to_process);
4785 void HTMLMediaElement::AudioClientImpl::SetFormat(uint32_t number_of_channels,
4786 float sample_rate) {
4788 client_->SetFormat(number_of_channels, sample_rate);
4791 void HTMLMediaElement::AudioClientImpl::Trace(Visitor* visitor) const {
4792 visitor->Trace(client_);
4795 void HTMLMediaElement::AudioSourceProviderImpl::Trace(Visitor* visitor) const {
4796 visitor->Trace(client_);
4799 bool HTMLMediaElement::HasNativeControls() {
4800 return ShouldShowControls(RecordMetricsBehavior::kDoRecord);
4803 bool HTMLMediaElement::IsAudioElement() {
4804 return IsHTMLAudioElement();
4807 DisplayType HTMLMediaElement::GetDisplayType() const {
4808 return IsFullscreen() ? DisplayType::kFullscreen : DisplayType::kInline;
4811 gfx::ColorSpace HTMLMediaElement::TargetColorSpace() {
4812 LocalFrame* frame = GetDocument().GetFrame();
4814 return gfx::ColorSpace();
4815 return frame->GetPage()
4817 .GetScreenInfo(*frame)
4818 .display_color_spaces.GetScreenInfoColorSpace();
4821 bool HTMLMediaElement::WasAutoplayInitiated() {
4822 return autoplay_policy_->WasAutoplayInitiated();
4825 void HTMLMediaElement::ResumePlayback() {
4826 autoplay_policy_->EnsureAutoplayInitiatedSet();
4830 void HTMLMediaElement::PausePlayback(PauseReason pause_reason) {
4831 switch (pause_reason) {
4832 case PauseReason::kUnknown:
4833 return PauseInternal(PlayPromiseError::kPaused_Unknown);
4834 case PauseReason::kBackgroundVideoOptimization:
4835 return PauseInternal(
4836 PlayPromiseError::kPaused_BackgroundVideoOptimization);
4837 case PauseReason::kSuspendedPlayerIdleTimeout:
4838 return PauseInternal(
4839 PlayPromiseError::kPaused_SuspendedPlayerIdleTimeout);
4840 case PauseReason::kRemotePlayStateChange:
4841 return PauseInternal(PlayPromiseError::kPaused_RemotePlayStateChange);
4846 void HTMLMediaElement::DidPlayerStartPlaying() {
4847 for (auto& observer : media_player_observer_remote_set_->Value())
4848 observer->OnMediaPlaying();
4851 void HTMLMediaElement::DidPlayerPaused(bool stream_ended) {
4852 for (auto& observer : media_player_observer_remote_set_->Value())
4853 observer->OnMediaPaused(stream_ended);
4856 void HTMLMediaElement::DidPlayerMutedStatusChange(bool muted) {
4857 for (auto& observer : media_player_observer_remote_set_->Value())
4858 observer->OnMutedStatusChanged(muted);
4861 void HTMLMediaElement::DidMediaMetadataChange(
4864 media::AudioCodec audio_codec,
4865 media::VideoCodec video_codec,
4866 media::MediaContentType media_content_type,
4867 bool is_encrypted_media) {
4868 for (auto& observer : media_player_observer_remote_set_->Value()) {
4869 observer->OnMediaMetadataChanged(has_audio, has_video, media_content_type);
4872 if (video_codec == media::VideoCodec::kUnknown &&
4873 audio_codec == media::AudioCodec::kUnknown) {
4876 video_codec_ = video_codec;
4877 audio_codec_ = audio_codec;
4878 is_encrypted_media_ = is_encrypted_media;
4879 OnRemotePlaybackMetadataChange();
4882 void HTMLMediaElement::DidPlayerMediaPositionStateChange(
4883 double playback_rate,
4884 base::TimeDelta duration,
4885 base::TimeDelta position,
4886 bool end_of_media) {
4887 for (auto& observer : media_player_observer_remote_set_->Value()) {
4888 observer->OnMediaPositionStateChanged(
4889 media_session::mojom::blink::MediaPosition::New(
4890 playback_rate, duration, position, base::TimeTicks::Now(),
4895 void HTMLMediaElement::DidDisableAudioOutputSinkChanges() {
4896 for (auto& observer : media_player_observer_remote_set_->Value())
4897 observer->OnAudioOutputSinkChangingDisabled();
4900 void HTMLMediaElement::DidUseAudioServiceChange(bool uses_audio_service) {
4901 for (auto& observer : media_player_observer_remote_set_->Value()) {
4902 observer->OnUseAudioServiceChanged(uses_audio_service);
4906 void HTMLMediaElement::DidPlayerSizeChange(const gfx::Size& size) {
4907 for (auto& observer : media_player_observer_remote_set_->Value())
4908 observer->OnMediaSizeChanged(size);
4911 void HTMLMediaElement::OnRemotePlaybackDisabled(bool disabled) {
4912 if (is_remote_playback_disabled_ == disabled)
4914 is_remote_playback_disabled_ = disabled;
4915 OnRemotePlaybackMetadataChange();
4918 #if defined(TIZEN_MULTIMEDIA)
4919 void HTMLMediaElement::SuspendPlayer() {
4920 progress_event_timer_.Stop();
4921 ScheduleEvent(event_type_names::kSuspend);
4922 SetShouldDelayLoadEvent(false);
4923 SetNetworkState(kNetworkIdle);
4926 void HTMLMediaElement::OnLivePlaybackComplete() {
4927 live_playback_complete_ = true;
4931 media::mojom::blink::MediaPlayerHost&
4932 HTMLMediaElement::GetMediaPlayerHostRemote() {
4933 // It is an error to call this before having access to the document's frame.
4934 DCHECK(GetDocument().GetFrame());
4935 if (!media_player_host_remote_->Value().is_bound()) {
4938 ->GetRemoteNavigationAssociatedInterfaces()
4940 media_player_host_remote_->Value().BindNewEndpointAndPassReceiver(
4941 GetDocument().GetTaskRunner(TaskType::kInternalMedia)));
4943 return *media_player_host_remote_->Value().get();
4946 mojo::PendingAssociatedReceiver<media::mojom::blink::MediaPlayerObserver>
4947 HTMLMediaElement::AddMediaPlayerObserverAndPassReceiver() {
4948 mojo::PendingAssociatedRemote<media::mojom::blink::MediaPlayerObserver>
4950 auto observer_receiver = observer.InitWithNewEndpointAndPassReceiver();
4951 media_player_observer_remote_set_->Value().Add(
4952 std::move(observer),
4953 GetDocument().GetTaskRunner(TaskType::kInternalMedia));
4954 return observer_receiver;
4957 void HTMLMediaElement::RequestPlay() {
4958 LocalFrame* frame = GetDocument().GetFrame();
4960 LocalFrame::NotifyUserActivation(
4961 frame, mojom::blink::UserActivationNotificationType::kInteraction);
4963 autoplay_policy_->EnsureAutoplayInitiatedSet();
4967 void HTMLMediaElement::RequestPause(bool triggered_by_user) {
4968 if (triggered_by_user) {
4969 LocalFrame* frame = GetDocument().GetFrame();
4971 LocalFrame::NotifyUserActivation(
4972 frame, mojom::blink::UserActivationNotificationType::kInteraction);
4975 PauseInternal(triggered_by_user
4976 ? PlayPromiseError::kPaused_PauseRequestedByUser
4977 : PlayPromiseError::kPaused_PauseRequestedInternally);
4980 void HTMLMediaElement::RequestSeekForward(base::TimeDelta seek_time) {
4981 double seconds = seek_time.InSecondsF();
4982 DCHECK_GE(seconds, 0) << "Attempted to seek by a negative number of seconds";
4983 setCurrentTime(currentTime() + seconds);
4986 void HTMLMediaElement::RequestSeekBackward(base::TimeDelta seek_time) {
4987 double seconds = seek_time.InSecondsF();
4988 DCHECK_GE(seconds, 0) << "Attempted to seek by a negative number of seconds";
4989 setCurrentTime(currentTime() - seconds);
4992 void HTMLMediaElement::RequestSeekTo(base::TimeDelta seek_time) {
4993 setCurrentTime(seek_time.InSecondsF());
4996 void HTMLMediaElement::RequestMute(bool mute) {
5000 void HTMLMediaElement::SetVolumeMultiplier(double multiplier) {
5001 if (web_media_player_)
5002 web_media_player_->SetVolumeMultiplier(multiplier);
5005 void HTMLMediaElement::SetPowerExperimentState(bool enabled) {
5006 if (web_media_player_)
5007 web_media_player_->SetPowerExperimentState(enabled);
5010 void HTMLMediaElement::SetAudioSinkId(const String& sink_id) {
5011 auto* audio_output_controller = AudioOutputDeviceController::From(*this);
5012 DCHECK(audio_output_controller);
5014 audio_output_controller->SetSinkId(sink_id);
5017 void HTMLMediaElement::SuspendForFrameClosed() {
5018 if (web_media_player_)
5019 web_media_player_->SuspendForFrameClosed();
5022 bool HTMLMediaElement::MediaShouldBeOpaque() const {
5023 return !IsMediaDataCorsSameOrigin() && ready_state_ < kHaveMetadata &&
5024 EffectivePreloadType() != WebMediaPlayer::kPreloadNone;
5027 void HTMLMediaElement::SetError(MediaError* error) {
5031 DLOG(ERROR) << __func__ << ": {code=" << error->code()
5032 << ", message=" << error->message() << "}";
5033 if (media_source_attachment_)
5034 media_source_attachment_->OnElementError();
5038 void HTMLMediaElement::ReportCurrentTimeToMediaSource() {
5039 if (!media_source_attachment_)
5042 // See MediaSourceAttachment::OnElementTimeUpdate() for why the attachment
5043 // needs our currentTime.
5044 media_source_attachment_->OnElementTimeUpdate(currentTime());
5047 void HTMLMediaElement::OnRemotePlaybackMetadataChange() {
5048 if (remote_playback_client_) {
5049 remote_playback_client_->MediaMetadataChanged(video_codec_, audio_codec_);
5051 for (auto& observer : media_player_observer_remote_set_->Value()) {
5052 observer->OnRemotePlaybackMetadataChange(
5053 media_session::mojom::blink::RemotePlaybackMetadata::New(
5054 WTF::String(media::GetCodecName(video_codec_)),
5055 WTF::String(media::GetCodecName(audio_codec_)),
5056 is_remote_playback_disabled_, is_remote_rendering_,
5057 WTF::String(remote_device_friendly_name_), is_encrypted_media_));
5061 HTMLMediaElement::OpenerContextObserver::OpenerContextObserver(
5062 HTMLMediaElement* element)
5063 : element_(element) {}
5065 HTMLMediaElement::OpenerContextObserver::~OpenerContextObserver() = default;
5067 void HTMLMediaElement::OpenerContextObserver::Trace(Visitor* visitor) const {
5068 ContextLifecycleObserver::Trace(visitor);
5069 visitor->Trace(element_);
5072 void HTMLMediaElement::OpenerContextObserver::ContextDestroyed() {
5073 element_->AttachToNewFrame();
5076 #if defined(TIZEN_MULTIMEDIA_SUPPORT)
5077 void HTMLMediaElement::MediaPlayerHidden() {
5078 LOG(INFO) << "Media player hidden: " << this;
5079 // TODO(m.debski): It should store periodic timers and restore on shown.
5080 // Currently they will still fire, but will return immediately.
5081 suppress_events_ = true;
5084 void HTMLMediaElement::MediaPlayerShown() {
5085 LOG(INFO) << "Media player shown: " << this;
5086 suppress_events_ = false;
5089 void HTMLMediaElement::Suspend() {
5090 LOG(INFO) << "Suspend(" << (void*)this << ")";
5091 if (!GetWebMediaPlayer())
5094 #if defined(SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE)
5095 if (media_source_attachment_) {
5096 // Send suspend event to the source before WebMediaPlayer suspends backend.
5097 media_source_attachment_->OnSuspend(media_source_tracer_);
5099 #endif // SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE
5101 GetWebMediaPlayer()->Suspend();
5104 void HTMLMediaElement::Resume() {
5105 LOG(INFO) << "Resume(" << (void*)this << ")";
5106 if (!GetWebMediaPlayer())
5109 #if BUILDFLAG(IS_TIZEN_TV)
5110 // IsViewResumedByTabSwitching, return true on the tab which has no
5111 // floatwindow, otherwise it is false
5112 if (GetDocument().GetPage() &&
5113 GetDocument().GetPage()->IsViewResumedByTabSwitching()) {
5114 GetWebMediaPlayer()->Deactivate();
5115 LOG(INFO) << "is_deactivate_ TRUE";
5116 is_deactivate_ = true;
5117 } else if (is_deactivate_) {
5118 GetWebMediaPlayer()->Activate();
5119 LOG(INFO) << "is_deactivate_ FALSE";
5120 is_deactivate_ = false;
5124 GetWebMediaPlayer()->Resume();
5126 #if defined(SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE)
5127 if (media_source_attachment_) {
5128 // Send suspend event to the source after WebMediaPlayer resumes backend.
5129 media_source_attachment_->OnResume(media_source_tracer_);
5131 #endif // SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE
5134 void HTMLMediaElement::ActivatePlayer() {
5135 if (!GetWebMediaPlayer())
5138 GetWebMediaPlayer()->Activate();
5142 STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveNothing,
5143 HTMLMediaElement::kHaveNothing);
5144 STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveMetadata,
5145 HTMLMediaElement::kHaveMetadata);
5146 STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveCurrentData,
5147 HTMLMediaElement::kHaveCurrentData);
5148 STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveFutureData,
5149 HTMLMediaElement::kHaveFutureData);
5150 STATIC_ASSERT_ENUM(WebMediaPlayer::kReadyStateHaveEnoughData,
5151 HTMLMediaElement::kHaveEnoughData);
5153 } // namespace blink