1/*
2 * Copyright (C) 2007-2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "HTMLMediaElement.h"
28
29#if ENABLE(VIDEO)
30
31#include "ApplicationCacheHost.h"
32#include "ApplicationCacheResource.h"
33#include "Attribute.h"
34#include "Blob.h"
35#include "CSSPropertyNames.h"
36#include "CSSValueKeywords.h"
37#include "ChromeClient.h"
38#include "CommonVM.h"
39#include "ContentRuleListResults.h"
40#include "ContentSecurityPolicy.h"
41#include "ContentType.h"
42#include "CookieJar.h"
43#include "DeprecatedGlobalSettings.h"
44#include "DiagnosticLoggingClient.h"
45#include "DiagnosticLoggingKeys.h"
46#include "Document.h"
47#include "DocumentLoader.h"
48#include "ElementChildIterator.h"
49#include "EventNames.h"
50#include "Frame.h"
51#include "FrameLoader.h"
52#include "FrameLoaderClient.h"
53#include "FrameView.h"
54#include "FullscreenManager.h"
55#include "HTMLParserIdioms.h"
56#include "HTMLSourceElement.h"
57#include "HTMLVideoElement.h"
58#include "InspectorInstrumentation.h"
59#include "JSDOMException.h"
60#include "JSDOMPromiseDeferred.h"
61#include "JSHTMLMediaElement.h"
62#include "Logging.h"
63#include "MIMETypeRegistry.h"
64#include "MediaController.h"
65#include "MediaControls.h"
66#include "MediaDocument.h"
67#include "MediaError.h"
68#include "MediaFragmentURIParser.h"
69#include "MediaList.h"
70#include "MediaPlayer.h"
71#include "MediaQueryEvaluator.h"
72#include "MediaResourceLoader.h"
73#include "NetworkingContext.h"
74#include "Page.h"
75#include "PageGroup.h"
76#include "PlatformMediaSessionManager.h"
77#include "ProgressTracker.h"
78#include "Quirks.h"
79#include "RegistrableDomain.h"
80#include "RenderLayerCompositor.h"
81#include "RenderTheme.h"
82#include "RenderVideo.h"
83#include "RenderView.h"
84#include "ResourceLoadInfo.h"
85#include "ScriptController.h"
86#include "ScriptDisallowedScope.h"
87#include "ScriptSourceCode.h"
88#include "SecurityOriginData.h"
89#include "SecurityPolicy.h"
90#include "Settings.h"
91#include "ShadowRoot.h"
92#include "TimeRanges.h"
93#include "UserContentController.h"
94#include "UserGestureIndicator.h"
95#include "VideoPlaybackQuality.h"
96#include <JavaScriptCore/Uint8Array.h>
97#include <limits>
98#include <pal/SessionID.h>
99#include <pal/system/SleepDisabler.h>
100#include <wtf/Algorithms.h>
101#include <wtf/IsoMallocInlines.h>
102#include <wtf/Language.h>
103#include <wtf/MathExtras.h>
104#include <wtf/MemoryPressureHandler.h>
105#include <wtf/Ref.h>
106#include <wtf/text/CString.h>
107
108#if ENABLE(VIDEO_TRACK)
109#include "AudioTrackList.h"
110#include "HTMLTrackElement.h"
111#include "InbandGenericTextTrack.h"
112#include "InbandTextTrackPrivate.h"
113#include "InbandWebVTTTextTrack.h"
114#include "RuntimeEnabledFeatures.h"
115#include "TextTrackCueList.h"
116#include "TextTrackList.h"
117#include "VideoTrackList.h"
118#endif
119
120#if ENABLE(WEB_AUDIO)
121#include "AudioSourceProvider.h"
122#include "MediaElementAudioSourceNode.h"
123#endif
124
125#if PLATFORM(IOS_FAMILY)
126#include "RuntimeApplicationChecks.h"
127#include "VideoFullscreenInterfaceAVKit.h"
128#endif
129
130#if ENABLE(WIRELESS_PLAYBACK_TARGET)
131#include "WebKitPlaybackTargetAvailabilityEvent.h"
132#endif
133
134#if ENABLE(MEDIA_SESSION)
135#include "MediaSession.h"
136#endif
137
138#if ENABLE(MEDIA_SOURCE)
139#include "DOMWindow.h"
140#include "MediaSource.h"
141#endif
142
143#if ENABLE(MEDIA_STREAM)
144#include "DOMURL.h"
145#include "MediaStream.h"
146#include "MediaStreamRegistry.h"
147#endif
148
149#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
150#include "WebKitMediaKeyNeededEvent.h"
151#include "WebKitMediaKeys.h"
152#endif
153
154#if ENABLE(ENCRYPTED_MEDIA)
155#include "MediaEncryptedEvent.h"
156#include "MediaKeys.h"
157#endif
158
159#if ENABLE(MEDIA_CONTROLS_SCRIPT)
160#include "JSMediaControlsHost.h"
161#include "MediaControlsHost.h"
162#include <JavaScriptCore/ScriptObject.h>
163#endif
164
165#if ENABLE(ENCRYPTED_MEDIA)
166#include "NotImplemented.h"
167#endif
168
169#if PLATFORM(IOS_FAMILY) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
170#include "VideoFullscreenModel.h"
171#endif
172
173namespace WTF {
174template <>
175struct LogArgument<URL> {
176 static String toString(const URL& url)
177 {
178#if !LOG_DISABLED
179 static const unsigned maximumURLLengthForLogging = 512;
180
181 if (url.string().length() < maximumURLLengthForLogging)
182 return url.string();
183 return url.string().substring(0, maximumURLLengthForLogging) + "...";
184#else
185 UNUSED_PARAM(url);
186 return "[url]";
187#endif
188 }
189};
190}
191
192
193namespace WebCore {
194
195WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLMediaElement);
196
197using namespace PAL;
198
199static const Seconds SeekRepeatDelay { 100_ms };
200static const double SeekTime = 0.2;
201static const Seconds ScanRepeatDelay { 1.5_s };
202static const double ScanMaximumRate = 8;
203static const double AutoplayInterferenceTimeThreshold = 10;
204static const Seconds hideMediaControlsAfterEndedDelay { 6_s };
205
206#ifndef LOG_CACHED_TIME_WARNINGS
207// Default to not logging warnings about excessive drift in the cached media time because it adds a
208// fair amount of overhead and logging.
209#define LOG_CACHED_TIME_WARNINGS 0
210#endif
211
212#if ENABLE(MEDIA_SOURCE)
213// URL protocol used to signal that the media source API is being used.
214static const char* mediaSourceBlobProtocol = "blob";
215#endif
216
217#if ENABLE(MEDIA_STREAM)
218// URL protocol used to signal that the media stream API is being used.
219static const char* mediaStreamBlobProtocol = "blob";
220#endif
221
222using namespace HTMLNames;
223
224String convertEnumerationToString(HTMLMediaElement::ReadyState enumerationValue)
225{
226 static const NeverDestroyed<String> values[] = {
227 MAKE_STATIC_STRING_IMPL("HAVE_NOTHING"),
228 MAKE_STATIC_STRING_IMPL("HAVE_METADATA"),
229 MAKE_STATIC_STRING_IMPL("HAVE_CURRENT_DATA"),
230 MAKE_STATIC_STRING_IMPL("HAVE_FUTURE_DATA"),
231 MAKE_STATIC_STRING_IMPL("HAVE_ENOUGH_DATA"),
232 };
233 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_NOTHING) == 0, "HTMLMediaElementEnums::HAVE_NOTHING is not 0 as expected");
234 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_METADATA) == 1, "HTMLMediaElementEnums::HAVE_METADATA is not 1 as expected");
235 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_CURRENT_DATA) == 2, "HTMLMediaElementEnums::HAVE_CURRENT_DATA is not 2 as expected");
236 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_FUTURE_DATA) == 3, "HTMLMediaElementEnums::HAVE_FUTURE_DATA is not 3 as expected");
237 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_ENOUGH_DATA) == 4, "HTMLMediaElementEnums::HAVE_ENOUGH_DATA is not 4 as expected");
238 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
239 return values[static_cast<size_t>(enumerationValue)];
240}
241
242String convertEnumerationToString(HTMLMediaElement::NetworkState enumerationValue)
243{
244 static const NeverDestroyed<String> values[] = {
245 MAKE_STATIC_STRING_IMPL("NETWORK_EMPTY"),
246 MAKE_STATIC_STRING_IMPL("NETWORK_IDLE"),
247 MAKE_STATIC_STRING_IMPL("NETWORK_LOADING"),
248 MAKE_STATIC_STRING_IMPL("NETWORK_NO_SOURCE"),
249 };
250 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_EMPTY) == 0, "HTMLMediaElementEnums::NETWORK_EMPTY is not 0 as expected");
251 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_IDLE) == 1, "HTMLMediaElementEnums::NETWORK_IDLE is not 1 as expected");
252 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_LOADING) == 2, "HTMLMediaElementEnums::NETWORK_LOADING is not 2 as expected");
253 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_NO_SOURCE) == 3, "HTMLMediaElementEnums::NETWORK_NO_SOURCE is not 3 as expected");
254 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
255 return values[static_cast<size_t>(enumerationValue)];
256}
257
258String convertEnumerationToString(HTMLMediaElement::AutoplayEventPlaybackState enumerationValue)
259{
260 static const NeverDestroyed<String> values[] = {
261 MAKE_STATIC_STRING_IMPL("None"),
262 MAKE_STATIC_STRING_IMPL("PreventedAutoplay"),
263 MAKE_STATIC_STRING_IMPL("StartedWithUserGesture"),
264 MAKE_STATIC_STRING_IMPL("StartedWithoutUserGesture"),
265 };
266 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::None) == 0, "AutoplayEventPlaybackState::None is not 0 as expected");
267 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::PreventedAutoplay) == 1, "AutoplayEventPlaybackState::PreventedAutoplay is not 1 as expected");
268 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::StartedWithUserGesture) == 2, "AutoplayEventPlaybackState::StartedWithUserGesture is not 2 as expected");
269 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::StartedWithoutUserGesture) == 3, "AutoplayEventPlaybackState::StartedWithoutUserGesture is not 3 as expected");
270 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
271 return values[static_cast<size_t>(enumerationValue)];
272}
273
274typedef HashMap<Document*, HashSet<HTMLMediaElement*>> DocumentElementSetMap;
275static DocumentElementSetMap& documentToElementSetMap()
276{
277 static NeverDestroyed<DocumentElementSetMap> map;
278 return map;
279}
280
281static void addElementToDocumentMap(HTMLMediaElement& element, Document& document)
282{
283 DocumentElementSetMap& map = documentToElementSetMap();
284 HashSet<HTMLMediaElement*> set = map.take(&document);
285 set.add(&element);
286 map.add(&document, set);
287}
288
289static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& document)
290{
291 DocumentElementSetMap& map = documentToElementSetMap();
292 HashSet<HTMLMediaElement*> set = map.take(&document);
293 set.remove(&element);
294 if (!set.isEmpty())
295 map.add(&document, set);
296}
297
298#if ENABLE(VIDEO_TRACK)
299
300class TrackDisplayUpdateScope {
301public:
302 TrackDisplayUpdateScope(HTMLMediaElement& element)
303 : m_element(element)
304 {
305 m_element.beginIgnoringTrackDisplayUpdateRequests();
306 }
307 ~TrackDisplayUpdateScope()
308 {
309 m_element.endIgnoringTrackDisplayUpdateRequests();
310 }
311
312private:
313 HTMLMediaElement& m_element;
314};
315
316#endif
317
318struct HTMLMediaElement::TrackGroup {
319 enum GroupKind { CaptionsAndSubtitles, Description, Chapter, Metadata, Other };
320
321 TrackGroup(GroupKind kind)
322 : kind(kind)
323 {
324 }
325
326 Vector<RefPtr<TextTrack>> tracks;
327 RefPtr<TextTrack> visibleTrack;
328 RefPtr<TextTrack> defaultTrack;
329 GroupKind kind;
330 bool hasSrcLang { false };
331};
332
333HashSet<HTMLMediaElement*>& HTMLMediaElement::allMediaElements()
334{
335 static NeverDestroyed<HashSet<HTMLMediaElement*>> elements;
336 return elements;
337}
338
339#if ENABLE(MEDIA_SESSION)
340typedef HashMap<uint64_t, HTMLMediaElement*> IDToElementMap;
341
342static IDToElementMap& elementIDsToElements()
343{
344 static NeverDestroyed<IDToElementMap> map;
345 return map;
346}
347
348HTMLMediaElement* HTMLMediaElement::elementWithID(uint64_t id)
349{
350 if (id == HTMLMediaElementInvalidID)
351 return nullptr;
352
353 return elementIDsToElements().get(id);
354}
355
356static uint64_t nextElementID()
357{
358 static uint64_t elementID = 0;
359 return ++elementID;
360}
361#endif
362
363struct MediaElementSessionInfo {
364 const MediaElementSession* session;
365 MediaElementSession::PlaybackControlsPurpose purpose;
366
367 MonotonicTime timeOfLastUserInteraction;
368 bool canShowControlsManager : 1;
369 bool isVisibleInViewportOrFullscreen : 1;
370 bool isLargeEnoughForMainContent : 1;
371 bool isPlayingAudio : 1;
372};
373
374static MediaElementSessionInfo mediaElementSessionInfoForSession(const MediaElementSession& session, MediaElementSession::PlaybackControlsPurpose purpose)
375{
376 const HTMLMediaElement& element = session.element();
377 return {
378 &session,
379 purpose,
380 session.mostRecentUserInteractionTime(),
381 session.canShowControlsManager(purpose),
382 element.isFullscreen() || element.isVisibleInViewport(),
383 session.isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls),
384 element.isPlaying() && element.hasAudio() && !element.muted()
385 };
386}
387
388static bool preferMediaControlsForCandidateSessionOverOtherCandidateSession(const MediaElementSessionInfo& session, const MediaElementSessionInfo& otherSession)
389{
390 MediaElementSession::PlaybackControlsPurpose purpose = session.purpose;
391 ASSERT(purpose == otherSession.purpose);
392
393 // For the controls manager, prioritize visible media over offscreen media.
394 if (purpose == MediaElementSession::PlaybackControlsPurpose::ControlsManager && session.isVisibleInViewportOrFullscreen != otherSession.isVisibleInViewportOrFullscreen)
395 return session.isVisibleInViewportOrFullscreen;
396
397 // For Now Playing, prioritize elements that would normally satisfy main content.
398 if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying && session.isLargeEnoughForMainContent != otherSession.isLargeEnoughForMainContent)
399 return session.isLargeEnoughForMainContent;
400
401 // As a tiebreaker, prioritize elements that the user recently interacted with.
402 return session.timeOfLastUserInteraction > otherSession.timeOfLastUserInteraction;
403}
404
405static bool mediaSessionMayBeConfusedWithMainContent(const MediaElementSessionInfo& session, MediaElementSession::PlaybackControlsPurpose purpose)
406{
407 if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying)
408 return session.isPlayingAudio;
409
410 if (!session.isVisibleInViewportOrFullscreen)
411 return false;
412
413 if (!session.isLargeEnoughForMainContent)
414 return false;
415
416 // Even if this video is not a candidate, if it is visible to the user and large enough
417 // to be main content, it poses a risk for being confused with main content.
418 return true;
419}
420
421HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser)
422 : HTMLElement(tagName, document)
423 , ActiveDOMObject(document)
424 , m_progressEventTimer(*this, &HTMLMediaElement::progressEventTimerFired)
425 , m_playbackProgressTimer(*this, &HTMLMediaElement::playbackProgressTimerFired)
426 , m_scanTimer(*this, &HTMLMediaElement::scanTimerFired)
427 , m_playbackControlsManagerBehaviorRestrictionsTimer(*this, &HTMLMediaElement::playbackControlsManagerBehaviorRestrictionsTimerFired)
428 , m_seekToPlaybackPositionEndedTimer(*this, &HTMLMediaElement::seekToPlaybackPositionEndedTimerFired)
429 , m_asyncEventQueue(*this)
430 , m_lastTimeUpdateEventMovieTime(MediaTime::positiveInfiniteTime())
431 , m_firstTimePlaying(true)
432 , m_playing(false)
433 , m_isWaitingUntilMediaCanStart(false)
434 , m_shouldDelayLoadEvent(false)
435 , m_haveFiredLoadedData(false)
436 , m_inActiveDocument(true)
437 , m_autoplaying(true)
438 , m_muted(false)
439 , m_explicitlyMuted(false)
440 , m_initiallyMuted(false)
441 , m_paused(true)
442 , m_seeking(false)
443 , m_seekRequested(false)
444 , m_sentStalledEvent(false)
445 , m_sentEndEvent(false)
446 , m_pausedInternal(false)
447 , m_closedCaptionsVisible(false)
448 , m_webkitLegacyClosedCaptionOverride(false)
449 , m_completelyLoaded(false)
450 , m_havePreparedToPlay(false)
451 , m_parsingInProgress(createdByParser)
452 , m_elementIsHidden(document.hidden())
453 , m_creatingControls(false)
454 , m_receivedLayoutSizeChanged(false)
455 , m_hasEverNotifiedAboutPlaying(false)
456 , m_hasEverHadAudio(false)
457 , m_hasEverHadVideo(false)
458#if ENABLE(MEDIA_CONTROLS_SCRIPT)
459 , m_mediaControlsDependOnPageScaleFactor(false)
460 , m_haveSetUpCaptionContainer(false)
461#endif
462 , m_isScrubbingRemotely(false)
463#if ENABLE(VIDEO_TRACK)
464 , m_tracksAreReady(true)
465 , m_haveVisibleTextTrack(false)
466 , m_processingPreferenceChange(false)
467#endif
468#if !RELEASE_LOG_DISABLED
469 , m_logger(&document.logger())
470 , m_logIdentifier(uniqueLogIdentifier())
471#endif
472{
473 allMediaElements().add(this);
474
475 ALWAYS_LOG(LOGIDENTIFIER);
476
477 setHasCustomStyleResolveCallbacks();
478
479 InspectorInstrumentation::addEventListenersToNode(*this);
480}
481
482void HTMLMediaElement::finishInitialization()
483{
484 m_mediaSession = std::make_unique<MediaElementSession>(*this);
485
486 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
487 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
488#if ENABLE(WIRELESS_PLAYBACK_TARGET)
489 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice);
490#endif
491 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
492 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
493
494 auto& document = this->document();
495 auto* page = document.page();
496
497 if (document.settings().invisibleAutoplayNotPermitted())
498 m_mediaSession->addBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted);
499
500 if (document.ownerElement() || !document.isMediaDocument()) {
501 const auto& topDocument = document.topDocument();
502 const bool isProcessingUserGesture = processingUserGestureForMedia();
503 const bool shouldAudioPlaybackRequireUserGesture = topDocument.audioPlaybackRequiresUserGesture() && !isProcessingUserGesture;
504 const bool shouldVideoPlaybackRequireUserGesture = topDocument.videoPlaybackRequiresUserGesture() && !isProcessingUserGesture;
505
506 if (shouldVideoPlaybackRequireUserGesture) {
507 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
508 if (document.settings().requiresUserGestureToLoadVideo())
509 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForLoad);
510 }
511
512 if (page && page->isLowPowerModeEnabled())
513 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoDueToLowPowerMode);
514
515 if (shouldAudioPlaybackRequireUserGesture)
516 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange);
517
518#if ENABLE(WIRELESS_PLAYBACK_TARGET)
519 if (shouldVideoPlaybackRequireUserGesture || shouldAudioPlaybackRequireUserGesture)
520 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker);
521#endif
522
523 if (!document.settings().mediaDataLoadsAutomatically())
524 m_mediaSession->addBehaviorRestriction(MediaElementSession::AutoPreloadingNotPermitted);
525
526 if (document.settings().mainContentUserGestureOverrideEnabled())
527 m_mediaSession->addBehaviorRestriction(MediaElementSession::OverrideUserGestureRequirementForMainContent);
528 }
529
530#if PLATFORM(IOS_FAMILY)
531 if (!document.settings().videoPlaybackRequiresUserGesture() && !document.settings().audioPlaybackRequiresUserGesture()) {
532 // Relax RequireUserGestureForFullscreen when videoPlaybackRequiresUserGesture and audioPlaybackRequiresUserGesture is not set:
533 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
534 }
535#endif
536
537#if ENABLE(MEDIA_SESSION)
538 m_elementID = nextElementID();
539 elementIDsToElements().add(m_elementID, this);
540
541 setSessionInternal(document.defaultMediaSession());
542#endif
543
544 registerWithDocument(document);
545
546#if USE(AUDIO_SESSION) && PLATFORM(MAC)
547 AudioSession::sharedSession().addMutedStateObserver(this);
548#endif
549
550 mediaSession().clientWillBeginAutoplaying();
551}
552
553// FIXME: Remove this code once https://webkit.org/b/185284 is fixed.
554static unsigned s_destructorCount = 0;
555
556bool HTMLMediaElement::isRunningDestructor()
557{
558 return !!s_destructorCount;
559}
560
561class HTMLMediaElementDestructorScope {
562public:
563 HTMLMediaElementDestructorScope() { ++s_destructorCount; }
564 ~HTMLMediaElementDestructorScope() { --s_destructorCount; }
565};
566
567HTMLMediaElement::~HTMLMediaElement()
568{
569 HTMLMediaElementDestructorScope destructorScope;
570 ALWAYS_LOG(LOGIDENTIFIER);
571
572 beginIgnoringTrackDisplayUpdateRequests();
573 allMediaElements().remove(this);
574
575 m_asyncEventQueue.close();
576
577 setShouldDelayLoadEvent(false);
578 unregisterWithDocument(document());
579
580#if USE(AUDIO_SESSION) && PLATFORM(MAC)
581 AudioSession::sharedSession().removeMutedStateObserver(this);
582#endif
583
584#if ENABLE(VIDEO_TRACK)
585 if (m_audioTracks)
586 m_audioTracks->clearElement();
587 if (m_textTracks)
588 m_textTracks->clearElement();
589 if (m_videoTracks)
590 m_videoTracks->clearElement();
591#endif
592
593#if ENABLE(WIRELESS_PLAYBACK_TARGET)
594 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
595 m_hasPlaybackTargetAvailabilityListeners = false;
596 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(false);
597 updateMediaState();
598 }
599#endif
600
601 if (m_mediaController) {
602 m_mediaController->removeMediaElement(*this);
603 m_mediaController = nullptr;
604 }
605
606#if ENABLE(MEDIA_SOURCE)
607 detachMediaSource();
608#endif
609
610#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
611 webkitSetMediaKeys(nullptr);
612#endif
613
614#if ENABLE(ENCRYPTED_MEDIA)
615 if (m_mediaKeys) {
616 m_mediaKeys->detachCDMClient(*this);
617 if (m_player)
618 m_player->cdmInstanceDetached(m_mediaKeys->cdmInstance());
619 }
620#endif
621
622#if ENABLE(MEDIA_CONTROLS_SCRIPT)
623 if (m_isolatedWorld)
624 m_isolatedWorld->clearWrappers();
625#endif
626
627#if ENABLE(MEDIA_SESSION)
628 if (m_session) {
629 m_session->removeMediaElement(*this);
630 m_session = nullptr;
631 }
632
633 elementIDsToElements().remove(m_elementID);
634#endif
635
636 m_seekTaskQueue.close();
637 m_resumeTaskQueue.close();
638 m_promiseTaskQueue.close();
639 m_pauseAfterDetachedTaskQueue.close();
640 m_playbackControlsManagerBehaviorRestrictionsQueue.close();
641 m_resourceSelectionTaskQueue.close();
642 m_visibilityChangeTaskQueue.close();
643#if ENABLE(ENCRYPTED_MEDIA)
644 m_encryptedMediaQueue.close();
645#endif
646
647 m_completelyLoaded = true;
648
649 if (m_player) {
650 m_player->invalidate();
651 m_player = nullptr;
652 }
653
654 m_mediaSession = nullptr;
655 schedulePlaybackControlsManagerUpdate();
656}
657RefPtr<HTMLMediaElement> HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose purpose)
658{
659 auto allSessions = PlatformMediaSessionManager::sharedManager().currentSessionsMatching([] (const PlatformMediaSession& session) {
660 return is<MediaElementSession>(session);
661 });
662
663 Vector<MediaElementSessionInfo> candidateSessions;
664 bool atLeastOneNonCandidateMayBeConfusedForMainContent = false;
665 for (auto& session : allSessions) {
666 auto mediaElementSessionInfo = mediaElementSessionInfoForSession(downcast<MediaElementSession>(*session), purpose);
667 if (mediaElementSessionInfo.canShowControlsManager)
668 candidateSessions.append(mediaElementSessionInfo);
669 else if (mediaSessionMayBeConfusedWithMainContent(mediaElementSessionInfo, purpose))
670 atLeastOneNonCandidateMayBeConfusedForMainContent = true;
671 }
672
673 if (!candidateSessions.size())
674 return nullptr;
675
676 std::sort(candidateSessions.begin(), candidateSessions.end(), preferMediaControlsForCandidateSessionOverOtherCandidateSession);
677 auto strongestSessionCandidate = candidateSessions.first();
678 if (!strongestSessionCandidate.isVisibleInViewportOrFullscreen && !strongestSessionCandidate.isPlayingAudio && atLeastOneNonCandidateMayBeConfusedForMainContent)
679 return nullptr;
680
681 return &strongestSessionCandidate.session->element();
682}
683
684void HTMLMediaElement::registerWithDocument(Document& document)
685{
686 m_mediaSession->registerWithDocument(document);
687
688 if (m_isWaitingUntilMediaCanStart)
689 document.addMediaCanStartListener(*this);
690
691#if !PLATFORM(IOS_FAMILY)
692 document.registerForMediaVolumeCallbacks(*this);
693 document.registerForPrivateBrowsingStateChangedCallbacks(*this);
694#endif
695
696 document.registerForVisibilityStateChangedCallbacks(*this);
697
698#if ENABLE(VIDEO_TRACK)
699 if (m_requireCaptionPreferencesChangedCallbacks)
700 document.registerForCaptionPreferencesChangedCallbacks(*this);
701#endif
702
703#if ENABLE(MEDIA_CONTROLS_SCRIPT)
704 if (m_mediaControlsDependOnPageScaleFactor)
705 document.registerForPageScaleFactorChangedCallbacks(*this);
706 document.registerForUserInterfaceLayoutDirectionChangedCallbacks(*this);
707#endif
708
709#if ENABLE(WIRELESS_PLAYBACK_TARGET)
710 document.registerForDocumentSuspensionCallbacks(*this);
711#endif
712
713 document.registerForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this);
714
715 document.addAudioProducer(*this);
716 addElementToDocumentMap(*this, document);
717
718#if ENABLE(MEDIA_STREAM)
719 document.registerForMediaStreamStateChangeCallbacks(*this);
720#endif
721
722 document.addApplicationStateChangeListener(*this);
723}
724
725void HTMLMediaElement::unregisterWithDocument(Document& document)
726{
727 m_mediaSession->unregisterWithDocument(document);
728
729 if (m_isWaitingUntilMediaCanStart)
730 document.removeMediaCanStartListener(*this);
731
732#if !PLATFORM(IOS_FAMILY)
733 document.unregisterForMediaVolumeCallbacks(*this);
734 document.unregisterForPrivateBrowsingStateChangedCallbacks(*this);
735#endif
736
737 document.unregisterForVisibilityStateChangedCallbacks(*this);
738
739#if ENABLE(VIDEO_TRACK)
740 if (m_requireCaptionPreferencesChangedCallbacks)
741 document.unregisterForCaptionPreferencesChangedCallbacks(*this);
742#endif
743
744#if ENABLE(MEDIA_CONTROLS_SCRIPT)
745 if (m_mediaControlsDependOnPageScaleFactor)
746 document.unregisterForPageScaleFactorChangedCallbacks(*this);
747 document.unregisterForUserInterfaceLayoutDirectionChangedCallbacks(*this);
748#endif
749
750#if ENABLE(WIRELESS_PLAYBACK_TARGET)
751 document.unregisterForDocumentSuspensionCallbacks(*this);
752#endif
753
754 document.unregisterForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this);
755
756 document.removeAudioProducer(*this);
757 removeElementFromDocumentMap(*this, document);
758
759#if ENABLE(MEDIA_STREAM)
760 document.unregisterForMediaStreamStateChangeCallbacks(*this);
761#endif
762
763 document.removeApplicationStateChangeListener(*this);
764}
765
766void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
767{
768 ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
769 if (m_shouldDelayLoadEvent) {
770 oldDocument.decrementLoadEventDelayCount();
771 newDocument.incrementLoadEventDelayCount();
772 }
773
774 unregisterWithDocument(oldDocument);
775 registerWithDocument(newDocument);
776
777 HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
778 updateShouldAutoplay();
779}
780
781#if ENABLE(WIRELESS_PLAYBACK_TARGET)
782void HTMLMediaElement::prepareForDocumentSuspension()
783{
784 m_mediaSession->unregisterWithDocument(document());
785}
786
787void HTMLMediaElement::resumeFromDocumentSuspension()
788{
789 m_mediaSession->registerWithDocument(document());
790 updateShouldAutoplay();
791}
792#endif
793
794bool HTMLMediaElement::supportsFocus() const
795{
796 if (document().isMediaDocument())
797 return false;
798
799 // If no controls specified, we should still be able to focus the element if it has tabIndex.
800 return controls() || HTMLElement::supportsFocus();
801}
802
803bool HTMLMediaElement::isMouseFocusable() const
804{
805 return false;
806}
807
808void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
809{
810 if (name == srcAttr) {
811 // https://html.spec.whatwg.org/multipage/embedded-content.html#location-of-the-media-resource
812 // Location of the Media Resource
813 // 12 February 2017
814
815 // If a src attribute of a media element is set or changed, the user
816 // agent must invoke the media element's media element load algorithm.
817 if (!value.isNull())
818 prepareForLoad();
819 } else if (name == controlsAttr)
820 configureMediaControls();
821 else if (name == loopAttr)
822 updateSleepDisabling();
823 else if (name == preloadAttr) {
824 if (equalLettersIgnoringASCIICase(value, "none"))
825 m_preload = MediaPlayer::None;
826 else if (equalLettersIgnoringASCIICase(value, "metadata"))
827 m_preload = MediaPlayer::MetaData;
828 else {
829 // The spec does not define an "invalid value default" but "auto" is suggested as the
830 // "missing value default", so use it for everything except "none" and "metadata"
831 m_preload = MediaPlayer::Auto;
832 }
833
834 // The attribute must be ignored if the autoplay attribute is present
835 if (!autoplay() && !m_havePreparedToPlay && m_player)
836 m_player->setPreload(m_mediaSession->effectivePreloadForElement());
837
838 } else if (name == mediagroupAttr)
839 setMediaGroup(value);
840 else if (name == autoplayAttr) {
841 if (processingUserGestureForMedia())
842 removeBehaviorRestrictionsAfterFirstUserGesture();
843 } else if (name == titleAttr) {
844 if (m_mediaSession)
845 m_mediaSession->clientCharacteristicsChanged();
846 }
847 else
848 HTMLElement::parseAttribute(name, value);
849}
850
851void HTMLMediaElement::finishParsingChildren()
852{
853 HTMLElement::finishParsingChildren();
854 m_parsingInProgress = false;
855
856#if ENABLE(VIDEO_TRACK)
857 if (childrenOfType<HTMLTrackElement>(*this).first())
858 scheduleConfigureTextTracks();
859#endif
860}
861
862bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
863{
864 return controls() && HTMLElement::rendererIsNeeded(style);
865}
866
867RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
868{
869 return createRenderer<RenderMedia>(*this, WTFMove(style));
870}
871
872bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const
873{
874#if ENABLE(MEDIA_CONTROLS_SCRIPT)
875 return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
876#else
877 if (!hasMediaControls())
878 return false;
879 // <media> doesn't allow its content, including shadow subtree, to
880 // be rendered. So this should return false for most of the children.
881 // One exception is a shadow tree built for rendering controls which should be visible.
882 // So we let them go here by comparing its subtree root with one of the controls.
883 return &mediaControls()->treeScope() == &child.treeScope()
884 && hasShadowRootParent(child)
885 && HTMLElement::childShouldCreateRenderer(child);
886#endif
887}
888
889Node::InsertedIntoAncestorResult HTMLMediaElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
890{
891 INFO_LOG(LOGIDENTIFIER);
892
893 HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
894 if (insertionType.connectedToDocument)
895 setInActiveDocument(true);
896
897 return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
898}
899
900void HTMLMediaElement::didFinishInsertingNode()
901{
902 Ref<HTMLMediaElement> protectedThis(*this); // prepareForLoad may result in a 'beforeload' event, which can make arbitrary DOM mutations.
903
904 INFO_LOG(LOGIDENTIFIER);
905
906 if (m_inActiveDocument && m_networkState == NETWORK_EMPTY && !attributeWithoutSynchronization(srcAttr).isEmpty())
907 prepareForLoad();
908
909 if (!m_explicitlyMuted) {
910 m_explicitlyMuted = true;
911 m_muted = hasAttributeWithoutSynchronization(mutedAttr);
912 m_mediaSession->canProduceAudioChanged();
913 }
914
915 configureMediaControls();
916}
917
918void HTMLMediaElement::pauseAfterDetachedTask()
919{
920 // If we were re-inserted into an active document, no need to pause.
921 if (m_inActiveDocument)
922 return;
923
924 if (hasMediaControls())
925 mediaControls()->hide();
926 if (m_networkState > NETWORK_EMPTY)
927 pause();
928 if (m_videoFullscreenMode != VideoFullscreenModeNone)
929 exitFullscreen();
930
931 if (!m_player)
932 return;
933
934 size_t extraMemoryCost = m_player->extraMemoryCost();
935 if (extraMemoryCost > m_reportedExtraMemoryCost) {
936 JSC::VM& vm = commonVM();
937 JSC::JSLockHolder lock(vm);
938
939 size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost;
940 m_reportedExtraMemoryCost = extraMemoryCost;
941 // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated.
942 // https://bugs.webkit.org/show_bug.cgi?id=142595
943 vm.heap.deprecatedReportExtraMemory(extraMemoryCostDelta);
944 }
945}
946
947void HTMLMediaElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
948{
949 INFO_LOG(LOGIDENTIFIER);
950
951 setInActiveDocument(false);
952 if (removalType.disconnectedFromDocument) {
953 // Pause asynchronously to let the operation that removed us finish, in case we get inserted back into a document.
954 m_pauseAfterDetachedTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::pauseAfterDetachedTask, this));
955 }
956
957 if (m_mediaSession)
958 m_mediaSession->clientCharacteristicsChanged();
959
960 HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
961}
962
963void HTMLMediaElement::willAttachRenderers()
964{
965 ASSERT(!renderer());
966}
967
968inline void HTMLMediaElement::updateRenderer()
969{
970 if (auto* renderer = this->renderer())
971 renderer->updateFromElement();
972}
973
974void HTMLMediaElement::didAttachRenderers()
975{
976 if (auto* renderer = this->renderer()) {
977 renderer->updateFromElement();
978 if (m_mediaSession && m_mediaSession->wantsToObserveViewportVisibilityForAutoplay())
979 renderer->registerForVisibleInViewportCallback();
980 }
981 updateShouldAutoplay();
982}
983
984void HTMLMediaElement::willDetachRenderers()
985{
986 if (auto* renderer = this->renderer())
987 renderer->unregisterForVisibleInViewportCallback();
988}
989
990void HTMLMediaElement::didDetachRenderers()
991{
992 updateShouldAutoplay();
993}
994
995void HTMLMediaElement::didRecalcStyle(Style::Change)
996{
997 updateRenderer();
998}
999
1000void HTMLMediaElement::scheduleNextSourceChild()
1001{
1002 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
1003 m_resourceSelectionTaskQueue.enqueueTask([this] {
1004 loadNextSourceChild();
1005 });
1006}
1007
1008void HTMLMediaElement::mediaPlayerActiveSourceBuffersChanged(const MediaPlayer*)
1009{
1010 m_hasEverHadAudio |= hasAudio();
1011 m_hasEverHadVideo |= hasVideo();
1012}
1013
1014void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
1015{
1016 auto event = Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::Yes);
1017
1018 // Don't set the event target, the event queue will set it in GenericEventQueue::timerFired and setting it here
1019 // will trigger an ASSERT if this element has been marked for deletion.
1020
1021 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1022}
1023
1024void HTMLMediaElement::scheduleResolvePendingPlayPromises()
1025{
1026 m_promiseTaskQueue.enqueueTask([this, pendingPlayPromises = WTFMove(m_pendingPlayPromises)] () mutable {
1027 resolvePendingPlayPromises(WTFMove(pendingPlayPromises));
1028 });
1029}
1030
1031void HTMLMediaElement::scheduleRejectPendingPlayPromises(Ref<DOMException>&& error)
1032{
1033 m_promiseTaskQueue.enqueueTask([this, error = WTFMove(error), pendingPlayPromises = WTFMove(m_pendingPlayPromises)] () mutable {
1034 rejectPendingPlayPromises(WTFMove(pendingPlayPromises), WTFMove(error));
1035 });
1036}
1037
1038void HTMLMediaElement::rejectPendingPlayPromises(PlayPromiseVector&& pendingPlayPromises, Ref<DOMException>&& error)
1039{
1040 for (auto& promise : pendingPlayPromises)
1041 promise.rejectType<IDLInterface<DOMException>>(error);
1042}
1043
1044void HTMLMediaElement::resolvePendingPlayPromises(PlayPromiseVector&& pendingPlayPromises)
1045{
1046 for (auto& promise : pendingPlayPromises)
1047 promise.resolve();
1048}
1049
1050void HTMLMediaElement::scheduleNotifyAboutPlaying()
1051{
1052 m_promiseTaskQueue.enqueueTask([this, pendingPlayPromises = WTFMove(m_pendingPlayPromises)] () mutable {
1053 notifyAboutPlaying(WTFMove(pendingPlayPromises));
1054 });
1055}
1056
1057void HTMLMediaElement::notifyAboutPlaying(PlayPromiseVector&& pendingPlayPromises)
1058{
1059 Ref<HTMLMediaElement> protectedThis(*this); // The 'playing' event can make arbitrary DOM mutations.
1060 m_playbackStartedTime = currentMediaTime().toDouble();
1061 m_hasEverNotifiedAboutPlaying = true;
1062 dispatchEvent(Event::create(eventNames().playingEvent, Event::CanBubble::No, Event::IsCancelable::Yes));
1063 resolvePendingPlayPromises(WTFMove(pendingPlayPromises));
1064
1065 schedulePlaybackControlsManagerUpdate();
1066}
1067
1068bool HTMLMediaElement::hasEverNotifiedAboutPlaying() const
1069{
1070 return m_hasEverNotifiedAboutPlaying;
1071}
1072
1073void HTMLMediaElement::scheduleCheckPlaybackTargetCompatability()
1074{
1075 if (m_checkPlaybackTargetCompatablityTask.hasPendingTask())
1076 return;
1077
1078 auto logSiteIdentifier = LOGIDENTIFIER;
1079 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
1080 m_checkPlaybackTargetCompatablityTask.scheduleTask([this, logSiteIdentifier] {
1081 UNUSED_PARAM(logSiteIdentifier);
1082 ALWAYS_LOG(logSiteIdentifier, "lambda(), task fired");
1083 checkPlaybackTargetCompatablity();
1084 });
1085}
1086
1087void HTMLMediaElement::checkPlaybackTargetCompatablity()
1088{
1089#if ENABLE(WIRELESS_PLAYBACK_TARGET)
1090 auto logSiteIdentifier = LOGIDENTIFIER;
1091 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
1092 if (m_isPlayingToWirelessTarget && !m_player->canPlayToWirelessPlaybackTarget()) {
1093 UNUSED_PARAM(logSiteIdentifier);
1094 INFO_LOG(logSiteIdentifier, "calling setShouldPlayToPlaybackTarget(false)");
1095 m_failedToPlayToWirelessTarget = true;
1096 m_player->setShouldPlayToPlaybackTarget(false);
1097 }
1098#endif
1099}
1100
1101MediaError* HTMLMediaElement::error() const
1102{
1103 return m_error.get();
1104}
1105
1106void HTMLMediaElement::setSrcObject(MediaProvider&& mediaProvider)
1107{
1108 // FIXME: Setting the srcObject attribute may cause other changes to the media element's internal state:
1109 // Specifically, if srcObject is specified, the UA must use it as the source of media, even if the src
1110 // attribute is also set or children are present. If the value of srcObject is replaced or set to null
1111 // the UA must re-run the media element load algorithm.
1112 //
1113 // https://bugs.webkit.org/show_bug.cgi?id=124896
1114
1115
1116 // https://www.w3.org/TR/html51/semantics-embedded-content.html#dom-htmlmediaelement-srcobject
1117 // 4.7.14.2. Location of the media resource
1118 // srcObject: On setting, it must set the element’s assigned media provider object to the new
1119 // value, and then invoke the element’s media element load algorithm.
1120 INFO_LOG(LOGIDENTIFIER);
1121 m_mediaProvider = WTFMove(mediaProvider);
1122 prepareForLoad();
1123}
1124
1125void HTMLMediaElement::setCrossOrigin(const AtomicString& value)
1126{
1127 setAttributeWithoutSynchronization(crossoriginAttr, value);
1128}
1129
1130String HTMLMediaElement::crossOrigin() const
1131{
1132 return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
1133}
1134
1135HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
1136{
1137 return m_networkState;
1138}
1139
1140String HTMLMediaElement::canPlayType(const String& mimeType) const
1141{
1142 MediaEngineSupportParameters parameters;
1143 ContentType contentType(mimeType);
1144 parameters.type = contentType;
1145 parameters.contentTypesRequiringHardwareSupport = mediaContentTypesRequiringHardwareSupport();
1146 MediaPlayer::SupportsType support = MediaPlayer::supportsType(parameters);
1147 String canPlay;
1148
1149 // 4.8.10.3
1150 switch (support)
1151 {
1152 case MediaPlayer::IsNotSupported:
1153 canPlay = emptyString();
1154 break;
1155 case MediaPlayer::MayBeSupported:
1156 canPlay = "maybe"_s;
1157 break;
1158 case MediaPlayer::IsSupported:
1159 canPlay = "probably"_s;
1160 break;
1161 }
1162
1163 INFO_LOG(LOGIDENTIFIER, mimeType, ": ", canPlay);
1164
1165 return canPlay;
1166}
1167
1168double HTMLMediaElement::getStartDate() const
1169{
1170 if (!m_player)
1171 return std::numeric_limits<double>::quiet_NaN();
1172 return m_player->getStartDate().toDouble();
1173}
1174
1175void HTMLMediaElement::load()
1176{
1177 Ref<HTMLMediaElement> protectedThis(*this); // prepareForLoad may result in a 'beforeload' event, which can make arbitrary DOM mutations.
1178
1179 INFO_LOG(LOGIDENTIFIER);
1180
1181 prepareForLoad();
1182 m_resourceSelectionTaskQueue.enqueueTask([this] {
1183 prepareToPlay();
1184 });
1185}
1186
1187void HTMLMediaElement::prepareForLoad()
1188{
1189 // https://html.spec.whatwg.org/multipage/embedded-content.html#media-element-load-algorithm
1190 // The Media Element Load Algorithm
1191 // 12 February 2017
1192
1193 ALWAYS_LOG(LOGIDENTIFIER, "gesture = ", processingUserGestureForMedia());
1194
1195 if (processingUserGestureForMedia())
1196 removeBehaviorRestrictionsAfterFirstUserGesture();
1197
1198 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
1199 // Perform the cleanup required for the resource load algorithm to run.
1200 stopPeriodicTimers();
1201 m_resourceSelectionTaskQueue.cancelAllTasks();
1202 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
1203 m_sentEndEvent = false;
1204 m_sentStalledEvent = false;
1205 m_haveFiredLoadedData = false;
1206 m_completelyLoaded = false;
1207 m_havePreparedToPlay = false;
1208 m_displayMode = Unknown;
1209 m_currentSrc = URL();
1210
1211#if ENABLE(WIRELESS_PLAYBACK_TARGET)
1212 m_failedToPlayToWirelessTarget = false;
1213#endif
1214
1215 m_loadState = WaitingForSource;
1216 m_currentSourceNode = nullptr;
1217
1218 if (!document().hasBrowsingContext())
1219 return;
1220
1221 createMediaPlayer();
1222
1223 // 2 - Let pending tasks be a list of all tasks from the media element's media element event task source in one of the task queues.
1224 // 3 - For each task in pending tasks that would resolve pending play promises or reject pending play promises, immediately resolve or reject those promises in the order the corresponding tasks were queued.
1225 // 4 - Remove each task in pending tasks from its task queue
1226 cancelPendingEventsAndCallbacks();
1227
1228 // 5 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
1229 // a task to fire a simple event named abort at the media element.
1230 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
1231 scheduleEvent(eventNames().abortEvent);
1232
1233 // 6 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
1234 if (m_networkState != NETWORK_EMPTY) {
1235 // 6.1 - Queue a task to fire a simple event named emptied at the media element.
1236 scheduleEvent(eventNames().emptiedEvent);
1237
1238 // 6.2 - If a fetching process is in progress for the media element, the user agent should stop it.
1239 m_networkState = NETWORK_EMPTY;
1240
1241 // 6.3 - If the media element’s assigned media provider object is a MediaSource object, then detach it.
1242#if ENABLE(MEDIA_SOURCE)
1243 detachMediaSource();
1244#endif
1245
1246 // 6.4 - Forget the media element's media-resource-specific tracks.
1247 forgetResourceSpecificTracks();
1248
1249 // 6.5 - If readyState is not set to HAVE_NOTHING, then set it to that state.
1250 m_readyState = HAVE_NOTHING;
1251 m_readyStateMaximum = HAVE_NOTHING;
1252
1253 // 6.6 - If the paused attribute is false, then set it to true.
1254 m_paused = true;
1255
1256 // 6.7 - If seeking is true, set it to false.
1257 clearSeeking();
1258
1259 // 6.8 - Set the current playback position to 0.
1260 // Set the official playback position to 0.
1261 // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
1262 m_lastSeekTime = MediaTime::zeroTime();
1263 m_playedTimeRanges = TimeRanges::create();
1264 // FIXME: Add support for firing this event. e.g., scheduleEvent(eventNames().timeUpdateEvent);
1265
1266 // 4.9 - Set the initial playback position to 0.
1267 // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call
1268 // above.
1269 refreshCachedTime();
1270
1271 invalidateCachedTime();
1272
1273 // 4.10 - Set the timeline offset to Not-a-Number (NaN).
1274 // 4.11 - Update the duration attribute to Not-a-Number (NaN).
1275
1276 updateMediaController();
1277#if ENABLE(VIDEO_TRACK)
1278 updateActiveTextTrackCues(MediaTime::zeroTime());
1279#endif
1280 }
1281
1282 // 7 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
1283 setPlaybackRate(defaultPlaybackRate());
1284
1285 // 8 - Set the error attribute to null and the autoplaying flag to true.
1286 m_error = nullptr;
1287 m_autoplaying = true;
1288 mediaSession().clientWillBeginAutoplaying();
1289
1290 if (!MediaPlayer::isAvailable())
1291 noneSupported();
1292 else {
1293 // 9 - Invoke the media element's resource selection algorithm.
1294 // Note, unless the restriction on requiring user action has been removed,
1295 // do not begin downloading data.
1296 if (m_mediaSession->dataLoadingPermitted())
1297 selectMediaResource();
1298 }
1299
1300 // 10 - Note: Playback of any previously playing media resource for this element stops.
1301
1302 configureMediaControls();
1303}
1304
1305void HTMLMediaElement::selectMediaResource()
1306{
1307 // https://www.w3.org/TR/2016/REC-html51-20161101/semantics-embedded-content.html#resource-selection-algorithm
1308 // The Resource Selection Algorithm
1309
1310 // 1. Set the element’s networkState attribute to the NETWORK_NO_SOURCE value.
1311 m_networkState = NETWORK_NO_SOURCE;
1312
1313 // 2. Set the element’s show poster flag to true.
1314 setDisplayMode(Poster);
1315
1316 // 3. Set the media element’s delaying-the-load-event flag to true (this delays the load event).
1317 setShouldDelayLoadEvent(true);
1318
1319 // 4. in parallel await a stable state, allowing the task that invoked this algorithm to continue.
1320 if (m_resourceSelectionTaskQueue.hasPendingTasks())
1321 return;
1322
1323 if (!m_mediaSession->pageAllowsDataLoading()) {
1324 ALWAYS_LOG(LOGIDENTIFIER, "not allowed to load in background, waiting");
1325 setShouldDelayLoadEvent(false);
1326 if (m_isWaitingUntilMediaCanStart)
1327 return;
1328 m_isWaitingUntilMediaCanStart = true;
1329 document().addMediaCanStartListener(*this);
1330 return;
1331 }
1332
1333 // Once the page has allowed an element to load media, it is free to load at will. This allows a
1334 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
1335 // put into the background.
1336 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
1337
1338 auto logSiteIdentifier = LOGIDENTIFIER;
1339 UNUSED_PARAM(logSiteIdentifier);
1340
1341 m_resourceSelectionTaskQueue.enqueueTask([this, logSiteIdentifier] {
1342
1343 ALWAYS_LOG(logSiteIdentifier, "lambda(), task fired");
1344
1345 // 5. If the media element’s blocked-on-parser flag is false, then populate the list of pending text tracks.
1346#if ENABLE(VIDEO_TRACK)
1347 if (hasMediaControls())
1348 mediaControls()->changedClosedCaptionsVisibility();
1349
1350 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
1351 // disabled state when the element's resource selection algorithm last started".
1352 // FIXME: Update this to match "populate the list of pending text tracks" step.
1353 m_textTracksWhenResourceSelectionBegan.clear();
1354 if (m_textTracks) {
1355 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
1356 RefPtr<TextTrack> track = m_textTracks->item(i);
1357 if (track->mode() != TextTrack::Mode::Disabled)
1358 m_textTracksWhenResourceSelectionBegan.append(track);
1359 }
1360 }
1361#endif
1362
1363 enum Mode { None, Object, Attribute, Children };
1364 Mode mode = None;
1365
1366 if (m_mediaProvider) {
1367 // 6. If the media element has an assigned media provider object, then let mode be object.
1368 mode = Object;
1369 } else if (hasAttributeWithoutSynchronization(srcAttr)) {
1370 // Otherwise, if the media element has no assigned media provider object but has a src attribute, then let mode be attribute.
1371 mode = Attribute;
1372 ASSERT(m_player);
1373 if (!m_player) {
1374 ERROR_LOG(logSiteIdentifier, " has srcAttr but m_player is not created");
1375 return;
1376 }
1377 } else if (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) {
1378 // Otherwise, if the media element does not have an assigned media provider object and does not have a src attribute,
1379 // but does have a source element child, then let mode be children and let candidate be the first such source element
1380 // child in tree order.
1381 mode = Children;
1382 m_nextChildNodeToConsider = firstSource;
1383 m_currentSourceNode = nullptr;
1384 } else {
1385 // Otherwise the media element has no assigned media provider object and has neither a src attribute nor a source
1386 // element child: set the networkState to NETWORK_EMPTY, and abort these steps; the synchronous section ends.
1387 m_loadState = WaitingForSource;
1388 setShouldDelayLoadEvent(false);
1389 m_networkState = NETWORK_EMPTY;
1390
1391 ALWAYS_LOG(logSiteIdentifier, "nothing to load");
1392 return;
1393 }
1394
1395 // 7. Set the media element’s networkState to NETWORK_LOADING.
1396 m_networkState = NETWORK_LOADING;
1397
1398 // 8. Queue a task to fire a simple event named loadstart at the media element.
1399 scheduleEvent(eventNames().loadstartEvent);
1400
1401 // 9. Run the appropriate steps from the following list:
1402 // ↳ If mode is object
1403 if (mode == Object) {
1404 // 1. Set the currentSrc attribute to the empty string.
1405 m_currentSrc = URL();
1406
1407 // 2. End the synchronous section, continuing the remaining steps in parallel.
1408 // 3. Run the resource fetch algorithm with the assigned media provider object.
1409 switchOn(m_mediaProvider.value(),
1410#if ENABLE(MEDIA_STREAM)
1411 [this](RefPtr<MediaStream> stream) { m_mediaStreamSrcObject = stream; },
1412#endif
1413#if ENABLE(MEDIA_SOURCE)
1414 [this](RefPtr<MediaSource> source) { m_mediaSource = source; },
1415#endif
1416 [this](RefPtr<Blob> blob) { m_blob = blob; }
1417 );
1418
1419 ContentType contentType;
1420 loadResource(URL(), contentType, String());
1421 ALWAYS_LOG(logSiteIdentifier, "using 'srcObject' property");
1422
1423 // If that algorithm returns without aborting this one, then the load failed.
1424 // 4. Failed with media provider: Reaching this step indicates that the media resource
1425 // failed to load. Queue a task to run the dedicated media source failure steps.
1426 // 5. Wait for the task queued by the previous step to have executed.
1427 // 6. Abort these steps. The element won’t attempt to load another resource until this
1428 // algorithm is triggered again.
1429 return;
1430 }
1431
1432 // ↳ If mode is attribute
1433 if (mode == Attribute) {
1434 m_loadState = LoadingFromSrcAttr;
1435
1436 // 1. If the src attribute’s value is the empty string, then end the synchronous section,
1437 // and jump down to the failed with attribute step below.
1438 // 2. Let absolute URL be the absolute URL that would have resulted from parsing the URL
1439 // specified by the src attribute’s value relative to the media element when the src
1440 // attribute was last changed.
1441 URL absoluteURL = getNonEmptyURLAttribute(srcAttr);
1442 if (absoluteURL.isEmpty()) {
1443 mediaLoadingFailed(MediaPlayer::FormatError);
1444 ALWAYS_LOG(logSiteIdentifier, "empty 'src'");
1445 return;
1446 }
1447
1448 if (!isSafeToLoadURL(absoluteURL, Complain) || !dispatchBeforeLoadEvent(absoluteURL.string())) {
1449 mediaLoadingFailed(MediaPlayer::FormatError);
1450 return;
1451 }
1452
1453 // 3. If absolute URL was obtained successfully, set the currentSrc attribute to absolute URL.
1454 m_currentSrc = absoluteURL;
1455
1456 // 4. End the synchronous section, continuing the remaining steps in parallel.
1457 // 5. If absolute URL was obtained successfully, run the resource fetch algorithm with absolute
1458 // URL. If that algorithm returns without aborting this one, then the load failed.
1459
1460 // No type or key system information is available when the url comes
1461 // from the 'src' attribute so MediaPlayer
1462 // will have to pick a media engine based on the file extension.
1463 ContentType contentType;
1464 loadResource(absoluteURL, contentType, String());
1465 ALWAYS_LOG(logSiteIdentifier, "using 'src' attribute url");
1466
1467 // 6. Failed with attribute: Reaching this step indicates that the media resource failed to load
1468 // or that the given URL could not be resolved. Queue a task to run the dedicated media source failure steps.
1469 // 7. Wait for the task queued by the previous step to have executed.
1470 // 8. Abort these steps. The element won’t attempt to load another resource until this algorithm is triggered again.
1471 return;
1472 }
1473
1474 // ↳ Otherwise (mode is children)
1475 // (Ctd. in loadNextSourceChild())
1476 loadNextSourceChild();
1477 });
1478}
1479
1480void HTMLMediaElement::loadNextSourceChild()
1481{
1482 ContentType contentType;
1483 String keySystem;
1484 URL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
1485 if (!mediaURL.isValid()) {
1486 waitForSourceChange();
1487 return;
1488 }
1489
1490 // Recreate the media player for the new url
1491 createMediaPlayer();
1492
1493 m_loadState = LoadingFromSourceElement;
1494 loadResource(mediaURL, contentType, keySystem);
1495}
1496
1497void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentType, const String& keySystem)
1498{
1499 ASSERT(initialURL.isEmpty() || isSafeToLoadURL(initialURL, Complain));
1500
1501 INFO_LOG(LOGIDENTIFIER, initialURL, contentType, keySystem);
1502
1503 RefPtr<Frame> frame = document().frame();
1504 if (!frame) {
1505 mediaLoadingFailed(MediaPlayer::FormatError);
1506 return;
1507 }
1508
1509 Page* page = frame->page();
1510 if (!page) {
1511 mediaLoadingFailed(MediaPlayer::FormatError);
1512 return;
1513 }
1514
1515 URL url = initialURL;
1516 if (!url.isEmpty() && !frame->loader().willLoadMediaElementURL(url, *this)) {
1517 mediaLoadingFailed(MediaPlayer::FormatError);
1518 return;
1519 }
1520
1521#if ENABLE(CONTENT_EXTENSIONS)
1522 if (auto documentLoader = makeRefPtr(frame->loader().documentLoader())) {
1523 if (page->userContentProvider().processContentRuleListsForLoad(url, ContentExtensions::ResourceType::Media, *documentLoader).summary.blockedLoad) {
1524 mediaLoadingFailed(MediaPlayer::FormatError);
1525 return;
1526 }
1527 }
1528#endif
1529
1530 // The resource fetch algorithm
1531 m_networkState = NETWORK_LOADING;
1532
1533 // If the URL should be loaded from the application cache, pass the URL of the cached file to the media engine.
1534 ApplicationCacheResource* resource = nullptr;
1535 if (!url.isEmpty() && frame->loader().documentLoader()->applicationCacheHost().shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
1536 // Resources that are not present in the manifest will always fail to load (at least, after the
1537 // cache has been primed the first time), making the testing of offline applications simpler.
1538 if (!resource || resource->path().isEmpty()) {
1539 mediaLoadingFailed(MediaPlayer::NetworkError);
1540 return;
1541 }
1542 }
1543
1544 // Log that we started loading a media element.
1545 page->diagnosticLoggingClient().logDiagnosticMessage(isVideo() ? DiagnosticLoggingKeys::videoKey() : DiagnosticLoggingKeys::audioKey(), DiagnosticLoggingKeys::loadingKey(), ShouldSample::No);
1546
1547 m_firstTimePlaying = true;
1548
1549 // Set m_currentSrc *before* changing to the cache URL, the fact that we are loading from the app
1550 // cache is an internal detail not exposed through the media element API.
1551 m_currentSrc = url;
1552
1553 if (resource) {
1554 url = ApplicationCacheHost::createFileURL(resource->path());
1555 INFO_LOG(LOGIDENTIFIER, "will load from app cache ", url);
1556 }
1557
1558 INFO_LOG(LOGIDENTIFIER, "m_currentSrc is ", m_currentSrc);
1559
1560 startProgressEventTimer();
1561
1562 bool privateMode = document().page() && document().page()->usesEphemeralSession();
1563 m_player->setPrivateBrowsingMode(privateMode);
1564
1565 // Reset display mode to force a recalculation of what to show because we are resetting the player.
1566 setDisplayMode(Unknown);
1567
1568 if (!autoplay() && !m_havePreparedToPlay)
1569 m_player->setPreload(m_mediaSession->effectivePreloadForElement());
1570 m_player->setPreservesPitch(m_webkitPreservesPitch);
1571
1572 if (!m_explicitlyMuted) {
1573 m_explicitlyMuted = true;
1574 m_muted = hasAttributeWithoutSynchronization(mutedAttr);
1575 m_mediaSession->canProduceAudioChanged();
1576 }
1577
1578 updateVolume();
1579
1580 bool loadAttempted = false;
1581#if ENABLE(MEDIA_SOURCE)
1582 if (!m_mediaSource && url.protocolIs(mediaSourceBlobProtocol))
1583 m_mediaSource = MediaSource::lookup(url.string());
1584
1585 if (m_mediaSource) {
1586 loadAttempted = true;
1587
1588 ALWAYS_LOG(LOGIDENTIFIER, "loading MSE blob");
1589 if (!m_mediaSource->attachToElement(*this) || !m_player->load(url, contentType, m_mediaSource.get())) {
1590 // Forget our reference to the MediaSource, so we leave it alone
1591 // while processing remainder of load failure.
1592 m_mediaSource = nullptr;
1593 mediaLoadingFailed(MediaPlayer::FormatError);
1594 }
1595 }
1596#endif
1597
1598#if ENABLE(MEDIA_STREAM)
1599 if (!loadAttempted) {
1600 if (!m_mediaStreamSrcObject && url.protocolIs(mediaStreamBlobProtocol))
1601 m_mediaStreamSrcObject = MediaStreamRegistry::shared().lookUp(url);
1602
1603 if (m_mediaStreamSrcObject) {
1604 loadAttempted = true;
1605 ALWAYS_LOG(LOGIDENTIFIER, "loading media stream blob");
1606 if (!m_player->load(m_mediaStreamSrcObject->privateStream()))
1607 mediaLoadingFailed(MediaPlayer::FormatError);
1608 }
1609 }
1610#endif
1611
1612 if (!loadAttempted && m_blob) {
1613 loadAttempted = true;
1614 ALWAYS_LOG(LOGIDENTIFIER, "loading generic blob");
1615 if (!m_player->load(m_blob->url(), contentType, keySystem))
1616 mediaLoadingFailed(MediaPlayer::FormatError);
1617 }
1618
1619 if (!loadAttempted && !m_player->load(url, contentType, keySystem))
1620 mediaLoadingFailed(MediaPlayer::FormatError);
1621
1622 // If there is no poster to display, allow the media engine to render video frames as soon as
1623 // they are available.
1624 updateDisplayState();
1625
1626 updateRenderer();
1627}
1628
1629#if ENABLE(VIDEO_TRACK)
1630
1631static bool trackIndexCompare(TextTrack* a, TextTrack* b)
1632{
1633 return a->trackIndex() - b->trackIndex() < 0;
1634}
1635
1636static bool eventTimeCueCompare(const std::pair<MediaTime, TextTrackCue*>& a, const std::pair<MediaTime, TextTrackCue*>& b)
1637{
1638 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1639 // times first).
1640 if (a.first != b.first)
1641 return a.first - b.first < MediaTime::zeroTime();
1642
1643 // If the cues belong to different text tracks, it doesn't make sense to
1644 // compare the two tracks by the relative cue order, so return the relative
1645 // track order.
1646 if (a.second->track() != b.second->track())
1647 return trackIndexCompare(a.second->track(), b.second->track());
1648
1649 // 12 - Further sort tasks in events that have the same time by the
1650 // relative text track cue order of the text track cues associated
1651 // with these tasks.
1652 return a.second->isOrderedBefore(b.second);
1653}
1654
1655static bool compareCueInterval(const CueInterval& one, const CueInterval& two)
1656{
1657 return one.data()->isOrderedBefore(two.data());
1658}
1659
1660static bool compareCueIntervalEndTime(const CueInterval& one, const CueInterval& two)
1661{
1662 return one.data()->endMediaTime() > two.data()->endMediaTime();
1663}
1664
1665void HTMLMediaElement::updateActiveTextTrackCues(const MediaTime& movieTime)
1666{
1667 // 4.8.10.8 Playing the media resource
1668
1669 // If the current playback position changes while the steps are running,
1670 // then the user agent must wait for the steps to complete, and then must
1671 // immediately rerun the steps.
1672 if (ignoreTrackDisplayUpdateRequests())
1673 return;
1674
1675 // 1 - Let current cues be a list of cues, initialized to contain all the
1676 // cues of all the hidden, showing, or showing by default text tracks of the
1677 // media element (not the disabled ones) whose start times are less than or
1678 // equal to the current playback position and whose end times are greater
1679 // than the current playback position.
1680 CueList currentCues;
1681
1682 // The user agent must synchronously unset [the text track cue active] flag
1683 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1684 auto movieTimeInterval = m_cueTree.createInterval(movieTime, movieTime);
1685 if (m_readyState != HAVE_NOTHING && m_player) {
1686 currentCues = m_cueTree.allOverlaps(movieTimeInterval);
1687 if (currentCues.size() > 1)
1688 std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval);
1689 }
1690
1691 CueList previousCues;
1692 CueList missedCues;
1693
1694 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1695 // of hidden, showing, and showing by default text tracks of the media
1696 // element that are not present in current cues.
1697 previousCues = m_currentlyActiveCues;
1698
1699 // 3 - Let last time be the current playback position at the time this
1700 // algorithm was last run for this media element, if this is not the first
1701 // time it has run.
1702 MediaTime lastTime = m_lastTextTrackUpdateTime;
1703
1704 // 4 - If the current playback position has, since the last time this
1705 // algorithm was run, only changed through its usual monotonic increase
1706 // during normal playback, then let missed cues be the list of cues in other
1707 // cues whose start times are greater than or equal to last time and whose
1708 // end times are less than or equal to the current playback position.
1709 // Otherwise, let missed cues be an empty list.
1710 if (lastTime >= MediaTime::zeroTime() && m_lastSeekTime < movieTime) {
1711 for (auto& cue : m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime))) {
1712 // Consider cues that may have been missed since the last seek time.
1713 if (cue.low() > std::max(m_lastSeekTime, lastTime) && cue.high() < movieTime)
1714 missedCues.append(cue);
1715 }
1716 }
1717
1718 m_lastTextTrackUpdateTime = movieTime;
1719
1720 // 5 - If the time was reached through the usual monotonic increase of the
1721 // current playback position during normal playback, and if the user agent
1722 // has not fired a timeupdate event at the element in the past 15 to 250ms
1723 // and is not still running event handlers for such an event, then the user
1724 // agent must queue a task to fire a simple event named timeupdate at the
1725 // element. (In the other cases, such as explicit seeks, relevant events get
1726 // fired as part of the overall process of changing the current playback
1727 // position.)
1728 if (!m_paused && m_lastSeekTime <= lastTime)
1729 scheduleTimeupdateEvent(false);
1730
1731 // Explicitly cache vector sizes, as their content is constant from here.
1732 size_t currentCuesSize = currentCues.size();
1733 size_t missedCuesSize = missedCues.size();
1734 size_t previousCuesSize = previousCues.size();
1735
1736 // 6 - If all of the cues in current cues have their text track cue active
1737 // flag set, none of the cues in other cues have their text track cue active
1738 // flag set, and missed cues is empty, then abort these steps.
1739 bool activeSetChanged = missedCuesSize;
1740
1741 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1742 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1743 activeSetChanged = true;
1744
1745 for (size_t i = 0; i < currentCuesSize; ++i) {
1746 RefPtr<TextTrackCue> cue = currentCues[i].data();
1747
1748 if (cue->isRenderable())
1749 toVTTCue(cue.get())->updateDisplayTree(movieTime);
1750
1751 if (!cue->isActive())
1752 activeSetChanged = true;
1753 }
1754
1755 MediaTime nextInterestingTime = MediaTime::invalidTime();
1756 if (auto nearestEndingCue = std::min_element(currentCues.begin(), currentCues.end(), compareCueIntervalEndTime))
1757 nextInterestingTime = nearestEndingCue->data()->endMediaTime();
1758
1759 Optional<CueInterval> nextCue = m_cueTree.nextIntervalAfter(movieTimeInterval);
1760 if (nextCue)
1761 nextInterestingTime = std::min(nextInterestingTime, nextCue->low());
1762
1763 INFO_LOG(LOGIDENTIFIER, "nextInterestingTime:", nextInterestingTime);
1764
1765 if (nextInterestingTime.isValid() && m_player) {
1766 m_player->performTaskAtMediaTime([this, weakThis = makeWeakPtr(this), nextInterestingTime] {
1767 if (!weakThis)
1768 return;
1769
1770 auto currentMediaTime = this->currentMediaTime();
1771 INFO_LOG(LOGIDENTIFIER, " lambda, currentMediaTime: ", currentMediaTime);
1772 this->updateActiveTextTrackCues(currentMediaTime);
1773 }, nextInterestingTime);
1774 }
1775
1776 if (!activeSetChanged)
1777 return;
1778
1779 // 7 - If the time was reached through the usual monotonic increase of the
1780 // current playback position during normal playback, and there are cues in
1781 // other cues that have their text track cue pause-on-exi flag set and that
1782 // either have their text track cue active flag set or are also in missed
1783 // cues, then immediately pause the media element.
1784 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1785 if (previousCues[i].data()->pauseOnExit()
1786 && previousCues[i].data()->isActive()
1787 && !currentCues.contains(previousCues[i]))
1788 pause();
1789 }
1790
1791 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1792 if (missedCues[i].data()->pauseOnExit())
1793 pause();
1794 }
1795
1796 // 8 - Let events be a list of tasks, initially empty. Each task in this
1797 // list will be associated with a text track, a text track cue, and a time,
1798 // which are used to sort the list before the tasks are queued.
1799 Vector<std::pair<MediaTime, TextTrackCue*>> eventTasks;
1800
1801 // 8 - Let affected tracks be a list of text tracks, initially empty.
1802 Vector<TextTrack*> affectedTracks;
1803
1804 for (size_t i = 0; i < missedCuesSize; ++i) {
1805 // 9 - For each text track cue in missed cues, prepare an event named enter
1806 // for the TextTrackCue object with the text track cue start time.
1807 eventTasks.append({ missedCues[i].data()->startMediaTime(), missedCues[i].data() });
1808
1809 // 10 - For each text track [...] in missed cues, prepare an event
1810 // named exit for the TextTrackCue object with the with the later of
1811 // the text track cue end time and the text track cue start time.
1812
1813 // Note: An explicit task is added only if the cue is NOT a zero or
1814 // negative length cue. Otherwise, the need for an exit event is
1815 // checked when these tasks are actually queued below. This doesn't
1816 // affect sorting events before dispatch either, because the exit
1817 // event has the same time as the enter event.
1818 if (missedCues[i].data()->startMediaTime() < missedCues[i].data()->endMediaTime())
1819 eventTasks.append({ missedCues[i].data()->endMediaTime(), missedCues[i].data() });
1820 }
1821
1822 for (size_t i = 0; i < previousCuesSize; ++i) {
1823 // 10 - For each text track cue in other cues that has its text
1824 // track cue active flag set prepare an event named exit for the
1825 // TextTrackCue object with the text track cue end time.
1826 if (!currentCues.contains(previousCues[i]))
1827 eventTasks.append({ previousCues[i].data()->endMediaTime(), previousCues[i].data() });
1828 }
1829
1830 for (size_t i = 0; i < currentCuesSize; ++i) {
1831 // 11 - For each text track cue in current cues that does not have its
1832 // text track cue active flag set, prepare an event named enter for the
1833 // TextTrackCue object with the text track cue start time.
1834 if (!previousCues.contains(currentCues[i]))
1835 eventTasks.append({ currentCues[i].data()->startMediaTime(), currentCues[i].data() });
1836 }
1837
1838 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1839 // times first).
1840 std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1841
1842 for (auto& eventTask : eventTasks) {
1843 if (!affectedTracks.contains(eventTask.second->track()))
1844 affectedTracks.append(eventTask.second->track());
1845
1846 // 13 - Queue each task in events, in list order.
1847
1848 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1849 // depending on the time that is associated with the event. This
1850 // correctly identifies the type of the event, if the startTime is
1851 // less than the endTime in the cue.
1852 if (eventTask.second->startTime() >= eventTask.second->endTime()) {
1853 auto enterEvent = Event::create(eventNames().enterEvent, Event::CanBubble::No, Event::IsCancelable::No);
1854 enterEvent->setTarget(eventTask.second);
1855 m_asyncEventQueue.enqueueEvent(WTFMove(enterEvent));
1856
1857 auto exitEvent = Event::create(eventNames().exitEvent, Event::CanBubble::No, Event::IsCancelable::No);
1858 exitEvent->setTarget(eventTask.second);
1859 m_asyncEventQueue.enqueueEvent(WTFMove(exitEvent));
1860 } else {
1861 RefPtr<Event> event;
1862 if (eventTask.first == eventTask.second->startMediaTime())
1863 event = Event::create(eventNames().enterEvent, Event::CanBubble::No, Event::IsCancelable::No);
1864 else
1865 event = Event::create(eventNames().exitEvent, Event::CanBubble::No, Event::IsCancelable::No);
1866 event->setTarget(eventTask.second);
1867 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1868 }
1869 }
1870
1871 // 14 - Sort affected tracks in the same order as the text tracks appear in
1872 // the media element's list of text tracks, and remove duplicates.
1873 std::sort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1874
1875 // 15 - For each text track in affected tracks, in the list order, queue a
1876 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1877 for (auto& affectedTrack : affectedTracks) {
1878 auto event = Event::create(eventNames().cuechangeEvent, Event::CanBubble::No, Event::IsCancelable::No);
1879 event->setTarget(affectedTrack);
1880 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1881
1882 // ... if the text track has a corresponding track element, to then fire a
1883 // simple event named cuechange at the track element as well.
1884 if (is<LoadableTextTrack>(*affectedTrack)) {
1885 auto event = Event::create(eventNames().cuechangeEvent, Event::CanBubble::No, Event::IsCancelable::No);
1886 auto trackElement = makeRefPtr(downcast<LoadableTextTrack>(*affectedTrack).trackElement());
1887 ASSERT(trackElement);
1888 event->setTarget(trackElement);
1889 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1890 }
1891 }
1892
1893 // 16 - Set the text track cue active flag of all the cues in the current
1894 // cues, and unset the text track cue active flag of all the cues in the
1895 // other cues.
1896 for (size_t i = 0; i < currentCuesSize; ++i)
1897 currentCues[i].data()->setIsActive(true);
1898
1899 for (size_t i = 0; i < previousCuesSize; ++i)
1900 if (!currentCues.contains(previousCues[i]))
1901 previousCues[i].data()->setIsActive(false);
1902
1903 // Update the current active cues.
1904 m_currentlyActiveCues = currentCues;
1905
1906 if (activeSetChanged)
1907 updateTextTrackDisplay();
1908}
1909
1910bool HTMLMediaElement::textTracksAreReady() const
1911{
1912 // 4.8.10.12.1 Text track model
1913 // ...
1914 // The text tracks of a media element are ready if all the text tracks whose mode was not
1915 // in the disabled state when the element's resource selection algorithm last started now
1916 // have a text track readiness state of loaded or failed to load.
1917 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1918 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1919 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1920 return false;
1921 }
1922
1923 return true;
1924}
1925
1926void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1927{
1928 if (track->readinessState() != TextTrack::Loading
1929 && track->mode() != TextTrack::Mode::Disabled) {
1930 // The display trees exist as long as the track is active, in this case,
1931 // and if the same track is loaded again (for example if the src attribute was changed),
1932 // cues can be accumulated with the old ones, that's why they needs to be flushed
1933 if (hasMediaControls())
1934 mediaControls()->clearTextDisplayContainer();
1935 updateTextTrackDisplay();
1936 }
1937 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1938 if (track->readinessState() != TextTrack::Loading)
1939 setReadyState(m_player->readyState());
1940 } else {
1941 // The track readiness state might have changed as a result of the user
1942 // clicking the captions button. In this case, a check whether all the
1943 // resources have failed loading should be done in order to hide the CC button.
1944 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1945 mediaControls()->refreshClosedCaptionsButtonVisibility();
1946 }
1947}
1948
1949void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack& track)
1950{
1951 if (m_audioTracks && m_audioTracks->contains(track))
1952 m_audioTracks->scheduleChangeEvent();
1953 if (processingUserGestureForMedia())
1954 removeBehaviorRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
1955}
1956
1957void HTMLMediaElement::textTrackModeChanged(TextTrack& track)
1958{
1959 bool trackIsLoaded = true;
1960 if (track.trackType() == TextTrack::TrackElement) {
1961 trackIsLoaded = false;
1962 for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) {
1963 if (&trackElement.track() == &track) {
1964 if (trackElement.readyState() == HTMLTrackElement::LOADING || trackElement.readyState() == HTMLTrackElement::LOADED)
1965 trackIsLoaded = true;
1966 break;
1967 }
1968 }
1969 }
1970
1971 // If this is the first added track, create the list of text tracks.
1972 if (!m_textTracks)
1973 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
1974
1975 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1976 track.setHasBeenConfigured(true);
1977
1978 if (track.mode() != TextTrack::Mode::Disabled && trackIsLoaded)
1979 textTrackAddCues(track, *track.cues());
1980
1981 configureTextTrackDisplay(AssumeTextTrackVisibilityChanged);
1982
1983 if (m_textTracks && m_textTracks->contains(track))
1984 m_textTracks->scheduleChangeEvent();
1985
1986#if ENABLE(AVF_CAPTIONS)
1987 if (track.trackType() == TextTrack::TrackElement && m_player)
1988 m_player->notifyTrackModeChanged();
1989#endif
1990}
1991
1992void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack& track)
1993{
1994 if (m_videoTracks && m_videoTracks->contains(track))
1995 m_videoTracks->scheduleChangeEvent();
1996}
1997
1998void HTMLMediaElement::textTrackKindChanged(TextTrack& track)
1999{
2000 if (track.kind() != TextTrack::Kind::Captions && track.kind() != TextTrack::Kind::Subtitles && track.mode() == TextTrack::Mode::Showing)
2001 track.setMode(TextTrack::Mode::Hidden);
2002}
2003
2004void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
2005{
2006 ++m_ignoreTrackDisplayUpdate;
2007}
2008
2009void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
2010{
2011 ASSERT(m_ignoreTrackDisplayUpdate);
2012 --m_ignoreTrackDisplayUpdate;
2013 if (!m_ignoreTrackDisplayUpdate && m_inActiveDocument)
2014 updateActiveTextTrackCues(currentMediaTime());
2015}
2016
2017void HTMLMediaElement::textTrackAddCues(TextTrack& track, const TextTrackCueList& cues)
2018{
2019 if (track.mode() == TextTrack::Mode::Disabled)
2020 return;
2021
2022 TrackDisplayUpdateScope scope { *this };
2023 for (unsigned i = 0; i < cues.length(); ++i)
2024 textTrackAddCue(track, *cues.item(i));
2025}
2026
2027void HTMLMediaElement::textTrackRemoveCues(TextTrack&, const TextTrackCueList& cues)
2028{
2029 TrackDisplayUpdateScope scope { *this };
2030 for (unsigned i = 0; i < cues.length(); ++i) {
2031 auto& cue = *cues.item(i);
2032 textTrackRemoveCue(*cue.track(), cue);
2033 }
2034}
2035
2036void HTMLMediaElement::textTrackAddCue(TextTrack& track, TextTrackCue& cue)
2037{
2038 if (track.mode() == TextTrack::Mode::Disabled)
2039 return;
2040
2041 // Negative duration cues need be treated in the interval tree as
2042 // zero-length cues.
2043 MediaTime endTime = std::max(cue.startMediaTime(), cue.endMediaTime());
2044
2045 CueInterval interval = m_cueTree.createInterval(cue.startMediaTime(), endTime, &cue);
2046 if (!m_cueTree.contains(interval))
2047 m_cueTree.add(interval);
2048 updateActiveTextTrackCues(currentMediaTime());
2049}
2050
2051void HTMLMediaElement::textTrackRemoveCue(TextTrack&, TextTrackCue& cue)
2052{
2053 // Negative duration cues need to be treated in the interval tree as
2054 // zero-length cues.
2055 MediaTime endTime = std::max(cue.startMediaTime(), cue.endMediaTime());
2056
2057 CueInterval interval = m_cueTree.createInterval(cue.startMediaTime(), endTime, &cue);
2058 m_cueTree.remove(interval);
2059
2060 // Since the cue will be removed from the media element and likely the
2061 // TextTrack might also be destructed, notifying the region of the cue
2062 // removal shouldn't be done.
2063 if (cue.isRenderable())
2064 toVTTCue(&cue)->notifyRegionWhenRemovingDisplayTree(false);
2065
2066 size_t index = m_currentlyActiveCues.find(interval);
2067 if (index != notFound) {
2068 cue.setIsActive(false);
2069 m_currentlyActiveCues.remove(index);
2070 }
2071
2072 if (cue.isRenderable())
2073 toVTTCue(&cue)->removeDisplayTree();
2074 updateActiveTextTrackCues(currentMediaTime());
2075
2076 if (cue.isRenderable())
2077 toVTTCue(&cue)->notifyRegionWhenRemovingDisplayTree(true);
2078}
2079
2080#endif
2081
2082static inline bool isAllowedToLoadMediaURL(HTMLMediaElement& element, const URL& url, bool isInUserAgentShadowTree)
2083{
2084 // Elements in user agent show tree should load whatever the embedding document policy is.
2085 if (isInUserAgentShadowTree)
2086 return true;
2087
2088 ASSERT(element.document().contentSecurityPolicy());
2089 return element.document().contentSecurityPolicy()->allowMediaFromSource(url);
2090}
2091
2092bool HTMLMediaElement::isSafeToLoadURL(const URL& url, InvalidURLAction actionIfInvalid)
2093{
2094 if (!url.isValid()) {
2095 ERROR_LOG(LOGIDENTIFIER, url, " is invalid");
2096 return false;
2097 }
2098
2099 RefPtr<Frame> frame = document().frame();
2100 if (!frame || !document().securityOrigin().canDisplay(url)) {
2101 if (actionIfInvalid == Complain) {
2102 FrameLoader::reportLocalLoadFailed(frame.get(), url.stringCenterEllipsizedToLength());
2103 ERROR_LOG(LOGIDENTIFIER, url , " was rejected by SecurityOrigin");
2104 }
2105 return false;
2106 }
2107
2108 if (!isAllowedToLoadMediaURL(*this, url, isInUserAgentShadowTree())) {
2109 ERROR_LOG(LOGIDENTIFIER, url, " was rejected by Content Security Policy");
2110 return false;
2111 }
2112
2113 return true;
2114}
2115
2116void HTMLMediaElement::startProgressEventTimer()
2117{
2118 if (m_progressEventTimer.isActive())
2119 return;
2120
2121 m_previousProgressTime = MonotonicTime::now();
2122 // 350ms is not magic, it is in the spec!
2123 m_progressEventTimer.startRepeating(350_ms);
2124}
2125
2126void HTMLMediaElement::waitForSourceChange()
2127{
2128 INFO_LOG(LOGIDENTIFIER);
2129
2130 stopPeriodicTimers();
2131 m_loadState = WaitingForSource;
2132
2133 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
2134 m_networkState = NETWORK_NO_SOURCE;
2135
2136 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2137 setShouldDelayLoadEvent(false);
2138
2139 updateDisplayState();
2140 updateRenderer();
2141}
2142
2143void HTMLMediaElement::noneSupported()
2144{
2145 if (m_error)
2146 return;
2147
2148 INFO_LOG(LOGIDENTIFIER);
2149
2150 stopPeriodicTimers();
2151 m_loadState = WaitingForSource;
2152 m_currentSourceNode = nullptr;
2153
2154 // 4.8.10.5
2155 // 6 - Reaching this step indicates that the media resource failed to load or that the given
2156 // URL could not be resolved. In one atomic operation, run the following steps:
2157
2158 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
2159 // MEDIA_ERR_SRC_NOT_SUPPORTED.
2160 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
2161
2162 // 6.2 - Forget the media element's media-resource-specific text tracks.
2163 forgetResourceSpecificTracks();
2164
2165 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
2166 m_networkState = NETWORK_NO_SOURCE;
2167
2168 // 7 - Queue a task to fire a simple event named error at the media element.
2169 scheduleEvent(eventNames().errorEvent);
2170
2171 rejectPendingPlayPromises(WTFMove(m_pendingPlayPromises), DOMException::create(NotSupportedError));
2172
2173#if ENABLE(MEDIA_SOURCE)
2174 detachMediaSource();
2175#endif
2176
2177 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2178 setShouldDelayLoadEvent(false);
2179
2180 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
2181 // the element won't attempt to load another resource.
2182
2183 updateDisplayState();
2184 updateRenderer();
2185}
2186
2187void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error)
2188{
2189 // 1 - The user agent should cancel the fetching process.
2190 stopPeriodicTimers();
2191 m_loadState = WaitingForSource;
2192
2193 // 2 - Set the error attribute to a new MediaError object whose code attribute is
2194 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
2195 if (error == MediaPlayer::NetworkError)
2196 m_error = MediaError::create(MediaError::MEDIA_ERR_NETWORK);
2197 else if (error == MediaPlayer::DecodeError)
2198 m_error = MediaError::create(MediaError::MEDIA_ERR_DECODE);
2199 else
2200 ASSERT_NOT_REACHED();
2201
2202 // 3 - Queue a task to fire a simple event named error at the media element.
2203 scheduleEvent(eventNames().errorEvent);
2204
2205#if ENABLE(MEDIA_SOURCE)
2206 detachMediaSource();
2207#endif
2208
2209 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
2210 // task to fire a simple event called emptied at the element.
2211 m_networkState = NETWORK_EMPTY;
2212 scheduleEvent(eventNames().emptiedEvent);
2213
2214 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2215 setShouldDelayLoadEvent(false);
2216
2217 // 6 - Abort the overall resource selection algorithm.
2218 m_currentSourceNode = nullptr;
2219
2220#if PLATFORM(COCOA)
2221 if (is<MediaDocument>(document()))
2222 downcast<MediaDocument>(document()).mediaElementSawUnsupportedTracks();
2223#endif
2224}
2225
2226void HTMLMediaElement::cancelPendingEventsAndCallbacks()
2227{
2228 INFO_LOG(LOGIDENTIFIER);
2229 m_asyncEventQueue.cancelAllEvents();
2230
2231 for (auto& source : childrenOfType<HTMLSourceElement>(*this))
2232 source.cancelPendingErrorEvent();
2233
2234 rejectPendingPlayPromises(WTFMove(m_pendingPlayPromises), DOMException::create(AbortError));
2235}
2236
2237void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
2238{
2239 beginProcessingMediaPlayerCallback();
2240 setNetworkState(m_player->networkState());
2241 endProcessingMediaPlayerCallback();
2242}
2243
2244static void logMediaLoadRequest(Page* page, const String& mediaEngine, const String& errorMessage, bool succeeded)
2245{
2246 if (!page)
2247 return;
2248
2249 DiagnosticLoggingClient& diagnosticLoggingClient = page->diagnosticLoggingClient();
2250 if (!succeeded) {
2251 diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingResultFail, ShouldSample::No);
2252 return;
2253 }
2254
2255 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, ShouldSample::No);
2256
2257 if (!page->hasSeenAnyMediaEngine())
2258 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), ShouldSample::No);
2259
2260 if (!page->hasSeenMediaEngine(mediaEngine))
2261 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, ShouldSample::No);
2262
2263 page->sawMediaEngine(mediaEngine);
2264}
2265
2266static String stringForNetworkState(MediaPlayer::NetworkState state)
2267{
2268 switch (state) {
2269 case MediaPlayer::Empty: return "Empty"_s;
2270 case MediaPlayer::Idle: return "Idle"_s;
2271 case MediaPlayer::Loading: return "Loading"_s;
2272 case MediaPlayer::Loaded: return "Loaded"_s;
2273 case MediaPlayer::FormatError: return "FormatError"_s;
2274 case MediaPlayer::NetworkError: return "NetworkError"_s;
2275 case MediaPlayer::DecodeError: return "DecodeError"_s;
2276 default: return emptyString();
2277 }
2278}
2279
2280void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
2281{
2282 stopPeriodicTimers();
2283
2284 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
2285 // <source> children, schedule the next one
2286 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
2287
2288 // resource selection algorithm
2289 // Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM manipulation task source, to fire a simple event named error at the candidate element.
2290 if (m_currentSourceNode)
2291 m_currentSourceNode->scheduleErrorEvent();
2292 else
2293 INFO_LOG(LOGIDENTIFIER, "error event not sent, <source> was removed");
2294
2295 // 9.Otherwise.10 - Asynchronously await a stable state. The synchronous section consists of all the remaining steps of this algorithm until the algorithm says the synchronous section has ended.
2296
2297 // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks.
2298 forgetResourceSpecificTracks();
2299
2300 if (havePotentialSourceChild()) {
2301 INFO_LOG(LOGIDENTIFIER, "scheduling next <source>");
2302 scheduleNextSourceChild();
2303 } else {
2304 INFO_LOG(LOGIDENTIFIER, "no more <source> elements, waiting");
2305 waitForSourceChange();
2306 }
2307
2308 return;
2309 }
2310
2311 if ((error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA) || error == MediaPlayer::DecodeError)
2312 mediaLoadingFailedFatally(error);
2313 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
2314 noneSupported();
2315
2316 updateDisplayState();
2317 if (hasMediaControls()) {
2318 mediaControls()->reset();
2319 mediaControls()->reportedError();
2320 }
2321
2322 ERROR_LOG(LOGIDENTIFIER, "error = ", static_cast<int>(error));
2323
2324 logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false);
2325
2326 m_mediaSession->clientCharacteristicsChanged();
2327}
2328
2329void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
2330{
2331 if (static_cast<int>(state) != static_cast<int>(m_networkState))
2332 ALWAYS_LOG(LOGIDENTIFIER, "new state = ", state, ", current state = ", m_networkState);
2333
2334 if (state == MediaPlayer::Empty) {
2335 // Just update the cached state and leave, we can't do anything.
2336 m_networkState = NETWORK_EMPTY;
2337 return;
2338 }
2339
2340 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
2341 mediaLoadingFailed(state);
2342 return;
2343 }
2344
2345 if (state == MediaPlayer::Idle) {
2346 if (m_networkState > NETWORK_IDLE) {
2347 changeNetworkStateFromLoadingToIdle();
2348 setShouldDelayLoadEvent(false);
2349 } else {
2350 m_networkState = NETWORK_IDLE;
2351 }
2352 }
2353
2354 if (state == MediaPlayer::Loading) {
2355 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
2356 startProgressEventTimer();
2357 m_networkState = NETWORK_LOADING;
2358 }
2359
2360 if (state == MediaPlayer::Loaded) {
2361 if (m_networkState != NETWORK_IDLE)
2362 changeNetworkStateFromLoadingToIdle();
2363 m_completelyLoaded = true;
2364 }
2365
2366 if (hasMediaControls())
2367 mediaControls()->updateStatusDisplay();
2368}
2369
2370void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
2371{
2372 m_progressEventTimer.stop();
2373 if (hasMediaControls() && m_player->didLoadingProgress())
2374 mediaControls()->bufferingProgressed();
2375
2376 // Schedule one last progress event so we guarantee that at least one is fired
2377 // for files that load very quickly.
2378 scheduleEvent(eventNames().progressEvent);
2379 scheduleEvent(eventNames().suspendEvent);
2380 m_networkState = NETWORK_IDLE;
2381}
2382
2383void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
2384{
2385 beginProcessingMediaPlayerCallback();
2386
2387 setReadyState(m_player->readyState());
2388
2389 endProcessingMediaPlayerCallback();
2390}
2391
2392SuccessOr<MediaPlaybackDenialReason> HTMLMediaElement::canTransitionFromAutoplayToPlay() const
2393{
2394 if (m_readyState != HAVE_ENOUGH_DATA) {
2395 ALWAYS_LOG(LOGIDENTIFIER, "m_readyState != HAVE_ENOUGH_DATA");
2396 return MediaPlaybackDenialReason::PageConsentRequired;
2397 }
2398 if (!isAutoplaying()) {
2399 ALWAYS_LOG(LOGIDENTIFIER, "!isAutoplaying");
2400 return MediaPlaybackDenialReason::PageConsentRequired;
2401 }
2402 if (!mediaSession().autoplayPermitted()) {
2403 ALWAYS_LOG(LOGIDENTIFIER, "!mediaSession().autoplayPermitted");
2404 return MediaPlaybackDenialReason::PageConsentRequired;
2405 }
2406 if (!paused()) {
2407 ALWAYS_LOG(LOGIDENTIFIER, "!paused");
2408 return MediaPlaybackDenialReason::PageConsentRequired;
2409 }
2410 if (!autoplay()) {
2411 ALWAYS_LOG(LOGIDENTIFIER, "!autoplay");
2412 return MediaPlaybackDenialReason::PageConsentRequired;
2413 }
2414 if (pausedForUserInteraction()) {
2415 ALWAYS_LOG(LOGIDENTIFIER, "pausedForUserInteraction");
2416 return MediaPlaybackDenialReason::PageConsentRequired;
2417 }
2418 if (document().isSandboxed(SandboxAutomaticFeatures)) {
2419 ALWAYS_LOG(LOGIDENTIFIER, "isSandboxed");
2420 return MediaPlaybackDenialReason::PageConsentRequired;
2421 }
2422
2423 auto permitted = mediaSession().playbackPermitted();
2424#if !RELEASE_LOG_DISABLED
2425 if (!permitted)
2426 ALWAYS_LOG(LOGIDENTIFIER, permitted.value());
2427 else
2428 ALWAYS_LOG(LOGIDENTIFIER, "can transition!");
2429#endif
2430
2431 return permitted;
2432}
2433
2434void HTMLMediaElement::dispatchPlayPauseEventsIfNeedsQuirks()
2435{
2436 if (!document().quirks().needsAutoplayPlayPauseEvents())
2437 return;
2438
2439 ALWAYS_LOG(LOGIDENTIFIER);
2440 scheduleEvent(eventNames().playingEvent);
2441 scheduleEvent(eventNames().pauseEvent);
2442}
2443
2444void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
2445{
2446 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
2447 bool wasPotentiallyPlaying = potentiallyPlaying();
2448
2449 ReadyState oldState = m_readyState;
2450 ReadyState newState = static_cast<ReadyState>(state);
2451
2452#if ENABLE(VIDEO_TRACK)
2453 bool tracksAreReady = textTracksAreReady();
2454
2455 if (newState == oldState && m_tracksAreReady == tracksAreReady)
2456 return;
2457
2458 m_tracksAreReady = tracksAreReady;
2459#else
2460 if (newState == oldState)
2461 return;
2462 bool tracksAreReady = true;
2463#endif
2464
2465 ALWAYS_LOG(LOGIDENTIFIER, "new state = ", state, ", current state = ", m_readyState);
2466
2467 if (tracksAreReady)
2468 m_readyState = newState;
2469 else {
2470 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
2471 // the text tracks are ready, regardless of the state of the media file.
2472 if (newState <= HAVE_METADATA)
2473 m_readyState = newState;
2474 else
2475 m_readyState = HAVE_CURRENT_DATA;
2476 }
2477
2478 if (oldState > m_readyStateMaximum)
2479 m_readyStateMaximum = oldState;
2480
2481 if (m_networkState == NETWORK_EMPTY)
2482 return;
2483
2484 if (m_seeking) {
2485 // 4.8.10.9, step 11
2486 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
2487 scheduleEvent(eventNames().waitingEvent);
2488
2489 // 4.8.10.10 step 14 & 15.
2490 if (m_seekRequested && !m_player->seeking() && m_readyState >= HAVE_CURRENT_DATA)
2491 finishSeek();
2492 } else {
2493 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
2494 // 4.8.10.8
2495 invalidateCachedTime();
2496 scheduleTimeupdateEvent(false);
2497 scheduleEvent(eventNames().waitingEvent);
2498 }
2499 }
2500
2501 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
2502 prepareMediaFragmentURI();
2503 scheduleEvent(eventNames().durationchangeEvent);
2504 scheduleResizeEvent();
2505 scheduleEvent(eventNames().loadedmetadataEvent);
2506#if ENABLE(WIRELESS_PLAYBACK_TARGET)
2507 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent))
2508 enqueuePlaybackTargetAvailabilityChangedEvent();
2509#endif
2510 m_initiallyMuted = m_volume < 0.05 || muted();
2511
2512 if (hasMediaControls())
2513 mediaControls()->loadedMetadata();
2514 updateRenderer();
2515
2516 if (is<MediaDocument>(document()))
2517 downcast<MediaDocument>(document()).mediaElementNaturalSizeChanged(expandedIntSize(m_player->naturalSize()));
2518
2519 logMediaLoadRequest(document().page(), m_player->engineDescription(), String(), true);
2520
2521#if ENABLE(WIRELESS_PLAYBACK_TARGET)
2522 scheduleUpdateMediaState();
2523#endif
2524
2525 m_mediaSession->clientCharacteristicsChanged();
2526 }
2527
2528 bool shouldUpdateDisplayState = false;
2529
2530 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA) {
2531 if (!m_haveFiredLoadedData) {
2532 m_haveFiredLoadedData = true;
2533 scheduleEvent(eventNames().loadeddataEvent);
2534 // FIXME: It's not clear that it's correct to skip these two operations just
2535 // because m_haveFiredLoadedData is already true. At one time we were skipping
2536 // the call to setShouldDelayLoadEvent, which was definitely incorrect.
2537 shouldUpdateDisplayState = true;
2538 applyMediaFragmentURI();
2539 }
2540 setShouldDelayLoadEvent(false);
2541 }
2542
2543 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
2544 scheduleEvent(eventNames().canplayEvent);
2545 shouldUpdateDisplayState = true;
2546 }
2547
2548 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
2549 if (oldState <= HAVE_CURRENT_DATA)
2550 scheduleEvent(eventNames().canplayEvent);
2551
2552 scheduleEvent(eventNames().canplaythroughEvent);
2553
2554 auto success = canTransitionFromAutoplayToPlay();
2555 if (success) {
2556 m_paused = false;
2557 invalidateCachedTime();
2558 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::StartedWithoutUserGesture);
2559 m_playbackStartedTime = currentMediaTime().toDouble();
2560 scheduleEvent(eventNames().playEvent);
2561 } else if (success.value() == MediaPlaybackDenialReason::UserGestureRequired) {
2562 ALWAYS_LOG(LOGIDENTIFIER, "Autoplay blocked, user gesture required");
2563 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
2564 }
2565
2566 shouldUpdateDisplayState = true;
2567 }
2568
2569 // If we transition to the Future Data state and we're about to begin playing, ensure playback is actually permitted first,
2570 // honoring any playback denial reasons such as the requirement of a user gesture.
2571 if (m_readyState == HAVE_FUTURE_DATA && oldState < HAVE_FUTURE_DATA && potentiallyPlaying() && !m_mediaSession->playbackPermitted()) {
2572 auto canTransition = canTransitionFromAutoplayToPlay();
2573 if (canTransition && canTransition.value() == MediaPlaybackDenialReason::UserGestureRequired)
2574 ALWAYS_LOG(LOGIDENTIFIER, "Autoplay blocked, user gesture required");
2575
2576 pauseInternal();
2577 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
2578 }
2579
2580 if (shouldUpdateDisplayState) {
2581 updateDisplayState();
2582 if (hasMediaControls()) {
2583 mediaControls()->refreshClosedCaptionsButtonVisibility();
2584 mediaControls()->updateStatusDisplay();
2585 }
2586 }
2587
2588 updatePlayState();
2589 updateMediaController();
2590#if ENABLE(VIDEO_TRACK)
2591 updateActiveTextTrackCues(currentMediaTime());
2592#endif
2593}
2594
2595#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
2596RefPtr<ArrayBuffer> HTMLMediaElement::mediaPlayerCachedKeyForKeyId(const String& keyId) const
2597{
2598 return m_webKitMediaKeys ? m_webKitMediaKeys->cachedKeyForKeyId(keyId) : nullptr;
2599}
2600
2601bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData)
2602{
2603 if (!RuntimeEnabledFeatures::sharedFeatures().legacyEncryptedMediaAPIEnabled())
2604 return false;
2605
2606 if (!hasEventListeners("webkitneedkey")
2607#if ENABLE(ENCRYPTED_MEDIA)
2608 // Only fire an error if ENCRYPTED_MEDIA is not enabled, to give clients of the
2609 // "encrypted" event a chance to handle it without resulting in a synthetic error.
2610 && (!RuntimeEnabledFeatures::sharedFeatures().encryptedMediaAPIEnabled() || document().quirks().hasBrokenEncryptedMediaAPISupportQuirk())
2611#endif
2612 ) {
2613 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
2614 scheduleEvent(eventNames().errorEvent);
2615 return false;
2616 }
2617
2618 auto event = WebKitMediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initData);
2619 event->setTarget(this);
2620 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2621
2622 return true;
2623}
2624
2625String HTMLMediaElement::mediaPlayerMediaKeysStorageDirectory() const
2626{
2627 auto* page = document().page();
2628 if (!page || page->usesEphemeralSession())
2629 return emptyString();
2630
2631 String storageDirectory = document().settings().mediaKeysStorageDirectory();
2632 if (storageDirectory.isEmpty())
2633 return emptyString();
2634
2635 return FileSystem::pathByAppendingComponent(storageDirectory, document().securityOrigin().data().databaseIdentifier());
2636}
2637
2638void HTMLMediaElement::webkitSetMediaKeys(WebKitMediaKeys* mediaKeys)
2639{
2640 if (!RuntimeEnabledFeatures::sharedFeatures().legacyEncryptedMediaAPIEnabled())
2641 return;
2642
2643 if (m_webKitMediaKeys == mediaKeys)
2644 return;
2645
2646 if (m_webKitMediaKeys)
2647 m_webKitMediaKeys->setMediaElement(nullptr);
2648 m_webKitMediaKeys = mediaKeys;
2649 if (m_webKitMediaKeys)
2650 m_webKitMediaKeys->setMediaElement(this);
2651}
2652
2653void HTMLMediaElement::keyAdded()
2654{
2655 if (!RuntimeEnabledFeatures::sharedFeatures().legacyEncryptedMediaAPIEnabled())
2656 return;
2657
2658 if (m_player)
2659 m_player->keyAdded();
2660}
2661
2662#endif
2663
2664#if ENABLE(ENCRYPTED_MEDIA)
2665
2666MediaKeys* HTMLMediaElement::mediaKeys() const
2667{
2668 return m_mediaKeys.get();
2669}
2670
2671void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys, Ref<DeferredPromise>&& promise)
2672{
2673 // https://w3c.github.io/encrypted-media/#dom-htmlmediaelement-setmediakeys
2674 // W3C Editor's Draft 23 June 2017
2675
2676 // 1. If this object's attaching media keys value is true, return a promise rejected with an InvalidStateError.
2677 if (m_attachingMediaKeys) {
2678 promise->reject(InvalidStateError);
2679 return;
2680 }
2681
2682 // 2. If mediaKeys and the mediaKeys attribute are the same object, return a resolved promise.
2683 if (mediaKeys == m_mediaKeys) {
2684 promise->resolve();
2685 return;
2686 }
2687
2688 // 3. Let this object's attaching media keys value be true.
2689 m_attachingMediaKeys = true;
2690
2691 // 4. Let promise be a new promise.
2692 // 5. Run the following steps in parallel:
2693 m_encryptedMediaQueue.enqueueTask([this, mediaKeys = RefPtr<MediaKeys>(mediaKeys), promise = WTFMove(promise)]() mutable {
2694 // 5.1. If all the following conditions hold:
2695 // - mediaKeys is not null,
2696 // - the CDM instance represented by mediaKeys is already in use by another media element
2697 // - the user agent is unable to use it with this element
2698 // then let this object's attaching media keys value be false and reject promise with a QuotaExceededError.
2699 // FIXME: ^
2700
2701 // 5.2. If the mediaKeys attribute is not null, run the following steps:
2702 if (m_mediaKeys) {
2703 // 5.2.1. If the user agent or CDM do not support removing the association, let this object's attaching media keys value be false and reject promise with a NotSupportedError.
2704 // 5.2.2. If the association cannot currently be removed, let this object's attaching media keys value be false and reject promise with an InvalidStateError.
2705 // 5.2.3. Stop using the CDM instance represented by the mediaKeys attribute to decrypt media data and remove the association with the media element.
2706 // 5.2.4. If the preceding step failed, let this object's attaching media keys value be false and reject promise with the appropriate error name.
2707 // FIXME: ^
2708
2709 m_mediaKeys->detachCDMClient(*this);
2710 if (m_player)
2711 m_player->cdmInstanceDetached(m_mediaKeys->cdmInstance());
2712 }
2713
2714 // 5.3. If mediaKeys is not null, run the following steps:
2715 if (mediaKeys) {
2716 // 5.3.1. Associate the CDM instance represented by mediaKeys with the media element for decrypting media data.
2717 mediaKeys->attachCDMClient(*this);
2718 if (m_player)
2719 m_player->cdmInstanceAttached(mediaKeys->cdmInstance());
2720
2721 // 5.3.2. If the preceding step failed, run the following steps:
2722 // 5.3.2.1. Set the mediaKeys attribute to null.
2723 // 5.3.2.2. Let this object's attaching media keys value be false.
2724 // 5.3.2.3. Reject promise with a new DOMException whose name is the appropriate error name.
2725 // FIXME: ^
2726
2727 // 5.3.3. Queue a task to run the Attempt to Resume Playback If Necessary algorithm on the media element.
2728 m_encryptedMediaQueue.enqueueTask([this] {
2729 attemptToResumePlaybackIfNecessary();
2730 });
2731 }
2732
2733 // 5.4. Set the mediaKeys attribute to mediaKeys.
2734 // 5.5. Let this object's attaching media keys value be false.
2735 // 5.6. Resolve promise.
2736 m_mediaKeys = WTFMove(mediaKeys);
2737 m_attachingMediaKeys = false;
2738 promise->resolve();
2739 });
2740
2741 // 6. Return promise.
2742}
2743
2744void HTMLMediaElement::mediaPlayerInitializationDataEncountered(const String& initDataType, RefPtr<ArrayBuffer>&& initData)
2745{
2746 if (!RuntimeEnabledFeatures::sharedFeatures().encryptedMediaAPIEnabled() || document().quirks().hasBrokenEncryptedMediaAPISupportQuirk())
2747 return;
2748
2749 // https://w3c.github.io/encrypted-media/#initdata-encountered
2750 // W3C Editor's Draft 23 June 2017
2751
2752 // 1. Let the media element be the specified HTMLMediaElement object.
2753 // 2. Let initDataType be the empty string.
2754 // 3. Let initData be null.
2755 // 4. If the media data is CORS-same-origin and not mixed content, run the following steps:
2756 // 4.1. Let initDataType be the string representing the Initialization Data Type of the Initialization Data.
2757 // 4.2. Let initData be the Initialization Data.
2758 // FIXME: ^
2759
2760 // 5. Queue a task to create an event named encrypted that does not bubble and is not cancellable using the
2761 // MediaEncryptedEvent interface with its type attribute set to encrypted and its isTrusted attribute
2762 // initialized to true, and dispatch it at the media element.
2763 // The event interface MediaEncryptedEvent has:
2764 // initDataType = initDataType
2765 // initData = initData
2766 MediaEncryptedEventInit initializer { initDataType, WTFMove(initData) };
2767 m_asyncEventQueue.enqueueEvent(MediaEncryptedEvent::create(eventNames().encryptedEvent, initializer, Event::IsTrusted::Yes));
2768}
2769
2770void HTMLMediaElement::mediaPlayerWaitingForKeyChanged()
2771{
2772 if (!m_player)
2773 return;
2774
2775 if (!m_player->waitingForKey() && m_playbackBlockedWaitingForKey) {
2776 // https://w3c.github.io/encrypted-media/#resume-playback
2777 // W3C Editor's Draft 23 June 2017
2778
2779 // NOTE: continued from HTMLMediaElement::attemptToDecrypt().
2780 // 4. If the user agent can advance the current playback position in the direction of playback:
2781 // 4.1. Set the media element's decryption blocked waiting for key value to false.
2782 // FIXME: ^
2783 // 4.2. Set the media element's playback blocked waiting for key value to false.
2784 m_playbackBlockedWaitingForKey = false;
2785
2786 // 4.3. Set the media element's readyState value to HAVE_CURRENT_DATA, HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA as appropriate.
2787 setReadyState(m_player->readyState());
2788
2789 return;
2790 }
2791
2792 // https://www.w3.org/TR/encrypted-media/#wait-for-key
2793 // W3C Recommendation 18 September 2017
2794
2795 // The Wait for Key algorithm queues a waitingforkey event and
2796 // updates readyState. It should only be called when the
2797 // HTMLMediaElement object is potentially playing and its
2798 // readyState is equal to HAVE_FUTURE_DATA or greater. Requests to
2799 // run this algorithm include a target HTMLMediaElement object.
2800
2801 // The following steps are run:
2802
2803 // 1. Let the media element be the specified HTMLMediaElement
2804 // object.
2805 // 2. If the media element's playback blocked waiting for key
2806 // value is true, abort these steps.
2807 if (m_playbackBlockedWaitingForKey)
2808 return;
2809
2810 // 3. Set the media element's playback blocked waiting for key
2811 // value to true.
2812 m_playbackBlockedWaitingForKey = true;
2813
2814 // NOTE
2815 // As a result of the above step, the media element will become a
2816 // blocked media element if it wasn't already. In that case, the
2817 // media element will stop playback.
2818
2819 // 4. Follow the steps for the first matching condition from the
2820 // following list:
2821
2822 // If data for the immediate current playback position is
2823 // available
2824 // Set the readyState of media element to HAVE_CURRENT_DATA.
2825 // Otherwise
2826 // Set the readyState of media element to HAVE_METADATA.
2827 ReadyState nextReadyState = buffered()->contain(currentTime()) ? HAVE_CURRENT_DATA : HAVE_METADATA;
2828 if (nextReadyState < m_readyState)
2829 setReadyState(static_cast<MediaPlayer::ReadyState>(nextReadyState));
2830
2831 // NOTE
2832 // In other words, if the video frame and audio data for the
2833 // current playback position have been decoded because they were
2834 // unencrypted and/or successfully decrypted, set readyState to
2835 // HAVE_CURRENT_DATA. Otherwise, including if this was previously
2836 // the case but the data is no longer available, set readyState to
2837 // HAVE_METADATA.
2838
2839 // 5. Queue a task to fire a simple event named waitingforkey at the
2840 // media element.
2841 scheduleEvent(eventNames().waitingforkeyEvent);
2842
2843 // 6. Suspend playback.
2844 // GStreamer handles this without suspending explicitly.
2845}
2846
2847void HTMLMediaElement::attemptToDecrypt()
2848{
2849 // https://w3c.github.io/encrypted-media/#attempt-to-decrypt
2850 // W3C Editor's Draft 23 June 2017
2851
2852 // 1. Let the media element be the specified HTMLMediaElement object.
2853 // 2. If the media element's encrypted block queue is empty, abort these steps.
2854 // FIXME: ^
2855
2856 // 3. If the media element's mediaKeys attribute is not null, run the following steps:
2857 if (m_mediaKeys) {
2858 // 3.1. Let media keys be the MediaKeys object referenced by that attribute.
2859 // 3.2. Let cdm be the CDM instance represented by media keys's cdm instance value.
2860 auto& cdmInstance = m_mediaKeys->cdmInstance();
2861
2862 // 3.3. If cdm is no longer usable for any reason, run the following steps:
2863 // 3.3.1. Run the media data is corrupted steps of the resource fetch algorithm.
2864 // 3.3.2. Run the CDM Unavailable algorithm on media keys.
2865 // 3.3.3. Abort these steps.
2866 // FIXME: ^
2867
2868 // 3.4. If there is at least one MediaKeySession created by the media keys that is not closed, run the following steps:
2869 if (m_mediaKeys->hasOpenSessions()) {
2870 // Continued in MediaPlayer::attemptToDecryptWithInstance().
2871 if (m_player)
2872 m_player->attemptToDecryptWithInstance(cdmInstance);
2873 }
2874 }
2875
2876 // 4. Set the media element's decryption blocked waiting for key value to true.
2877 // FIXME: ^
2878}
2879
2880void HTMLMediaElement::attemptToResumePlaybackIfNecessary()
2881{
2882 // https://w3c.github.io/encrypted-media/#resume-playback
2883 // W3C Editor's Draft 23 June 2017
2884
2885 // 1. Let the media element be the specified HTMLMediaElement object.
2886 // 2. If the media element's playback blocked waiting for key is false, abort these steps.
2887 if (!m_playbackBlockedWaitingForKey)
2888 return;
2889
2890 // 3. Run the Attempt to Decrypt algorithm on the media element.
2891 attemptToDecrypt();
2892
2893 // NOTE: continued in HTMLMediaElement::waitingForKeyChanged()
2894}
2895
2896void HTMLMediaElement::cdmClientAttemptToResumePlaybackIfNecessary()
2897{
2898 attemptToResumePlaybackIfNecessary();
2899}
2900
2901#endif // ENABLE(ENCRYPTED_MEDIA)
2902
2903void HTMLMediaElement::progressEventTimerFired()
2904{
2905 ASSERT(m_player);
2906 if (m_networkState != NETWORK_LOADING)
2907 return;
2908
2909 MonotonicTime time = MonotonicTime::now();
2910 Seconds timedelta = time - m_previousProgressTime;
2911
2912 if (m_player->didLoadingProgress()) {
2913 scheduleEvent(eventNames().progressEvent);
2914 m_previousProgressTime = time;
2915 m_sentStalledEvent = false;
2916 updateRenderer();
2917 if (hasMediaControls())
2918 mediaControls()->bufferingProgressed();
2919 } else if (timedelta > 3_s && !m_sentStalledEvent) {
2920 scheduleEvent(eventNames().stalledEvent);
2921 m_sentStalledEvent = true;
2922 setShouldDelayLoadEvent(false);
2923 }
2924}
2925
2926void HTMLMediaElement::rewind(double timeDelta)
2927{
2928 setCurrentTime(std::max(currentMediaTime() - MediaTime::createWithDouble(timeDelta), minTimeSeekable()));
2929}
2930
2931void HTMLMediaElement::returnToRealtime()
2932{
2933 setCurrentTime(maxTimeSeekable());
2934}
2935
2936void HTMLMediaElement::addPlayedRange(const MediaTime& start, const MediaTime& end)
2937{
2938 DEBUG_LOG(LOGIDENTIFIER, MediaTimeRange { start, end });
2939 if (!m_playedTimeRanges)
2940 m_playedTimeRanges = TimeRanges::create();
2941 m_playedTimeRanges->ranges().add(start, end);
2942}
2943
2944bool HTMLMediaElement::supportsScanning() const
2945{
2946 return m_player ? m_player->supportsScanning() : false;
2947}
2948
2949void HTMLMediaElement::prepareToPlay()
2950{
2951 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
2952
2953 INFO_LOG(LOGIDENTIFIER);
2954 if (m_havePreparedToPlay || !document().hasBrowsingContext())
2955 return;
2956 m_havePreparedToPlay = true;
2957 if (m_player)
2958 m_player->prepareToPlay();
2959}
2960
2961void HTMLMediaElement::fastSeek(double time)
2962{
2963 fastSeek(MediaTime::createWithDouble(time));
2964}
2965
2966void HTMLMediaElement::fastSeek(const MediaTime& time)
2967{
2968 INFO_LOG(LOGIDENTIFIER, time);
2969 // 4.7.10.9 Seeking
2970 // 9. If the approximate-for-speed flag is set, adjust the new playback position to a value that will
2971 // allow for playback to resume promptly. If new playback position before this step is before current
2972 // playback position, then the adjusted new playback position must also be before the current playback
2973 // position. Similarly, if the new playback position before this step is after current playback position,
2974 // then the adjusted new playback position must also be after the current playback position.
2975 refreshCachedTime();
2976
2977 MediaTime delta = time - currentMediaTime();
2978 MediaTime negativeTolerance = delta < MediaTime::zeroTime() ? MediaTime::positiveInfiniteTime() : delta;
2979 seekWithTolerance(time, negativeTolerance, MediaTime::zeroTime(), true);
2980}
2981
2982void HTMLMediaElement::seek(const MediaTime& time)
2983{
2984 INFO_LOG(LOGIDENTIFIER, time);
2985 seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), true);
2986}
2987
2988void HTMLMediaElement::seekInternal(const MediaTime& time)
2989{
2990 INFO_LOG(LOGIDENTIFIER, time);
2991 seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), false);
2992}
2993
2994void HTMLMediaElement::seekWithTolerance(const MediaTime& inTime, const MediaTime& negativeTolerance, const MediaTime& positiveTolerance, bool fromDOM)
2995{
2996 // 4.8.10.9 Seeking
2997 MediaTime time = inTime;
2998
2999 // 1 - Set the media element's show poster flag to false.
3000 setDisplayMode(Video);
3001
3002 // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
3003 if (m_readyState == HAVE_NOTHING || !m_player)
3004 return;
3005
3006 // If the media engine has been told to postpone loading data, let it go ahead now.
3007 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
3008 prepareToPlay();
3009
3010 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
3011 refreshCachedTime();
3012 MediaTime now = currentMediaTime();
3013
3014 // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
3015 // already running. Abort that other instance of the algorithm without waiting for the step that
3016 // it is running to complete.
3017 if (m_seekTaskQueue.hasPendingTask()) {
3018 INFO_LOG(LOGIDENTIFIER, "cancelling pending seeks");
3019 m_seekTaskQueue.cancelTask();
3020 if (m_pendingSeek) {
3021 now = m_pendingSeek->now;
3022 m_pendingSeek = nullptr;
3023 }
3024 m_pendingSeekType = NoSeek;
3025 }
3026
3027 // 4 - Set the seeking IDL attribute to true.
3028 // The flag will be cleared when the engine tells us the time has actually changed.
3029 m_seeking = true;
3030 if (m_playing) {
3031 if (m_lastSeekTime < now)
3032 addPlayedRange(m_lastSeekTime, now);
3033 }
3034 m_lastSeekTime = time;
3035
3036 // 5 - If the seek was in response to a DOM method call or setting of an IDL attribute, then continue
3037 // the script. The remainder of these steps must be run asynchronously.
3038 m_pendingSeek = std::make_unique<PendingSeek>(now, time, negativeTolerance, positiveTolerance);
3039 if (fromDOM) {
3040 INFO_LOG(LOGIDENTIFIER, "enqueuing seek from ", now, " to ", time);
3041 m_seekTaskQueue.scheduleTask(std::bind(&HTMLMediaElement::seekTask, this));
3042 } else
3043 seekTask();
3044
3045 if (processingUserGestureForMedia())
3046 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
3047}
3048
3049void HTMLMediaElement::seekTask()
3050{
3051 INFO_LOG(LOGIDENTIFIER);
3052
3053 if (!m_player) {
3054 clearSeeking();
3055 return;
3056 }
3057
3058 ASSERT(m_pendingSeek);
3059 MediaTime now = m_pendingSeek->now;
3060 MediaTime time = m_pendingSeek->targetTime;
3061 MediaTime negativeTolerance = m_pendingSeek->negativeTolerance;
3062 MediaTime positiveTolerance = m_pendingSeek->positiveTolerance;
3063 m_pendingSeek = nullptr;
3064
3065 ASSERT(negativeTolerance >= MediaTime::zeroTime());
3066
3067 // 6 - If the new playback position is later than the end of the media resource, then let it be the end
3068 // of the media resource instead.
3069 time = std::min(time, durationMediaTime());
3070
3071 // 7 - If the new playback position is less than the earliest possible position, let it be that position instead.
3072 MediaTime earliestTime = m_player->startTime();
3073 time = std::max(time, earliestTime);
3074
3075 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
3076 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
3077 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
3078 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
3079 // fire a 'seeked' event.
3080 if (willLog(WTFLogLevel::Debug)) {
3081 MediaTime mediaTime = m_player->mediaTimeForTimeValue(time);
3082 if (time != mediaTime)
3083 INFO_LOG(LOGIDENTIFIER, time, " media timeline equivalent is ", mediaTime);
3084 }
3085
3086 time = m_player->mediaTimeForTimeValue(time);
3087
3088 // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the
3089 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
3090 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
3091 // attribute then set the seeking IDL attribute to false and abort these steps.
3092 RefPtr<TimeRanges> seekableRanges = seekable();
3093 bool noSeekRequired = !seekableRanges->length();
3094
3095 // Short circuit seeking to the current time by just firing the events if no seek is required.
3096 // Don't skip calling the media engine if 1) we are in poster mode (because a seek should always cancel
3097 // poster display), or 2) if there is a pending fast seek, or 3) if this seek is not an exact seek
3098 SeekType thisSeekType = (negativeTolerance == MediaTime::zeroTime() && positiveTolerance == MediaTime::zeroTime()) ? Precise : Fast;
3099 if (!noSeekRequired && time == now && thisSeekType == Precise && m_pendingSeekType != Fast && displayMode() != Poster)
3100 noSeekRequired = true;
3101
3102#if ENABLE(MEDIA_SOURCE)
3103 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
3104 // always in a flushed state when the 'seeking' event fires.
3105 if (m_mediaSource && !m_mediaSource->isClosed())
3106 noSeekRequired = false;
3107#endif
3108
3109 if (noSeekRequired) {
3110 INFO_LOG(LOGIDENTIFIER, "ignored seek to ", time);
3111 if (time == now) {
3112 scheduleEvent(eventNames().seekingEvent);
3113 scheduleTimeupdateEvent(false);
3114 scheduleEvent(eventNames().seekedEvent);
3115 }
3116 clearSeeking();
3117 return;
3118 }
3119 time = seekableRanges->ranges().nearest(time);
3120
3121 m_sentEndEvent = false;
3122 m_lastSeekTime = time;
3123 m_pendingSeekType = thisSeekType;
3124 m_seeking = true;
3125
3126 // 10 - Queue a task to fire a simple event named seeking at the element.
3127 scheduleEvent(eventNames().seekingEvent);
3128
3129 // 11 - Set the current playback position to the given new playback position
3130 m_seekRequested = true;
3131 m_player->seekWithTolerance(time, negativeTolerance, positiveTolerance);
3132
3133 // 12 - Wait until the user agent has established whether or not the media data for the new playback
3134 // position is available, and, if it is, until it has decoded enough data to play back that position.
3135 // 13 - Await a stable state. The synchronous section consists of all the remaining steps of this algorithm.
3136}
3137
3138void HTMLMediaElement::clearSeeking()
3139{
3140 m_seeking = false;
3141 m_seekRequested = false;
3142 m_pendingSeekType = NoSeek;
3143 invalidateCachedTime();
3144}
3145
3146void HTMLMediaElement::finishSeek()
3147{
3148 // 4.8.10.9 Seeking
3149 // 14 - Set the seeking IDL attribute to false.
3150 clearSeeking();
3151
3152 INFO_LOG(LOGIDENTIFIER, "current time = ", currentMediaTime());
3153
3154 // 15 - Run the time maches on steps.
3155 // Handled by mediaPlayerTimeChanged().
3156
3157 // 16 - Queue a task to fire a simple event named timeupdate at the element.
3158 scheduleEvent(eventNames().timeupdateEvent);
3159
3160 // 17 - Queue a task to fire a simple event named seeked at the element.
3161 scheduleEvent(eventNames().seekedEvent);
3162
3163 if (m_mediaSession)
3164 m_mediaSession->clientCharacteristicsChanged();
3165
3166#if ENABLE(MEDIA_SOURCE)
3167 if (m_mediaSource)
3168 m_mediaSource->monitorSourceBuffers();
3169#endif
3170}
3171
3172HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
3173{
3174 return m_readyState;
3175}
3176
3177MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
3178{
3179 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
3180}
3181
3182bool HTMLMediaElement::hasAudio() const
3183{
3184 return m_player ? m_player->hasAudio() : false;
3185}
3186
3187bool HTMLMediaElement::seeking() const
3188{
3189 return m_seeking;
3190}
3191
3192void HTMLMediaElement::refreshCachedTime() const
3193{
3194 if (!m_player)
3195 return;
3196
3197 m_cachedTime = m_player->currentTime();
3198 if (!m_cachedTime) {
3199 // Do not use m_cachedTime until the media engine returns a non-zero value because we can't
3200 // estimate current time until playback actually begins.
3201 invalidateCachedTime();
3202 return;
3203 }
3204
3205 m_clockTimeAtLastCachedTimeUpdate = MonotonicTime::now();
3206}
3207
3208void HTMLMediaElement::invalidateCachedTime() const
3209{
3210 m_cachedTime = MediaTime::invalidTime();
3211 if (!m_player || !m_player->maximumDurationToCacheMediaTime())
3212 return;
3213
3214 // Don't try to cache movie time when playback first starts as the time reported by the engine
3215 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
3216 // too early.
3217 static const Seconds minimumTimePlayingBeforeCacheSnapshot = 500_ms;
3218
3219 m_minimumClockTimeToUpdateCachedTime = MonotonicTime::now() + minimumTimePlayingBeforeCacheSnapshot;
3220}
3221
3222// playback state
3223double HTMLMediaElement::currentTime() const
3224{
3225 return currentMediaTime().toDouble();
3226}
3227
3228MediaTime HTMLMediaElement::currentMediaTime() const
3229{
3230#if LOG_CACHED_TIME_WARNINGS
3231 static const MediaTime minCachedDeltaForWarning = MediaTime::create(1, 100);
3232#endif
3233
3234 if (!m_player)
3235 return MediaTime::zeroTime();
3236
3237 if (m_seeking) {
3238 INFO_LOG(LOGIDENTIFIER, "seeking, returning", m_lastSeekTime);
3239 return m_lastSeekTime;
3240 }
3241
3242 if (m_cachedTime.isValid() && m_paused) {
3243#if LOG_CACHED_TIME_WARNINGS
3244 MediaTime delta = m_cachedTime - m_player->currentTime();
3245 if (delta > minCachedDeltaForWarning)
3246 WARNING_LOG(LOGIDENTIFIER, "cached time is ", delta, " seconds off of media time when paused");
3247#endif
3248 return m_cachedTime;
3249 }
3250
3251 // Is it too soon use a cached time?
3252 MonotonicTime now = MonotonicTime::now();
3253 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
3254
3255 if (maximumDurationToCacheMediaTime && m_cachedTime.isValid() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) {
3256 Seconds clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
3257
3258 // Not too soon, use the cached time only if it hasn't expired.
3259 if (clockDelta.seconds() < maximumDurationToCacheMediaTime) {
3260 MediaTime adjustedCacheTime = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta.seconds());
3261
3262#if LOG_CACHED_TIME_WARNINGS
3263 MediaTime delta = adjustedCacheTime - m_player->currentTime();
3264 if (delta > minCachedDeltaForWarning)
3265 WARNING_LOG(LOGIDENTIFIER, "cached time is ", delta, " seconds off of media time when playing");
3266#endif
3267 return adjustedCacheTime;
3268 }
3269 }
3270
3271#if LOG_CACHED_TIME_WARNINGS
3272 if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) {
3273 Seconds clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
3274 MediaTime delta = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta.seconds()) - m_player->currentTime();
3275 WARNING_LOG(LOGIDENTIFIER, "cached time was ", delta, " seconds off of media time when it expired");
3276 }
3277#endif
3278
3279 refreshCachedTime();
3280
3281 if (m_cachedTime.isInvalid())
3282 return MediaTime::zeroTime();
3283
3284 return m_cachedTime;
3285}
3286
3287void HTMLMediaElement::setCurrentTime(double time)
3288{
3289 setCurrentTime(MediaTime::createWithDouble(time));
3290}
3291
3292void HTMLMediaElement::setCurrentTimeWithTolerance(double time, double toleranceBefore, double toleranceAfter)
3293{
3294 seekWithTolerance(MediaTime::createWithDouble(time), MediaTime::createWithDouble(toleranceBefore), MediaTime::createWithDouble(toleranceAfter), true);
3295}
3296
3297void HTMLMediaElement::setCurrentTime(const MediaTime& time)
3298{
3299 if (m_mediaController)
3300 return;
3301
3302 seekInternal(time);
3303}
3304
3305ExceptionOr<void> HTMLMediaElement::setCurrentTimeForBindings(double time)
3306{
3307 if (m_mediaController)
3308 return Exception { InvalidStateError };
3309 seek(MediaTime::createWithDouble(time));
3310 return { };
3311}
3312
3313double HTMLMediaElement::duration() const
3314{
3315 return durationMediaTime().toDouble();
3316}
3317
3318MediaTime HTMLMediaElement::durationMediaTime() const
3319{
3320 if (m_player && m_readyState >= HAVE_METADATA)
3321 return m_player->duration();
3322
3323 return MediaTime::invalidTime();
3324}
3325
3326bool HTMLMediaElement::paused() const
3327{
3328 // As of this writing, JavaScript garbage collection calls this function directly. In the past
3329 // we had problems where this was called on an object after a bad cast. The assertion below
3330 // made our regression test detect the problem, so we should keep it because of that. But note
3331 // that the value of the assertion relies on the compiler not being smart enough to know that
3332 // isHTMLUnknownElement is guaranteed to return false for an HTMLMediaElement.
3333 ASSERT(!isHTMLUnknownElement());
3334
3335 return m_paused;
3336}
3337
3338double HTMLMediaElement::defaultPlaybackRate() const
3339{
3340#if ENABLE(MEDIA_STREAM)
3341 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3342 // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0
3343 // A MediaStream is not seekable. Therefore, this attribute must always have the
3344 // value 1.0 and any attempt to alter it must be ignored. Note that this also means
3345 // that the ratechange event will not fire.
3346 if (m_mediaStreamSrcObject)
3347 return 1;
3348#endif
3349
3350 return m_defaultPlaybackRate;
3351}
3352
3353void HTMLMediaElement::setDefaultPlaybackRate(double rate)
3354{
3355#if ENABLE(MEDIA_STREAM)
3356 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3357 // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0
3358 // A MediaStream is not seekable. Therefore, this attribute must always have the
3359 // value 1.0 and any attempt to alter it must be ignored. Note that this also means
3360 // that the ratechange event will not fire.
3361 if (m_mediaStreamSrcObject)
3362 return;
3363#endif
3364
3365 if (m_defaultPlaybackRate == rate)
3366 return;
3367
3368 ALWAYS_LOG(LOGIDENTIFIER, rate);
3369 m_defaultPlaybackRate = rate;
3370 scheduleEvent(eventNames().ratechangeEvent);
3371}
3372
3373double HTMLMediaElement::effectivePlaybackRate() const
3374{
3375 return m_mediaController ? m_mediaController->playbackRate() : m_reportedPlaybackRate;
3376}
3377
3378double HTMLMediaElement::requestedPlaybackRate() const
3379{
3380 return m_mediaController ? m_mediaController->playbackRate() : m_requestedPlaybackRate;
3381}
3382
3383double HTMLMediaElement::playbackRate() const
3384{
3385#if ENABLE(MEDIA_STREAM)
3386 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3387 // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always
3388 // have the value 1.0 and any attempt to alter it must be ignored. Note that this also
3389 // means that the ratechange event will not fire.
3390 if (m_mediaStreamSrcObject)
3391 return 1;
3392#endif
3393
3394 return m_requestedPlaybackRate;
3395}
3396
3397void HTMLMediaElement::setPlaybackRate(double rate)
3398{
3399 ALWAYS_LOG(LOGIDENTIFIER, rate);
3400
3401#if ENABLE(MEDIA_STREAM)
3402 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3403 // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always
3404 // have the value 1.0 and any attempt to alter it must be ignored. Note that this also
3405 // means that the ratechange event will not fire.
3406 if (m_mediaStreamSrcObject)
3407 return;
3408#endif
3409
3410 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
3411 m_player->setRate(rate);
3412
3413 if (m_requestedPlaybackRate != rate) {
3414 m_reportedPlaybackRate = m_requestedPlaybackRate = rate;
3415 invalidateCachedTime();
3416 scheduleEvent(eventNames().ratechangeEvent);
3417 }
3418}
3419
3420void HTMLMediaElement::updatePlaybackRate()
3421{
3422 double requestedRate = requestedPlaybackRate();
3423 if (m_player && potentiallyPlaying() && m_player->rate() != requestedRate)
3424 m_player->setRate(requestedRate);
3425}
3426
3427bool HTMLMediaElement::webkitPreservesPitch() const
3428{
3429 return m_webkitPreservesPitch;
3430}
3431
3432void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
3433{
3434 INFO_LOG(LOGIDENTIFIER, preservesPitch);
3435
3436 m_webkitPreservesPitch = preservesPitch;
3437
3438 if (!m_player)
3439 return;
3440
3441 m_player->setPreservesPitch(preservesPitch);
3442}
3443
3444bool HTMLMediaElement::ended() const
3445{
3446#if ENABLE(MEDIA_STREAM)
3447 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3448 // When the MediaStream state moves from the active to the inactive state, the User Agent
3449 // must raise an ended event on the HTMLMediaElement and set its ended attribute to true.
3450 if (m_mediaStreamSrcObject && m_player && m_player->ended())
3451 return true;
3452#endif
3453
3454 // 4.8.10.8 Playing the media resource
3455 // The ended attribute must return true if the media element has ended
3456 // playback and the direction of playback is forwards, and false otherwise.
3457 return endedPlayback() && requestedPlaybackRate() > 0;
3458}
3459
3460bool HTMLMediaElement::autoplay() const
3461{
3462 return hasAttributeWithoutSynchronization(autoplayAttr);
3463}
3464
3465String HTMLMediaElement::preload() const
3466{
3467#if ENABLE(MEDIA_STREAM)
3468 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3469 // "preload" - On getting: none. On setting: ignored.
3470 if (m_mediaStreamSrcObject)
3471 return "none"_s;
3472#endif
3473
3474 switch (m_preload) {
3475 case MediaPlayer::None:
3476 return "none"_s;
3477 case MediaPlayer::MetaData:
3478 return "metadata"_s;
3479 case MediaPlayer::Auto:
3480 return "auto"_s;
3481 }
3482
3483 ASSERT_NOT_REACHED();
3484 return String();
3485}
3486
3487void HTMLMediaElement::setPreload(const String& preload)
3488{
3489 INFO_LOG(LOGIDENTIFIER, preload);
3490#if ENABLE(MEDIA_STREAM)
3491 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3492 // "preload" - On getting: none. On setting: ignored.
3493 if (m_mediaStreamSrcObject)
3494 return;
3495#endif
3496
3497 setAttributeWithoutSynchronization(preloadAttr, preload);
3498}
3499
3500void HTMLMediaElement::play(DOMPromiseDeferred<void>&& promise)
3501{
3502 ALWAYS_LOG(LOGIDENTIFIER);
3503
3504 auto success = m_mediaSession->playbackPermitted();
3505 if (!success) {
3506 if (success.value() == MediaPlaybackDenialReason::UserGestureRequired)
3507 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
3508 promise.reject(NotAllowedError);
3509 return;
3510 }
3511
3512 if (m_error && m_error->code() == MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED) {
3513 promise.reject(NotSupportedError, "The operation is not supported.");
3514 return;
3515 }
3516
3517 if (processingUserGestureForMedia())
3518 removeBehaviorRestrictionsAfterFirstUserGesture();
3519
3520 m_pendingPlayPromises.append(WTFMove(promise));
3521 playInternal();
3522}
3523
3524void HTMLMediaElement::play()
3525{
3526 ALWAYS_LOG(LOGIDENTIFIER);
3527
3528 auto success = m_mediaSession->playbackPermitted();
3529 if (!success) {
3530 if (success.value() == MediaPlaybackDenialReason::UserGestureRequired)
3531 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
3532 return;
3533 }
3534 if (processingUserGestureForMedia())
3535 removeBehaviorRestrictionsAfterFirstUserGesture();
3536
3537 playInternal();
3538}
3539
3540void HTMLMediaElement::playInternal()
3541{
3542 ALWAYS_LOG(LOGIDENTIFIER);
3543
3544 if (isSuspended()) {
3545 ALWAYS_LOG(LOGIDENTIFIER, "returning because context is suspended");
3546 return;
3547 }
3548
3549 if (!document().hasBrowsingContext()) {
3550 INFO_LOG(LOGIDENTIFIER, "returning because there is no browsing context");
3551 return;
3552 }
3553
3554 if (!m_mediaSession->clientWillBeginPlayback()) {
3555 ALWAYS_LOG(LOGIDENTIFIER, "returning because of interruption");
3556 return;
3557 }
3558
3559 // 4.8.10.9. Playing the media resource
3560 if (!m_player || m_networkState == NETWORK_EMPTY)
3561 selectMediaResource();
3562
3563 if (endedPlayback())
3564 seekInternal(MediaTime::zeroTime());
3565
3566 if (m_mediaController)
3567 m_mediaController->bringElementUpToSpeed(*this);
3568
3569 if (m_paused) {
3570 m_paused = false;
3571 invalidateCachedTime();
3572
3573 // This avoids the first timeUpdated event after playback starts, when currentTime is still
3574 // the same as it was when the video was paused (and the time hasn't changed yet).
3575 m_lastTimeUpdateEventMovieTime = currentMediaTime();
3576 m_playbackStartedTime = m_lastTimeUpdateEventMovieTime.toDouble();
3577
3578 scheduleEvent(eventNames().playEvent);
3579
3580#if ENABLE(MEDIA_SESSION)
3581 // 6.3 Activating a media session from a media element
3582 // When the play() method is invoked, the paused attribute is true, and the readyState attribute has the value
3583 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, then
3584 // 1. Let media session be the value of the current media session.
3585 // 2. If we are not currently in media session's list of active participating media elements then append
3586 // ourselves to this list.
3587 // 3. Let activated be the result of running the media session invocation algorithm for media session.
3588 // 4. If activated is failure, pause ourselves.
3589 if (m_readyState == HAVE_ENOUGH_DATA || m_readyState == HAVE_FUTURE_DATA) {
3590 if (m_session) {
3591 m_session->addActiveMediaElement(*this);
3592
3593 if (m_session->kind() == MediaSessionKind::Content) {
3594 if (Page* page = document().page())
3595 page->chrome().client().focusedContentMediaElementDidChange(m_elementID);
3596 }
3597
3598 if (!m_session->invoke()) {
3599 pause();
3600 return;
3601 }
3602 }
3603 }
3604#endif
3605 if (m_readyState <= HAVE_CURRENT_DATA)
3606 scheduleEvent(eventNames().waitingEvent);
3607 } else if (m_readyState >= HAVE_FUTURE_DATA)
3608 scheduleResolvePendingPlayPromises();
3609
3610 if (processingUserGestureForMedia()) {
3611 if (m_autoplayEventPlaybackState == AutoplayEventPlaybackState::PreventedAutoplay) {
3612 handleAutoplayEvent(AutoplayEvent::DidPlayMediaWithUserGesture);
3613 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);
3614 } else
3615 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::StartedWithUserGesture);
3616 } else
3617 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::StartedWithoutUserGesture);
3618
3619 m_autoplaying = false;
3620 updatePlayState();
3621}
3622
3623void HTMLMediaElement::pause()
3624{
3625 ALWAYS_LOG(LOGIDENTIFIER);
3626
3627 m_temporarilyAllowingInlinePlaybackAfterFullscreen = false;
3628
3629 if (m_waitingToEnterFullscreen)
3630 m_waitingToEnterFullscreen = false;
3631
3632 if (!m_mediaSession->playbackPermitted())
3633 return;
3634
3635 if (processingUserGestureForMedia())
3636 removeBehaviorRestrictionsAfterFirstUserGesture(MediaElementSession::RequireUserGestureToControlControlsManager);
3637
3638 pauseInternal();
3639}
3640
3641
3642void HTMLMediaElement::pauseInternal()
3643{
3644 ALWAYS_LOG(LOGIDENTIFIER);
3645
3646 if (isSuspended()) {
3647 ALWAYS_LOG(LOGIDENTIFIER, "returning because context is suspended");
3648 return;
3649 }
3650
3651 if (!document().hasBrowsingContext()) {
3652 INFO_LOG(LOGIDENTIFIER, "returning because there is no browsing context");
3653 return;
3654 }
3655
3656 if (!m_mediaSession->clientWillPausePlayback()) {
3657 ALWAYS_LOG(LOGIDENTIFIER, "returning because of interruption");
3658 return;
3659 }
3660
3661 // 4.8.10.9. Playing the media resource
3662 if (!m_player || m_networkState == NETWORK_EMPTY) {
3663 // Unless the restriction on media requiring user action has been lifted
3664 // don't trigger loading if a script calls pause().
3665 if (!m_mediaSession->playbackPermitted())
3666 return;
3667 selectMediaResource();
3668 }
3669
3670 m_autoplaying = false;
3671
3672 if (processingUserGestureForMedia())
3673 userDidInterfereWithAutoplay();
3674
3675 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);
3676
3677 if (!m_paused) {
3678 m_paused = true;
3679 scheduleTimeupdateEvent(false);
3680 scheduleEvent(eventNames().pauseEvent);
3681 scheduleRejectPendingPlayPromises(DOMException::create(AbortError));
3682 if (MemoryPressureHandler::singleton().isUnderMemoryPressure())
3683 purgeBufferedDataIfPossible();
3684 }
3685
3686 updatePlayState();
3687}
3688
3689#if ENABLE(MEDIA_SOURCE)
3690
3691void HTMLMediaElement::detachMediaSource()
3692{
3693 if (!m_mediaSource)
3694 return;
3695
3696 m_mediaSource->detachFromElement(*this);
3697 m_mediaSource = nullptr;
3698}
3699
3700#endif
3701
3702bool HTMLMediaElement::loop() const
3703{
3704 return hasAttributeWithoutSynchronization(loopAttr);
3705}
3706
3707void HTMLMediaElement::setLoop(bool loop)
3708{
3709 INFO_LOG(LOGIDENTIFIER, loop);
3710 setBooleanAttribute(loopAttr, loop);
3711}
3712
3713bool HTMLMediaElement::controls() const
3714{
3715 RefPtr<Frame> frame = document().frame();
3716
3717 // always show controls when scripting is disabled
3718 if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
3719 return true;
3720
3721 return hasAttributeWithoutSynchronization(controlsAttr);
3722}
3723
3724void HTMLMediaElement::setControls(bool controls)
3725{
3726 INFO_LOG(LOGIDENTIFIER, controls);
3727 setBooleanAttribute(controlsAttr, controls);
3728}
3729
3730double HTMLMediaElement::volume() const
3731{
3732 return m_volume;
3733}
3734
3735ExceptionOr<void> HTMLMediaElement::setVolume(double volume)
3736{
3737 INFO_LOG(LOGIDENTIFIER, volume);
3738
3739 if (!(volume >= 0 && volume <= 1))
3740 return Exception { IndexSizeError };
3741
3742 if (m_volume == volume)
3743 return { };
3744
3745#if !PLATFORM(IOS_FAMILY)
3746 if (volume && processingUserGestureForMedia())
3747 removeBehaviorRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
3748
3749 m_volume = volume;
3750 m_volumeInitialized = true;
3751 updateVolume();
3752 scheduleEvent(eventNames().volumechangeEvent);
3753
3754 if (isPlaying() && !m_mediaSession->playbackPermitted()) {
3755 pauseInternal();
3756 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
3757 }
3758#else
3759 auto oldVolume = m_volume;
3760 m_volume = volume;
3761
3762 if (m_volumeRevertTaskQueue.hasPendingTask())
3763 return { };
3764
3765 m_volumeRevertTaskQueue.scheduleTask([this, oldVolume] {
3766 m_volume = oldVolume;
3767 });
3768
3769#endif
3770
3771 return { };
3772}
3773
3774bool HTMLMediaElement::muted() const
3775{
3776 return m_explicitlyMuted ? m_muted : hasAttributeWithoutSynchronization(mutedAttr);
3777}
3778
3779void HTMLMediaElement::setMuted(bool muted)
3780{
3781 INFO_LOG(LOGIDENTIFIER, muted);
3782
3783 bool mutedStateChanged = m_muted != muted;
3784 if (mutedStateChanged || !m_explicitlyMuted) {
3785 if (processingUserGestureForMedia()) {
3786 removeBehaviorRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
3787
3788 if (hasAudio() && muted)
3789 userDidInterfereWithAutoplay();
3790 }
3791
3792 m_muted = muted;
3793 m_explicitlyMuted = true;
3794
3795 // Avoid recursion when the player reports volume changes.
3796 if (!processingMediaPlayerCallback()) {
3797 if (m_player) {
3798 m_player->setMuted(effectiveMuted());
3799 if (hasMediaControls())
3800 mediaControls()->changedMute();
3801 }
3802 }
3803
3804 if (mutedStateChanged)
3805 scheduleEvent(eventNames().volumechangeEvent);
3806
3807 updateShouldPlay();
3808
3809#if ENABLE(MEDIA_SESSION)
3810 document().updateIsPlayingMedia(m_elementID);
3811#else
3812 document().updateIsPlayingMedia();
3813#endif
3814
3815#if ENABLE(WIRELESS_PLAYBACK_TARGET)
3816 scheduleUpdateMediaState();
3817#endif
3818 m_mediaSession->canProduceAudioChanged();
3819 }
3820
3821 schedulePlaybackControlsManagerUpdate();
3822}
3823
3824#if USE(AUDIO_SESSION) && PLATFORM(MAC)
3825void HTMLMediaElement::hardwareMutedStateDidChange(AudioSession* session)
3826{
3827 if (!session->isMuted())
3828 return;
3829
3830 if (!hasAudio())
3831 return;
3832
3833 if (effectiveMuted() || !volume())
3834 return;
3835
3836 INFO_LOG(LOGIDENTIFIER);
3837 userDidInterfereWithAutoplay();
3838}
3839#endif
3840
3841void HTMLMediaElement::togglePlayState()
3842{
3843 INFO_LOG(LOGIDENTIFIER, "canPlay() is ", canPlay());
3844
3845 // We can safely call the internal play/pause methods, which don't check restrictions, because
3846 // this method is only called from the built-in media controller
3847 if (canPlay()) {
3848 updatePlaybackRate();
3849 playInternal();
3850 } else
3851 pauseInternal();
3852}
3853
3854void HTMLMediaElement::beginScrubbing()
3855{
3856 INFO_LOG(LOGIDENTIFIER, "paused() is ", paused());
3857
3858 if (!paused()) {
3859 if (ended()) {
3860 // Because a media element stays in non-paused state when it reaches end, playback resumes
3861 // when the slider is dragged from the end to another position unless we pause first. Do
3862 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
3863 pause();
3864 } else {
3865 // Not at the end but we still want to pause playback so the media engine doesn't try to
3866 // continue playing during scrubbing. Pause without generating an event as we will
3867 // unpause after scrubbing finishes.
3868 setPausedInternal(true);
3869 }
3870 }
3871
3872 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
3873}
3874
3875void HTMLMediaElement::endScrubbing()
3876{
3877 INFO_LOG(LOGIDENTIFIER, "m_pausedInternal is", m_pausedInternal);
3878
3879 if (m_pausedInternal)
3880 setPausedInternal(false);
3881}
3882
3883void HTMLMediaElement::beginScanning(ScanDirection direction)
3884{
3885 m_scanType = supportsScanning() ? Scan : Seek;
3886 m_scanDirection = direction;
3887
3888 if (m_scanType == Seek) {
3889 // Scanning by seeking requires the video to be paused during scanning.
3890 m_actionAfterScan = paused() ? Nothing : Play;
3891 pause();
3892 } else {
3893 // Scanning by scanning requires the video to be playing during scanninging.
3894 m_actionAfterScan = paused() ? Pause : Nothing;
3895 play();
3896 setPlaybackRate(nextScanRate());
3897 }
3898
3899 m_scanTimer.start(0_s, m_scanType == Seek ? SeekRepeatDelay : ScanRepeatDelay);
3900}
3901
3902void HTMLMediaElement::endScanning()
3903{
3904 if (m_scanType == Scan)
3905 setPlaybackRate(defaultPlaybackRate());
3906
3907 if (m_actionAfterScan == Play)
3908 play();
3909 else if (m_actionAfterScan == Pause)
3910 pause();
3911
3912 if (m_scanTimer.isActive())
3913 m_scanTimer.stop();
3914}
3915
3916double HTMLMediaElement::nextScanRate()
3917{
3918 double rate = std::min(ScanMaximumRate, fabs(playbackRate() * 2));
3919 if (m_scanDirection == Backward)
3920 rate *= -1;
3921#if PLATFORM(IOS_FAMILY)
3922 rate = std::min(std::max(rate, minFastReverseRate()), maxFastForwardRate());
3923#endif
3924 return rate;
3925}
3926
3927void HTMLMediaElement::scanTimerFired()
3928{
3929 if (m_scanType == Seek) {
3930 double seekTime = m_scanDirection == Forward ? SeekTime : -SeekTime;
3931 setCurrentTime(currentTime() + seekTime);
3932 } else
3933 setPlaybackRate(nextScanRate());
3934}
3935
3936// The spec says to fire periodic timeupdate events (those sent while playing) every
3937// "15 to 250ms", we choose the slowest frequency
3938static const Seconds maxTimeupdateEventFrequency { 250_ms };
3939
3940void HTMLMediaElement::startPlaybackProgressTimer()
3941{
3942 if (m_playbackProgressTimer.isActive())
3943 return;
3944
3945 m_previousProgressTime = MonotonicTime::now();
3946 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
3947}
3948
3949void HTMLMediaElement::playbackProgressTimerFired()
3950{
3951 ASSERT(m_player);
3952
3953 if (m_fragmentEndTime.isValid() && currentMediaTime() >= m_fragmentEndTime && requestedPlaybackRate() > 0) {
3954 m_fragmentEndTime = MediaTime::invalidTime();
3955 if (!m_mediaController && !m_paused) {
3956 // changes paused to true and fires a simple event named pause at the media element.
3957 pauseInternal();
3958 }
3959 }
3960
3961 scheduleTimeupdateEvent(true);
3962
3963 if (!requestedPlaybackRate())
3964 return;
3965
3966 if (!m_paused && hasMediaControls())
3967 mediaControls()->playbackProgressed();
3968
3969#if ENABLE(VIDEO_TRACK)
3970 updateActiveTextTrackCues(currentMediaTime());
3971#endif
3972
3973#if ENABLE(MEDIA_SOURCE)
3974 if (m_mediaSource)
3975 m_mediaSource->monitorSourceBuffers();
3976#endif
3977
3978 bool playbackStarted = m_autoplayEventPlaybackState == AutoplayEventPlaybackState::StartedWithUserGesture || m_autoplayEventPlaybackState == AutoplayEventPlaybackState::StartedWithoutUserGesture;
3979 if (!seeking() && playbackStarted && currentTime() - playbackStartedTime() > AutoplayInterferenceTimeThreshold) {
3980 handleAutoplayEvent(m_autoplayEventPlaybackState == AutoplayEventPlaybackState::StartedWithoutUserGesture ? AutoplayEvent::DidAutoplayMediaPastThresholdWithoutUserInterference : AutoplayEvent::DidPlayMediaWithUserGesture);
3981 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);
3982 }
3983}
3984
3985void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
3986{
3987 MonotonicTime now = MonotonicTime::now();
3988 Seconds timedelta = now - m_clockTimeAtLastUpdateEvent;
3989
3990 // throttle the periodic events
3991 if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
3992 return;
3993
3994 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
3995 // event at a given time so filter here
3996 MediaTime movieTime = currentMediaTime();
3997 if (movieTime != m_lastTimeUpdateEventMovieTime) {
3998 scheduleEvent(eventNames().timeupdateEvent);
3999 m_clockTimeAtLastUpdateEvent = now;
4000 m_lastTimeUpdateEventMovieTime = movieTime;
4001 }
4002}
4003
4004bool HTMLMediaElement::canPlay() const
4005{
4006 return paused() || ended() || m_readyState < HAVE_METADATA;
4007}
4008
4009double HTMLMediaElement::percentLoaded() const
4010{
4011 if (!m_player)
4012 return 0;
4013 MediaTime duration = m_player->duration();
4014
4015 if (!duration || duration.isPositiveInfinite() || duration.isNegativeInfinite())
4016 return 0;
4017
4018 MediaTime buffered = MediaTime::zeroTime();
4019 bool ignored;
4020 std::unique_ptr<PlatformTimeRanges> timeRanges = m_player->buffered();
4021 for (unsigned i = 0; i < timeRanges->length(); ++i) {
4022 MediaTime start = timeRanges->start(i, ignored);
4023 MediaTime end = timeRanges->end(i, ignored);
4024 buffered += end - start;
4025 }
4026 return buffered.toDouble() / duration.toDouble();
4027}
4028
4029#if ENABLE(VIDEO_TRACK)
4030
4031void HTMLMediaElement::mediaPlayerDidAddAudioTrack(AudioTrackPrivate& track)
4032{
4033 if (isPlaying() && !m_mediaSession->playbackPermitted()) {
4034 pauseInternal();
4035 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
4036 }
4037
4038 addAudioTrack(AudioTrack::create(*this, track));
4039}
4040
4041void HTMLMediaElement::mediaPlayerDidAddTextTrack(InbandTextTrackPrivate& track)
4042{
4043 // 4.8.10.12.2 Sourcing in-band text tracks
4044 // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
4045 auto textTrack = InbandTextTrack::create(*ActiveDOMObject::scriptExecutionContext(), *this, track);
4046 textTrack->setMediaElement(this);
4047
4048 // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
4049 // as defined by the relevant specification. If there is no label in that data, then the label must
4050 // be set to the empty string.
4051 // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
4052 // for the format in question.
4053 // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
4054 // as follows, based on the type of the media resource:
4055 // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
4056 // cues, and begin updating it dynamically as necessary.
4057 // - Thess are all done by the media engine.
4058
4059 // 6. Set the new text track's readiness state to loaded.
4060 textTrack->setReadinessState(TextTrack::Loaded);
4061
4062 // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
4063 // the relevant specification for the data.
4064 // - This will happen in configureTextTracks()
4065 scheduleConfigureTextTracks();
4066
4067 // 8. Add the new text track to the media element's list of text tracks.
4068 // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
4069 // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
4070 // textTracks attribute's TextTrackList object.
4071 addTextTrack(WTFMove(textTrack));
4072}
4073
4074void HTMLMediaElement::mediaPlayerDidAddVideoTrack(VideoTrackPrivate& track)
4075{
4076 addVideoTrack(VideoTrack::create(*this, track));
4077}
4078
4079void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(AudioTrackPrivate& track)
4080{
4081 track.willBeRemoved();
4082}
4083
4084void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(InbandTextTrackPrivate& track)
4085{
4086 track.willBeRemoved();
4087}
4088
4089void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(VideoTrackPrivate& track)
4090{
4091 track.willBeRemoved();
4092}
4093
4094void HTMLMediaElement::closeCaptionTracksChanged()
4095{
4096 if (hasMediaControls())
4097 mediaControls()->closedCaptionTracksChanged();
4098}
4099
4100void HTMLMediaElement::addAudioTrack(Ref<AudioTrack>&& track)
4101{
4102 ensureAudioTracks().append(WTFMove(track));
4103}
4104
4105void HTMLMediaElement::addTextTrack(Ref<TextTrack>&& track)
4106{
4107 if (!m_requireCaptionPreferencesChangedCallbacks) {
4108 m_requireCaptionPreferencesChangedCallbacks = true;
4109 Document& document = this->document();
4110 document.registerForCaptionPreferencesChangedCallbacks(*this);
4111 if (Page* page = document.page())
4112 m_captionDisplayMode = page->group().captionPreferences().captionDisplayMode();
4113 }
4114
4115 ensureTextTracks().append(WTFMove(track));
4116
4117 closeCaptionTracksChanged();
4118}
4119
4120void HTMLMediaElement::addVideoTrack(Ref<VideoTrack>&& track)
4121{
4122 ensureVideoTracks().append(WTFMove(track));
4123}
4124
4125void HTMLMediaElement::removeAudioTrack(Ref<AudioTrack>&& track)
4126{
4127 track->clearClient();
4128 m_audioTracks->remove(track.get());
4129}
4130
4131void HTMLMediaElement::removeTextTrack(Ref<TextTrack>&& track, bool scheduleEvent)
4132{
4133 TrackDisplayUpdateScope scope { *this };
4134 if (auto cues = makeRefPtr(track->cues()))
4135 textTrackRemoveCues(track, *cues);
4136 track->clearClient();
4137 if (m_textTracks)
4138 m_textTracks->remove(track, scheduleEvent);
4139
4140 closeCaptionTracksChanged();
4141}
4142
4143void HTMLMediaElement::removeVideoTrack(Ref<VideoTrack>&& track)
4144{
4145 track->clearClient();
4146 m_videoTracks->remove(track);
4147}
4148
4149void HTMLMediaElement::forgetResourceSpecificTracks()
4150{
4151 while (m_audioTracks && m_audioTracks->length())
4152 removeAudioTrack(*m_audioTracks->lastItem());
4153
4154 if (m_textTracks) {
4155 TrackDisplayUpdateScope scope { *this };
4156 for (int i = m_textTracks->length() - 1; i >= 0; --i) {
4157 auto track = makeRef(*m_textTracks->item(i));
4158 if (track->trackType() == TextTrack::InBand)
4159 removeTextTrack(WTFMove(track), false);
4160 }
4161 }
4162
4163 while (m_videoTracks && m_videoTracks->length())
4164 removeVideoTrack(*m_videoTracks->lastItem());
4165}
4166
4167ExceptionOr<TextTrack&> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language)
4168{
4169 // 4.8.10.12.4 Text track API
4170 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
4171
4172 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
4173 if (!TextTrack::isValidKindKeyword(kind))
4174 return Exception { TypeError };
4175
4176 // 2. If the label argument was omitted, let label be the empty string.
4177 // 3. If the language argument was omitted, let language be the empty string.
4178 // 4. Create a new TextTrack object.
4179
4180 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
4181 // track label to label, its text track language to language...
4182 auto track = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, emptyString(), label, language);
4183 auto& trackReference = track.get();
4184
4185 // Note, due to side effects when changing track parameters, we have to
4186 // first append the track to the text track list.
4187
4188 // 6. Add the new text track to the media element's list of text tracks.
4189 addTextTrack(WTFMove(track));
4190
4191 // ... its text track readiness state to the text track loaded state ...
4192 trackReference.setReadinessState(TextTrack::Loaded);
4193
4194 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
4195 trackReference.setMode(TextTrack::Mode::Hidden);
4196
4197 return trackReference;
4198}
4199
4200AudioTrackList& HTMLMediaElement::ensureAudioTracks()
4201{
4202 if (!m_audioTracks)
4203 m_audioTracks = AudioTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
4204
4205 return *m_audioTracks;
4206}
4207
4208TextTrackList& HTMLMediaElement::ensureTextTracks()
4209{
4210 if (!m_textTracks)
4211 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
4212
4213 return *m_textTracks;
4214}
4215
4216VideoTrackList& HTMLMediaElement::ensureVideoTracks()
4217{
4218 if (!m_videoTracks)
4219 m_videoTracks = VideoTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
4220
4221 return *m_videoTracks;
4222}
4223
4224void HTMLMediaElement::didAddTextTrack(HTMLTrackElement& trackElement)
4225{
4226 ASSERT(trackElement.hasTagName(trackTag));
4227
4228 // 4.8.10.12.3 Sourcing out-of-band text tracks
4229 // When a track element's parent element changes and the new parent is a media element,
4230 // then the user agent must add the track element's corresponding text track to the
4231 // media element's list of text tracks ... [continues in TextTrackList::append]
4232 addTextTrack(trackElement.track());
4233
4234 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
4235 // in the markup have been added.
4236 if (!m_parsingInProgress)
4237 scheduleConfigureTextTracks();
4238
4239 if (hasMediaControls())
4240 mediaControls()->closedCaptionTracksChanged();
4241}
4242
4243void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement& trackElement)
4244{
4245 ASSERT(trackElement.hasTagName(trackTag));
4246
4247 auto& textTrack = trackElement.track();
4248
4249 textTrack.setHasBeenConfigured(false);
4250
4251 if (!m_textTracks)
4252 return;
4253
4254 // 4.8.10.12.3 Sourcing out-of-band text tracks
4255 // When a track element's parent element changes and the old parent was a media element,
4256 // then the user agent must remove the track element's corresponding text track from the
4257 // media element's list of text tracks.
4258 removeTextTrack(textTrack);
4259
4260 m_textTracksWhenResourceSelectionBegan.removeFirst(&textTrack);
4261}
4262
4263void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
4264{
4265 ASSERT(group.tracks.size());
4266
4267 Page* page = document().page();
4268 CaptionUserPreferences* captionPreferences = page ? &page->group().captionPreferences() : 0;
4269 CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences ? captionPreferences->captionDisplayMode() : CaptionUserPreferences::Automatic;
4270
4271 // First, find the track in the group that should be enabled (if any).
4272 Vector<RefPtr<TextTrack>> currentlyEnabledTracks;
4273 RefPtr<TextTrack> trackToEnable;
4274 RefPtr<TextTrack> defaultTrack;
4275 RefPtr<TextTrack> fallbackTrack;
4276 RefPtr<TextTrack> forcedSubitleTrack;
4277 int highestTrackScore = 0;
4278 int highestForcedScore = 0;
4279
4280 // If there is a visible track, it has already been configured so it won't be considered in the loop below. We don't want to choose another
4281 // track if it is less suitable, and we do want to disable it if another track is more suitable.
4282 int alreadyVisibleTrackScore = 0;
4283 if (group.visibleTrack && captionPreferences) {
4284 alreadyVisibleTrackScore = captionPreferences->textTrackSelectionScore(group.visibleTrack.get(), this);
4285 currentlyEnabledTracks.append(group.visibleTrack);
4286 }
4287
4288 for (size_t i = 0; i < group.tracks.size(); ++i) {
4289 RefPtr<TextTrack> textTrack = group.tracks[i];
4290
4291 if (m_processingPreferenceChange && textTrack->mode() == TextTrack::Mode::Showing)
4292 currentlyEnabledTracks.append(textTrack);
4293
4294 int trackScore = captionPreferences ? captionPreferences->textTrackSelectionScore(textTrack.get(), this) : 0;
4295 INFO_LOG(LOGIDENTIFIER, "'", textTrack->kindKeyword(), "' track with language '", textTrack->language(), "' and BCP 47 language '", textTrack->validBCP47Language(), "' has score ", trackScore);
4296
4297 if (trackScore) {
4298
4299 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
4300 // track with this text track kind, text track language, and text track label enabled, and there is no
4301 // other text track in the media element's list of text tracks with a text track kind of either subtitles
4302 // or captions whose text track mode is showing
4303 // ...
4304 // * If the text track kind is chapters and the text track language is one that the user agent has reason
4305 // to believe is appropriate for the user, and there is no other text track in the media element's list of
4306 // text tracks with a text track kind of chapters whose text track mode is showing
4307 // Let the text track mode be showing.
4308 if (trackScore > highestTrackScore && trackScore > alreadyVisibleTrackScore) {
4309 highestTrackScore = trackScore;
4310 trackToEnable = textTrack;
4311 }
4312
4313 if (!defaultTrack && textTrack->isDefault())
4314 defaultTrack = textTrack;
4315 if (!defaultTrack && !fallbackTrack)
4316 fallbackTrack = textTrack;
4317 if (textTrack->containsOnlyForcedSubtitles() && trackScore > highestForcedScore) {
4318 forcedSubitleTrack = textTrack;
4319 highestForcedScore = trackScore;
4320 }
4321 } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
4322 // * If the track element has a default attribute specified, and there is no other text track in the media
4323 // element's list of text tracks whose text track mode is showing or showing by default
4324 // Let the text track mode be showing by default.
4325 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly)
4326 defaultTrack = textTrack;
4327 }
4328 }
4329
4330 if (displayMode != CaptionUserPreferences::Manual) {
4331 if (!trackToEnable && defaultTrack)
4332 trackToEnable = defaultTrack;
4333
4334 // If no track matches the user's preferred language, none was marked as 'default', and there is a forced subtitle track
4335 // in the same language as the language of the primary audio track, enable it.
4336 if (!trackToEnable && forcedSubitleTrack)
4337 trackToEnable = forcedSubitleTrack;
4338
4339 // If no track matches, don't disable an already visible track unless preferences say they all should be off.
4340 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) {
4341 if (!trackToEnable && !defaultTrack && group.visibleTrack)
4342 trackToEnable = group.visibleTrack;
4343 }
4344
4345 // If no track matches the user's preferred language and non was marked 'default', enable the first track
4346 // because the user has explicitly stated a preference for this kind of track.
4347 if (!trackToEnable && fallbackTrack)
4348 trackToEnable = fallbackTrack;
4349
4350 if (trackToEnable)
4351 m_subtitleTrackLanguage = trackToEnable->language();
4352 else
4353 m_subtitleTrackLanguage = emptyString();
4354 }
4355
4356 if (currentlyEnabledTracks.size()) {
4357 for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
4358 RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
4359 if (textTrack != trackToEnable)
4360 textTrack->setMode(TextTrack::Mode::Disabled);
4361 }
4362 }
4363
4364 if (trackToEnable) {
4365 trackToEnable->setMode(TextTrack::Mode::Showing);
4366
4367 // If user preferences indicate we should always display captions, make sure we reflect the
4368 // proper status via the webkitClosedCaptionsVisible API call:
4369 if (!webkitClosedCaptionsVisible() && closedCaptionsVisible() && displayMode == CaptionUserPreferences::AlwaysOn)
4370 m_webkitLegacyClosedCaptionOverride = true;
4371 }
4372
4373 m_processingPreferenceChange = false;
4374}
4375
4376static JSC::JSValue controllerJSValue(JSC::ExecState& exec, JSDOMGlobalObject& globalObject, HTMLMediaElement& media)
4377{
4378 JSC::VM& vm = globalObject.vm();
4379 auto scope = DECLARE_THROW_SCOPE(vm);
4380 auto mediaJSWrapper = toJS(&exec, &globalObject, media);
4381
4382 // Retrieve the controller through the JS object graph
4383 JSC::JSObject* mediaJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, mediaJSWrapper);
4384 if (!mediaJSWrapperObject)
4385 return JSC::jsNull();
4386
4387 JSC::Identifier controlsHost = JSC::Identifier::fromString(&vm, "controlsHost");
4388 JSC::JSValue controlsHostJSWrapper = mediaJSWrapperObject->get(&exec, controlsHost);
4389 RETURN_IF_EXCEPTION(scope, JSC::jsNull());
4390
4391 JSC::JSObject* controlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, controlsHostJSWrapper);
4392 if (!controlsHostJSWrapperObject)
4393 return JSC::jsNull();
4394
4395 JSC::Identifier controllerID = JSC::Identifier::fromString(&vm, "controller");
4396 JSC::JSValue controllerJSWrapper = controlsHostJSWrapperObject->get(&exec, controllerID);
4397 RETURN_IF_EXCEPTION(scope, JSC::jsNull());
4398
4399 return controllerJSWrapper;
4400}
4401
4402void HTMLMediaElement::ensureMediaControlsShadowRoot()
4403{
4404 ASSERT(!m_creatingControls);
4405 m_creatingControls = true;
4406 ensureUserAgentShadowRoot();
4407 m_creatingControls = false;
4408}
4409
4410bool HTMLMediaElement::setupAndCallJS(const JSSetupFunction& task)
4411{
4412 Page* page = document().page();
4413 if (!page)
4414 return false;
4415
4416 auto pendingActivity = makePendingActivity(*this);
4417 auto& world = ensureIsolatedWorld();
4418 auto& scriptController = document().frame()->script();
4419 auto* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
4420 auto& vm = globalObject->vm();
4421 JSC::JSLockHolder lock(vm);
4422 auto scope = DECLARE_THROW_SCOPE(vm);
4423 auto* exec = globalObject->globalExec();
4424
4425 RETURN_IF_EXCEPTION(scope, false);
4426
4427 return task(*globalObject, *exec, scriptController, world);
4428}
4429
4430void HTMLMediaElement::updateCaptionContainer()
4431{
4432#if ENABLE(MEDIA_CONTROLS_SCRIPT)
4433 if (m_haveSetUpCaptionContainer)
4434 return;
4435
4436 if (!ensureMediaControlsInjectedScript())
4437 return;
4438
4439 ensureMediaControlsShadowRoot();
4440
4441 if (!m_mediaControlsHost)
4442 m_mediaControlsHost = MediaControlsHost::create(this);
4443
4444 setupAndCallJS([this](JSDOMGlobalObject& globalObject, JSC::ExecState& exec, ScriptController&, DOMWrapperWorld&) {
4445 auto& vm = globalObject.vm();
4446 auto scope = DECLARE_CATCH_SCOPE(vm);
4447 auto controllerValue = controllerJSValue(exec, globalObject, *this);
4448 auto* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, controllerValue);
4449 if (!controllerObject)
4450 return false;
4451
4452 // The media controls script must provide a method on the Controller object with the following details.
4453 // Name: updateCaptionContainer
4454 // Parameters:
4455 // None
4456 // Return value:
4457 // None
4458 auto methodValue = controllerObject->get(&exec, JSC::Identifier::fromString(&exec, "updateCaptionContainer"));
4459 auto* methodObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, methodValue);
4460 if (!methodObject)
4461 return false;
4462
4463 JSC::CallData callData;
4464 auto callType = methodObject->methodTable(vm)->getCallData(methodObject, callData);
4465 if (callType == JSC::CallType::None)
4466 return false;
4467
4468 JSC::MarkedArgumentBuffer noArguments;
4469 ASSERT(!noArguments.hasOverflowed());
4470 JSC::call(&exec, methodObject, callType, callData, controllerObject, noArguments);
4471 scope.clearException();
4472
4473 m_haveSetUpCaptionContainer = true;
4474
4475 return true;
4476 });
4477
4478#endif
4479}
4480
4481void HTMLMediaElement::layoutSizeChanged()
4482{
4483#if ENABLE(MEDIA_CONTROLS_SCRIPT)
4484 if (auto frameView = makeRefPtr(document().view())) {
4485 auto task = [this, protectedThis = makeRef(*this)] {
4486 if (auto root = userAgentShadowRoot())
4487 root->dispatchEvent(Event::create("resize", Event::CanBubble::No, Event::IsCancelable::No));
4488 };
4489 frameView->queuePostLayoutCallback(WTFMove(task));
4490 }
4491#endif
4492
4493 if (!m_receivedLayoutSizeChanged) {
4494 m_receivedLayoutSizeChanged = true;
4495 schedulePlaybackControlsManagerUpdate();
4496 }
4497
4498 // If the video is a candidate for main content, we should register it for viewport visibility callbacks
4499 // if it hasn't already been registered.
4500 if (renderer() && m_mediaSession && !m_mediaSession->wantsToObserveViewportVisibilityForAutoplay() && m_mediaSession->wantsToObserveViewportVisibilityForMediaControls())
4501 renderer()->registerForVisibleInViewportCallback();
4502}
4503
4504void HTMLMediaElement::visibilityDidChange()
4505{
4506 updateShouldAutoplay();
4507}
4508
4509void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect)
4510{
4511 auto* trackList = textTracks();
4512 if (!trackList || !trackList->length())
4513 return;
4514
4515 if (trackToSelect == TextTrack::captionMenuAutomaticItem()) {
4516 if (captionDisplayMode() != CaptionUserPreferences::Automatic)
4517 m_textTracks->scheduleChangeEvent();
4518 } else if (trackToSelect == TextTrack::captionMenuOffItem()) {
4519 for (int i = 0, length = trackList->length(); i < length; ++i)
4520 trackList->item(i)->setMode(TextTrack::Mode::Disabled);
4521
4522 if (captionDisplayMode() != CaptionUserPreferences::ForcedOnly && !trackList->isChangeEventScheduled())
4523 m_textTracks->scheduleChangeEvent();
4524 } else {
4525 if (!trackToSelect || !trackList->contains(*trackToSelect))
4526 return;
4527
4528 for (int i = 0, length = trackList->length(); i < length; ++i) {
4529 auto& track = *trackList->item(i);
4530 if (&track != trackToSelect)
4531 track.setMode(TextTrack::Mode::Disabled);
4532 else
4533 track.setMode(TextTrack::Mode::Showing);
4534 }
4535 }
4536
4537 if (!document().page())
4538 return;
4539
4540 auto& captionPreferences = document().page()->group().captionPreferences();
4541 CaptionUserPreferences::CaptionDisplayMode displayMode;
4542 if (trackToSelect == TextTrack::captionMenuOffItem())
4543 displayMode = CaptionUserPreferences::ForcedOnly;
4544 else if (trackToSelect == TextTrack::captionMenuAutomaticItem())
4545 displayMode = CaptionUserPreferences::Automatic;
4546 else {
4547 displayMode = CaptionUserPreferences::AlwaysOn;
4548 if (trackToSelect->validBCP47Language().length())
4549 captionPreferences.setPreferredLanguage(trackToSelect->validBCP47Language());
4550 }
4551
4552 captionPreferences.setCaptionDisplayMode(displayMode);
4553}
4554
4555void HTMLMediaElement::scheduleConfigureTextTracks()
4556{
4557 if (m_configureTextTracksTask.hasPendingTask())
4558 return;
4559
4560 auto logSiteIdentifier = LOGIDENTIFIER;
4561 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
4562 m_configureTextTracksTask.scheduleTask([this, logSiteIdentifier] {
4563 UNUSED_PARAM(logSiteIdentifier);
4564 ALWAYS_LOG(logSiteIdentifier, "lambda(), task fired");
4565 Ref<HTMLMediaElement> protectedThis(*this); // configureTextTracks calls methods that can trigger arbitrary DOM mutations.
4566 configureTextTracks();
4567 });
4568}
4569
4570void HTMLMediaElement::configureTextTracks()
4571{
4572 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
4573 TrackGroup descriptionTracks(TrackGroup::Description);
4574 TrackGroup chapterTracks(TrackGroup::Chapter);
4575 TrackGroup metadataTracks(TrackGroup::Metadata);
4576 TrackGroup otherTracks(TrackGroup::Other);
4577
4578 if (!m_textTracks)
4579 return;
4580
4581 for (size_t i = 0; i < m_textTracks->length(); ++i) {
4582 RefPtr<TextTrack> textTrack = m_textTracks->item(i);
4583 if (!textTrack)
4584 continue;
4585
4586 auto kind = textTrack->kind();
4587 TrackGroup* currentGroup;
4588 if (kind == TextTrack::Kind::Subtitles || kind == TextTrack::Kind::Captions || kind == TextTrack::Kind::Forced)
4589 currentGroup = &captionAndSubtitleTracks;
4590 else if (kind == TextTrack::Kind::Descriptions)
4591 currentGroup = &descriptionTracks;
4592 else if (kind == TextTrack::Kind::Chapters)
4593 currentGroup = &chapterTracks;
4594 else if (kind == TextTrack::Kind::Metadata)
4595 currentGroup = &metadataTracks;
4596 else
4597 currentGroup = &otherTracks;
4598
4599 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::Mode::Showing)
4600 currentGroup->visibleTrack = textTrack;
4601 if (!currentGroup->defaultTrack && textTrack->isDefault())
4602 currentGroup->defaultTrack = textTrack;
4603
4604 // Do not add this track to the group if it has already been automatically configured
4605 // as we only want to call configureTextTrack once per track so that adding another
4606 // track after the initial configuration doesn't reconfigure every track - only those
4607 // that should be changed by the new addition. For example all metadata tracks are
4608 // disabled by default, and we don't want a track that has been enabled by script
4609 // to be disabled automatically when a new metadata track is added later.
4610 if (textTrack->hasBeenConfigured())
4611 continue;
4612
4613 if (textTrack->language().length())
4614 currentGroup->hasSrcLang = true;
4615 currentGroup->tracks.append(textTrack);
4616 }
4617
4618 if (captionAndSubtitleTracks.tracks.size())
4619 configureTextTrackGroup(captionAndSubtitleTracks);
4620 if (descriptionTracks.tracks.size())
4621 configureTextTrackGroup(descriptionTracks);
4622 if (chapterTracks.tracks.size())
4623 configureTextTrackGroup(chapterTracks);
4624 if (metadataTracks.tracks.size())
4625 configureTextTrackGroup(metadataTracks);
4626 if (otherTracks.tracks.size())
4627 configureTextTrackGroup(otherTracks);
4628
4629 updateCaptionContainer();
4630 configureTextTrackDisplay();
4631 if (hasMediaControls())
4632 mediaControls()->closedCaptionTracksChanged();
4633}
4634#endif
4635
4636bool HTMLMediaElement::havePotentialSourceChild()
4637{
4638 // Stash the current <source> node and next nodes so we can restore them after checking
4639 // to see there is another potential.
4640 RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
4641 RefPtr<HTMLSourceElement> nextNode = m_nextChildNodeToConsider;
4642
4643 URL nextURL = selectNextSourceChild(0, 0, DoNothing);
4644
4645 m_currentSourceNode = currentSourceNode;
4646 m_nextChildNodeToConsider = nextNode;
4647
4648 return nextURL.isValid();
4649}
4650
4651URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
4652{
4653 UNUSED_PARAM(keySystem);
4654
4655 // Don't log if this was just called to find out if there are any valid <source> elements.
4656 bool shouldLog = willLog(WTFLogLevel::Debug) && actionIfInvalid != DoNothing;
4657 if (shouldLog)
4658 INFO_LOG(LOGIDENTIFIER);
4659
4660 if (!m_nextChildNodeToConsider) {
4661 if (shouldLog)
4662 INFO_LOG(LOGIDENTIFIER, "end of list, stopping");
4663 return URL();
4664 }
4665
4666 // Because the DOM may be mutated in the course of the following algorithm,
4667 // keep strong references to each of the child source nodes, and verify that
4668 // each still is a child of this media element before using.
4669 Vector<Ref<HTMLSourceElement>> potentialSourceNodes;
4670 auto sources = childrenOfType<HTMLSourceElement>(*this);
4671 for (auto next = m_nextChildNodeToConsider ? sources.beginAt(*m_nextChildNodeToConsider) : sources.begin(), end = sources.end(); next != end; ++next)
4672 potentialSourceNodes.append(*next);
4673
4674 for (auto& source : potentialSourceNodes) {
4675 if (source->parentNode() != this)
4676 continue;
4677
4678 // If candidate does not have a src attribute, or if its src attribute's value is the empty string ... jump down to the failed step below
4679 auto mediaURL = source->getNonEmptyURLAttribute(srcAttr);
4680 String type;
4681 if (shouldLog)
4682 INFO_LOG(LOGIDENTIFIER, "'src' is ", mediaURL);
4683 if (mediaURL.isEmpty())
4684 goto CheckAgain;
4685
4686 if (auto* media = source->parsedMediaAttribute(document())) {
4687 if (shouldLog)
4688 INFO_LOG(LOGIDENTIFIER, "'media' is ", source->attributeWithoutSynchronization(mediaAttr));
4689 auto* renderer = this->renderer();
4690 LOG(MediaQueries, "HTMLMediaElement %p selectNextSourceChild evaluating media queries", this);
4691 if (!MediaQueryEvaluator { "screen", document(), renderer ? &renderer->style() : nullptr }.evaluate(*media))
4692 goto CheckAgain;
4693 }
4694
4695 type = source->attributeWithoutSynchronization(typeAttr);
4696 if (type.isEmpty() && mediaURL.protocolIsData())
4697 type = mimeTypeFromDataURL(mediaURL);
4698 if (!type.isEmpty()) {
4699 if (shouldLog)
4700 INFO_LOG(LOGIDENTIFIER, "'type' is ", type);
4701 MediaEngineSupportParameters parameters;
4702 parameters.type = ContentType(type);
4703 parameters.url = mediaURL;
4704#if ENABLE(MEDIA_SOURCE)
4705 parameters.isMediaSource = mediaURL.protocolIs(mediaSourceBlobProtocol);
4706#endif
4707#if ENABLE(MEDIA_STREAM)
4708 parameters.isMediaStream = mediaURL.protocolIs(mediaStreamBlobProtocol);
4709#endif
4710 if (!document().settings().allowMediaContentTypesRequiringHardwareSupportAsFallback() || Traversal<HTMLSourceElement>::nextSkippingChildren(source))
4711 parameters.contentTypesRequiringHardwareSupport = mediaContentTypesRequiringHardwareSupport();
4712
4713 if (!MediaPlayer::supportsType(parameters))
4714 goto CheckAgain;
4715 }
4716
4717 // Is it safe to load this url?
4718 if (!isSafeToLoadURL(mediaURL, actionIfInvalid) || !dispatchBeforeLoadEvent(mediaURL.string()))
4719 goto CheckAgain;
4720
4721 // A 'beforeload' event handler can mutate the DOM, so check to see if the source element is still a child node.
4722 if (source->parentNode() != this) {
4723 INFO_LOG(LOGIDENTIFIER, "'beforeload' removed current element");
4724 continue;
4725 }
4726
4727 // Making it this far means the <source> looks reasonable.
4728 if (contentType)
4729 *contentType = ContentType(type);
4730 m_nextChildNodeToConsider = Traversal<HTMLSourceElement>::nextSkippingChildren(source);
4731 m_currentSourceNode = WTFMove(source);
4732
4733 if (shouldLog)
4734 INFO_LOG(LOGIDENTIFIER, " = ", mediaURL);
4735
4736 return mediaURL;
4737
4738CheckAgain:
4739 if (actionIfInvalid == Complain)
4740 source->scheduleErrorEvent();
4741 }
4742
4743 m_currentSourceNode = nullptr;
4744 m_nextChildNodeToConsider = nullptr;
4745
4746#if !LOG_DISABLED
4747 if (shouldLog)
4748 INFO_LOG(LOGIDENTIFIER, "failed");
4749#endif
4750 return URL();
4751}
4752
4753void HTMLMediaElement::sourceWasAdded(HTMLSourceElement& source)
4754{
4755 if (willLog(WTFLogLevel::Info) && source.hasTagName(sourceTag)) {
4756 URL url = source.getNonEmptyURLAttribute(srcAttr);
4757 INFO_LOG(LOGIDENTIFIER, "'src' is ", url);
4758 }
4759
4760 if (!document().hasBrowsingContext()) {
4761 INFO_LOG(LOGIDENTIFIER, "<source> inserted inside a document without a browsing context is not loaded");
4762 return;
4763 }
4764
4765 // We should only consider a <source> element when there is not src attribute at all.
4766 if (hasAttributeWithoutSynchronization(srcAttr))
4767 return;
4768
4769 // 4.8.8 - If a source element is inserted as a child of a media element that has no src
4770 // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
4771 // the media element's resource selection algorithm.
4772 if (m_networkState == NETWORK_EMPTY) {
4773 m_nextChildNodeToConsider = &source;
4774#if PLATFORM(IOS_FAMILY)
4775 if (m_mediaSession->dataLoadingPermitted())
4776#endif
4777 selectMediaResource();
4778 return;
4779 }
4780
4781 if (m_currentSourceNode && &source == Traversal<HTMLSourceElement>::nextSibling(*m_currentSourceNode)) {
4782 INFO_LOG(LOGIDENTIFIER, "<source> inserted immediately after current source");
4783 m_nextChildNodeToConsider = &source;
4784 return;
4785 }
4786
4787 if (m_nextChildNodeToConsider)
4788 return;
4789
4790 // 4.8.9.5, resource selection algorithm, source elements section:
4791 // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
4792 // 22. Asynchronously await a stable state...
4793 // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
4794 // it hasn't been fired yet).
4795 setShouldDelayLoadEvent(true);
4796
4797 // 24. Set the networkState back to NETWORK_LOADING.
4798 m_networkState = NETWORK_LOADING;
4799
4800 // 25. Jump back to the find next candidate step above.
4801 m_nextChildNodeToConsider = &source;
4802 scheduleNextSourceChild();
4803}
4804
4805void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement& source)
4806{
4807 if (willLog(WTFLogLevel::Info) && source.hasTagName(sourceTag)) {
4808 URL url = source.getNonEmptyURLAttribute(srcAttr);
4809 INFO_LOG(LOGIDENTIFIER, "'src' is ", url);
4810 }
4811
4812 if (&source != m_currentSourceNode && &source != m_nextChildNodeToConsider)
4813 return;
4814
4815 if (&source == m_nextChildNodeToConsider) {
4816 m_nextChildNodeToConsider = m_currentSourceNode ? Traversal<HTMLSourceElement>::nextSibling(*m_currentSourceNode) : nullptr;
4817 INFO_LOG(LOGIDENTIFIER);
4818 } else if (&source == m_currentSourceNode) {
4819 // Clear the current source node pointer, but don't change the movie as the spec says:
4820 // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
4821 // inserted in a video or audio element will have no effect.
4822 m_currentSourceNode = nullptr;
4823 INFO_LOG(LOGIDENTIFIER, "m_currentSourceNode cleared");
4824 }
4825}
4826
4827void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
4828{
4829 INFO_LOG(LOGIDENTIFIER);
4830
4831#if ENABLE(VIDEO_TRACK)
4832 updateActiveTextTrackCues(currentMediaTime());
4833#endif
4834
4835 beginProcessingMediaPlayerCallback();
4836
4837 invalidateCachedTime();
4838 bool wasSeeking = seeking();
4839
4840 // 4.8.10.9 step 14 & 15. Needed if no ReadyState change is associated with the seek.
4841 if (m_seekRequested && m_readyState >= HAVE_CURRENT_DATA && !m_player->seeking())
4842 finishSeek();
4843
4844 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
4845 // it will only queue a 'timeupdate' event if we haven't already posted one at the current
4846 // movie time.
4847 else
4848 scheduleTimeupdateEvent(false);
4849
4850 MediaTime now = currentMediaTime();
4851 MediaTime dur = durationMediaTime();
4852 double playbackRate = requestedPlaybackRate();
4853
4854 // When the current playback position reaches the end of the media resource then the user agent must follow these steps:
4855 if (dur && dur.isValid() && !dur.isPositiveInfinite() && !dur.isNegativeInfinite()) {
4856 // If the media element has a loop attribute specified and does not have a current media controller,
4857 if (loop() && !m_mediaController && playbackRate > 0) {
4858 m_sentEndEvent = false;
4859 // then seek to the earliest possible position of the media resource and abort these steps when the direction of
4860 // playback is forwards,
4861 if (now >= dur)
4862 seekInternal(MediaTime::zeroTime());
4863 } else if ((now <= MediaTime::zeroTime() && playbackRate < 0) || (now >= dur && playbackRate > 0)) {
4864 // If the media element does not have a current media controller, and the media element
4865 // has still ended playback and paused is false,
4866 if (!m_mediaController && !m_paused) {
4867 // changes paused to true and fires a simple event named pause at the media element.
4868 m_paused = true;
4869 scheduleEvent(eventNames().pauseEvent);
4870 m_mediaSession->clientWillPausePlayback();
4871 }
4872 // Queue a task to fire a simple event named ended at the media element.
4873 if (!m_sentEndEvent) {
4874 m_sentEndEvent = true;
4875 scheduleEvent(eventNames().endedEvent);
4876 if (!wasSeeking)
4877 addBehaviorRestrictionsOnEndIfNecessary();
4878 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);
4879 }
4880 setPlaying(false);
4881 // If the media element has a current media controller, then report the controller state
4882 // for the media element's current media controller.
4883 updateMediaController();
4884 } else
4885 m_sentEndEvent = false;
4886 } else {
4887#if ENABLE(MEDIA_STREAM)
4888 if (m_mediaStreamSrcObject) {
4889 // http://w3c.github.io/mediacapture-main/#event-mediastream-inactive
4890 // 6. MediaStreams in Media Elements
4891 // When the MediaStream state moves from the active to the inactive state, the User Agent
4892 // must raise an ended event on the HTMLMediaElement and set its ended attribute to true.
4893 // Note that once ended equals true the HTMLMediaElement will not play media even if new
4894 // MediaStreamTrack's are added to the MediaStream (causing it to return to the active
4895 // state) unless autoplay is true or the web application restarts the element, e.g.,
4896 // by calling play()
4897 if (!m_sentEndEvent && m_player && m_player->ended()) {
4898 m_sentEndEvent = true;
4899 scheduleEvent(eventNames().endedEvent);
4900 if (!wasSeeking)
4901 addBehaviorRestrictionsOnEndIfNecessary();
4902 m_paused = true;
4903 setPlaying(false);
4904 }
4905 } else
4906#endif
4907 m_sentEndEvent = false;
4908 }
4909
4910 scheduleUpdatePlayState();
4911 endProcessingMediaPlayerCallback();
4912}
4913
4914void HTMLMediaElement::addBehaviorRestrictionsOnEndIfNecessary()
4915{
4916 if (isFullscreen())
4917 return;
4918
4919 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
4920 m_playbackControlsManagerBehaviorRestrictionsTimer.stop();
4921 m_playbackControlsManagerBehaviorRestrictionsTimer.startOneShot(hideMediaControlsAfterEndedDelay);
4922}
4923
4924void HTMLMediaElement::handleSeekToPlaybackPosition(double position)
4925{
4926#if PLATFORM(MAC)
4927 // FIXME: This should ideally use faskSeek, but this causes MediaRemote's playhead to flicker upon release.
4928 // Please see <rdar://problem/28457219> for more details.
4929 seek(MediaTime::createWithDouble(position));
4930 m_seekToPlaybackPositionEndedTimer.stop();
4931 m_seekToPlaybackPositionEndedTimer.startOneShot(500_ms);
4932
4933 if (!m_isScrubbingRemotely) {
4934 m_isScrubbingRemotely = true;
4935 if (!paused())
4936 pauseInternal();
4937 }
4938#else
4939 fastSeek(position);
4940#endif
4941}
4942
4943void HTMLMediaElement::seekToPlaybackPositionEndedTimerFired()
4944{
4945#if PLATFORM(MAC)
4946 if (!m_isScrubbingRemotely)
4947 return;
4948
4949 PlatformMediaSessionManager::sharedManager().sessionDidEndRemoteScrubbing(*m_mediaSession);
4950 m_isScrubbingRemotely = false;
4951 m_seekToPlaybackPositionEndedTimer.stop();
4952#endif
4953}
4954
4955void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
4956{
4957 INFO_LOG(LOGIDENTIFIER);
4958
4959 beginProcessingMediaPlayerCallback();
4960 if (m_player) {
4961 double vol = m_player->volume();
4962 if (vol != m_volume) {
4963 m_volume = vol;
4964 updateVolume();
4965 scheduleEvent(eventNames().volumechangeEvent);
4966 }
4967 }
4968 endProcessingMediaPlayerCallback();
4969}
4970
4971void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*)
4972{
4973 INFO_LOG(LOGIDENTIFIER);
4974
4975 beginProcessingMediaPlayerCallback();
4976 if (m_player)
4977 setMuted(m_player->muted());
4978 endProcessingMediaPlayerCallback();
4979}
4980
4981void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer* player)
4982{
4983 INFO_LOG(LOGIDENTIFIER);
4984
4985 beginProcessingMediaPlayerCallback();
4986
4987 scheduleEvent(eventNames().durationchangeEvent);
4988 mediaPlayerCharacteristicChanged(player);
4989
4990 MediaTime now = currentMediaTime();
4991 MediaTime dur = durationMediaTime();
4992 if (now > dur)
4993 seekInternal(dur);
4994
4995 endProcessingMediaPlayerCallback();
4996}
4997
4998void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*)
4999{
5000 beginProcessingMediaPlayerCallback();
5001
5002 // Stash the rate in case the one we tried to set isn't what the engine is
5003 // using (eg. it can't handle the rate we set)
5004 m_reportedPlaybackRate = m_player->rate();
5005
5006 INFO_LOG(LOGIDENTIFIER, "rate: ", m_reportedPlaybackRate);
5007
5008 if (m_playing)
5009 invalidateCachedTime();
5010
5011 updateSleepDisabling();
5012
5013 endProcessingMediaPlayerCallback();
5014}
5015
5016void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*)
5017{
5018 INFO_LOG(LOGIDENTIFIER);
5019
5020 if (!m_player || m_pausedInternal)
5021 return;
5022
5023 beginProcessingMediaPlayerCallback();
5024 if (m_player->paused())
5025 pauseInternal();
5026 else
5027 playInternal();
5028
5029 updateSleepDisabling();
5030
5031 endProcessingMediaPlayerCallback();
5032}
5033
5034void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*)
5035{
5036 INFO_LOG(LOGIDENTIFIER);
5037
5038 // The MediaPlayer came across content it cannot completely handle.
5039 // This is normally acceptable except when we are in a standalone
5040 // MediaDocument. If so, tell the document what has happened.
5041 if (is<MediaDocument>(document()))
5042 downcast<MediaDocument>(document()).mediaElementSawUnsupportedTracks();
5043}
5044
5045void HTMLMediaElement::mediaPlayerResourceNotSupported(MediaPlayer*)
5046{
5047 INFO_LOG(LOGIDENTIFIER);
5048
5049 // The MediaPlayer came across content which no installed engine supports.
5050 mediaLoadingFailed(MediaPlayer::FormatError);
5051}
5052
5053// MediaPlayerPresentation methods
5054void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
5055{
5056 beginProcessingMediaPlayerCallback();
5057 updateDisplayState();
5058 if (auto* renderer = this->renderer())
5059 renderer->repaint();
5060 endProcessingMediaPlayerCallback();
5061}
5062
5063void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*)
5064{
5065 INFO_LOG(LOGIDENTIFIER);
5066
5067 if (is<MediaDocument>(document()) && m_player)
5068 downcast<MediaDocument>(document()).mediaElementNaturalSizeChanged(expandedIntSize(m_player->naturalSize()));
5069
5070 beginProcessingMediaPlayerCallback();
5071 if (m_readyState > HAVE_NOTHING)
5072 scheduleResizeEventIfSizeChanged();
5073 updateRenderer();
5074 endProcessingMediaPlayerCallback();
5075}
5076
5077bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*)
5078{
5079 auto* renderer = this->renderer();
5080 return is<RenderVideo>(renderer)
5081 && downcast<RenderVideo>(*renderer).view().compositor().canAccelerateVideoRendering(downcast<RenderVideo>(*renderer));
5082}
5083
5084void HTMLMediaElement::mediaPlayerRenderingModeChanged(MediaPlayer*)
5085{
5086 INFO_LOG(LOGIDENTIFIER);
5087
5088 // Kick off a fake recalcStyle that will update the compositing tree.
5089 invalidateStyleAndLayerComposition();
5090}
5091
5092bool HTMLMediaElement::mediaPlayerAcceleratedCompositingEnabled()
5093{
5094 return document().settings().acceleratedCompositingEnabled();
5095}
5096
5097#if PLATFORM(WIN) && USE(AVFOUNDATION)
5098
5099GraphicsDeviceAdapter* HTMLMediaElement::mediaPlayerGraphicsDeviceAdapter(const MediaPlayer*) const
5100{
5101 auto* page = document().page();
5102 if (!page)
5103 return nullptr;
5104 return page->chrome().client().graphicsDeviceAdapter();
5105}
5106
5107#endif
5108
5109void HTMLMediaElement::scheduleMediaEngineWasUpdated()
5110{
5111 if (m_mediaEngineUpdatedTask.hasPendingTask())
5112 return;
5113
5114 auto logSiteIdentifier = LOGIDENTIFIER;
5115 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
5116 m_mediaEngineUpdatedTask.scheduleTask([this, logSiteIdentifier] {
5117 UNUSED_PARAM(logSiteIdentifier);
5118 ALWAYS_LOG(logSiteIdentifier, "lambda(), task fired");
5119 Ref<HTMLMediaElement> protectedThis(*this); // mediaEngineWasUpdated calls methods that can trigger arbitrary DOM mutations.
5120 mediaEngineWasUpdated();
5121 });
5122}
5123
5124void HTMLMediaElement::mediaEngineWasUpdated()
5125{
5126 INFO_LOG(LOGIDENTIFIER);
5127 beginProcessingMediaPlayerCallback();
5128 updateRenderer();
5129 endProcessingMediaPlayerCallback();
5130
5131 m_mediaSession->mediaEngineUpdated();
5132
5133#if ENABLE(WEB_AUDIO)
5134 if (m_audioSourceNode && audioSourceProvider()) {
5135 m_audioSourceNode->lock();
5136 audioSourceProvider()->setClient(m_audioSourceNode);
5137 m_audioSourceNode->unlock();
5138 }
5139#endif
5140
5141#if ENABLE(ENCRYPTED_MEDIA)
5142 if (m_player && m_mediaKeys)
5143 m_player->cdmInstanceAttached(m_mediaKeys->cdmInstance());
5144#endif
5145
5146#if PLATFORM(IOS_FAMILY) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
5147 if (!m_player)
5148 return;
5149 m_player->setVideoFullscreenFrame(m_videoFullscreenFrame);
5150 m_player->setVideoFullscreenGravity(m_videoFullscreenGravity);
5151 m_player->setVideoFullscreenLayer(m_videoFullscreenLayer.get());
5152#endif
5153
5154#if ENABLE(WIRELESS_PLAYBACK_TARGET)
5155 scheduleUpdateMediaState();
5156#endif
5157}
5158
5159void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*)
5160{
5161 INFO_LOG(LOGIDENTIFIER);
5162
5163#if ENABLE(MEDIA_SOURCE)
5164 m_droppedVideoFrames = 0;
5165#endif
5166
5167 m_havePreparedToPlay = false;
5168
5169 scheduleMediaEngineWasUpdated();
5170}
5171
5172void HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(MediaPlayer*)
5173{
5174 INFO_LOG(LOGIDENTIFIER, "current display mode = ", (int)displayMode());
5175
5176 beginProcessingMediaPlayerCallback();
5177 if (displayMode() == PosterWaitingForVideo) {
5178 setDisplayMode(Video);
5179 mediaPlayerRenderingModeChanged(m_player.get());
5180 }
5181 endProcessingMediaPlayerCallback();
5182}
5183
5184void HTMLMediaElement::mediaPlayerCharacteristicChanged(MediaPlayer*)
5185{
5186 INFO_LOG(LOGIDENTIFIER);
5187
5188 beginProcessingMediaPlayerCallback();
5189
5190#if ENABLE(VIDEO_TRACK)
5191 if (captionDisplayMode() == CaptionUserPreferences::Automatic && m_subtitleTrackLanguage != m_player->languageOfPrimaryAudioTrack())
5192 markCaptionAndSubtitleTracksAsUnconfigured(AfterDelay);
5193#endif
5194
5195 if (potentiallyPlaying() && displayMode() == PosterWaitingForVideo) {
5196 setDisplayMode(Video);
5197 mediaPlayerRenderingModeChanged(m_player.get());
5198 }
5199
5200 if (hasMediaControls())
5201 mediaControls()->reset();
5202 updateRenderer();
5203
5204 if (!paused() && !m_mediaSession->playbackPermitted()) {
5205 pauseInternal();
5206 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
5207 }
5208
5209#if ENABLE(MEDIA_SESSION)
5210 document().updateIsPlayingMedia(m_elementID);
5211#else
5212 document().updateIsPlayingMedia();
5213#endif
5214
5215 m_hasEverHadAudio |= hasAudio();
5216 m_hasEverHadVideo |= hasVideo();
5217
5218 m_mediaSession->canProduceAudioChanged();
5219
5220 updateSleepDisabling();
5221
5222 endProcessingMediaPlayerCallback();
5223}
5224
5225Ref<TimeRanges> HTMLMediaElement::buffered() const
5226{
5227 if (!m_player)
5228 return TimeRanges::create();
5229
5230#if ENABLE(MEDIA_SOURCE)
5231 if (m_mediaSource)
5232 return TimeRanges::create(*m_mediaSource->buffered());
5233#endif
5234
5235 return TimeRanges::create(*m_player->buffered());
5236}
5237
5238double HTMLMediaElement::maxBufferedTime() const
5239{
5240 auto bufferedRanges = buffered();
5241 unsigned numRanges = bufferedRanges->length();
5242 if (!numRanges)
5243 return 0;
5244 return bufferedRanges.get().ranges().end(numRanges - 1).toDouble();
5245}
5246
5247Ref<TimeRanges> HTMLMediaElement::played()
5248{
5249 if (m_playing) {
5250 MediaTime time = currentMediaTime();
5251 if (time > m_lastSeekTime)
5252 addPlayedRange(m_lastSeekTime, time);
5253 }
5254
5255 if (!m_playedTimeRanges)
5256 m_playedTimeRanges = TimeRanges::create();
5257
5258 return m_playedTimeRanges->copy();
5259}
5260
5261Ref<TimeRanges> HTMLMediaElement::seekable() const
5262{
5263#if ENABLE(MEDIA_SOURCE)
5264 if (m_mediaSource)
5265 return m_mediaSource->seekable();
5266#endif
5267
5268 if (m_player)
5269 return TimeRanges::create(*m_player->seekable());
5270
5271 return TimeRanges::create();
5272}
5273
5274double HTMLMediaElement::seekableTimeRangesLastModifiedTime() const
5275{
5276 return m_player ? m_player->seekableTimeRangesLastModifiedTime() : 0;
5277}
5278
5279double HTMLMediaElement::liveUpdateInterval() const
5280{
5281 return m_player ? m_player->liveUpdateInterval() : 0;
5282}
5283
5284bool HTMLMediaElement::potentiallyPlaying() const
5285{
5286 if (isBlockedOnMediaController())
5287 return false;
5288
5289 if (!couldPlayIfEnoughData())
5290 return false;
5291
5292 if (m_readyState >= HAVE_FUTURE_DATA)
5293 return true;
5294
5295 return m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
5296}
5297
5298bool HTMLMediaElement::couldPlayIfEnoughData() const
5299{
5300 if (paused())
5301 return false;
5302
5303 if (endedPlayback())
5304 return false;
5305
5306 if (stoppedDueToErrors())
5307 return false;
5308
5309 if (pausedForUserInteraction())
5310 return false;
5311
5312 return true;
5313}
5314
5315bool HTMLMediaElement::endedPlayback() const
5316{
5317 MediaTime dur = durationMediaTime();
5318 if (!m_player || !dur.isValid())
5319 return false;
5320
5321 // 4.8.10.8 Playing the media resource
5322
5323 // A media element is said to have ended playback when the element's
5324 // readyState attribute is HAVE_METADATA or greater,
5325 if (m_readyState < HAVE_METADATA)
5326 return false;
5327
5328 // and the current playback position is the end of the media resource and the direction
5329 // of playback is forwards, Either the media element does not have a loop attribute specified,
5330 // or the media element has a current media controller.
5331 MediaTime now = currentMediaTime();
5332 if (requestedPlaybackRate() > 0)
5333 return dur > MediaTime::zeroTime() && now >= dur && (!loop() || m_mediaController);
5334
5335 // or the current playback position is the earliest possible position and the direction
5336 // of playback is backwards
5337 if (requestedPlaybackRate() < 0)
5338 return now <= MediaTime::zeroTime();
5339
5340 return false;
5341}
5342
5343bool HTMLMediaElement::stoppedDueToErrors() const
5344{
5345 if (m_readyState >= HAVE_METADATA && m_error) {
5346 RefPtr<TimeRanges> seekableRanges = seekable();
5347 if (!seekableRanges->contain(currentTime()))
5348 return true;
5349 }
5350
5351 return false;
5352}
5353
5354bool HTMLMediaElement::pausedForUserInteraction() const
5355{
5356 if (m_mediaSession->state() == PlatformMediaSession::Interrupted)
5357 return true;
5358
5359 return false;
5360}
5361
5362MediaTime HTMLMediaElement::minTimeSeekable() const
5363{
5364 return m_player ? m_player->minTimeSeekable() : MediaTime::zeroTime();
5365}
5366
5367MediaTime HTMLMediaElement::maxTimeSeekable() const
5368{
5369 return m_player ? m_player->maxTimeSeekable() : MediaTime::zeroTime();
5370}
5371
5372void HTMLMediaElement::updateVolume()
5373{
5374 if (!m_player)
5375 return;
5376#if PLATFORM(IOS_FAMILY)
5377 // Only the user can change audio volume so update the cached volume and post the changed event.
5378 float volume = m_player->volume();
5379 if (m_volume != volume) {
5380 m_volume = volume;
5381 scheduleEvent(eventNames().volumechangeEvent);
5382 }
5383#else
5384 // Avoid recursion when the player reports volume changes.
5385 if (!processingMediaPlayerCallback()) {
5386 Page* page = document().page();
5387 double volumeMultiplier = page ? page->mediaVolume() : 1;
5388 bool shouldMute = effectiveMuted();
5389
5390 if (m_mediaController) {
5391 volumeMultiplier *= m_mediaController->volume();
5392 shouldMute = m_mediaController->muted() || (page && page->isAudioMuted());
5393 }
5394
5395#if ENABLE(MEDIA_SESSION)
5396 if (m_shouldDuck)
5397 volumeMultiplier *= 0.25;
5398#endif
5399
5400 m_player->setMuted(shouldMute);
5401 m_player->setVolume(m_volume * volumeMultiplier);
5402 }
5403
5404#if ENABLE(MEDIA_SESSION)
5405 document().updateIsPlayingMedia(m_elementID);
5406#else
5407 document().updateIsPlayingMedia();
5408#endif
5409
5410 if (hasMediaControls())
5411 mediaControls()->changedVolume();
5412#endif
5413}
5414
5415void HTMLMediaElement::scheduleUpdatePlayState()
5416{
5417 if (m_updatePlayStateTask.hasPendingTask())
5418 return;
5419
5420 auto logSiteIdentifier = LOGIDENTIFIER;
5421 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
5422 m_updatePlayStateTask.scheduleTask([this, logSiteIdentifier] {
5423 UNUSED_PARAM(logSiteIdentifier);
5424 ALWAYS_LOG(logSiteIdentifier, "lambda(), task fired");
5425 Ref<HTMLMediaElement> protectedThis(*this); // updatePlayState calls methods that can trigger arbitrary DOM mutations.
5426 updatePlayState();
5427 });
5428}
5429
5430void HTMLMediaElement::updatePlayState()
5431{
5432 if (!m_player)
5433 return;
5434
5435 if (m_pausedInternal) {
5436 if (!m_player->paused())
5437 m_player->pause();
5438 refreshCachedTime();
5439 m_playbackProgressTimer.stop();
5440 if (hasMediaControls())
5441 mediaControls()->playbackStopped();
5442 return;
5443 }
5444
5445 bool shouldBePlaying = potentiallyPlaying();
5446 bool playerPaused = m_player->paused();
5447
5448 INFO_LOG(LOGIDENTIFIER, "shouldBePlaying = ", shouldBePlaying, ", playerPaused = ", playerPaused);
5449
5450 if (shouldBePlaying && playerPaused && m_mediaSession->requiresFullscreenForVideoPlayback() && (m_waitingToEnterFullscreen || !isFullscreen())) {
5451 if (!m_waitingToEnterFullscreen)
5452 enterFullscreen();
5453
5454#if PLATFORM(WATCHOS)
5455 // FIXME: Investigate doing this for all builds.
5456 return;
5457#endif
5458 }
5459
5460 if (shouldBePlaying) {
5461 schedulePlaybackControlsManagerUpdate();
5462
5463 setDisplayMode(Video);
5464 invalidateCachedTime();
5465
5466 if (playerPaused) {
5467 m_mediaSession->clientWillBeginPlayback();
5468
5469 // Set rate, muted before calling play in case they were set before the media engine was setup.
5470 // The media engine should just stash the rate and muted values since it isn't already playing.
5471 m_player->setRate(requestedPlaybackRate());
5472 m_player->setMuted(effectiveMuted());
5473
5474 if (m_firstTimePlaying) {
5475 // Log that a media element was played.
5476 if (auto* page = document().page())
5477 page->diagnosticLoggingClient().logDiagnosticMessage(isVideo() ? DiagnosticLoggingKeys::videoKey() : DiagnosticLoggingKeys::audioKey(), DiagnosticLoggingKeys::playedKey(), ShouldSample::No);
5478 m_firstTimePlaying = false;
5479 }
5480
5481 m_player->play();
5482 }
5483
5484 if (hasMediaControls())
5485 mediaControls()->playbackStarted();
5486
5487 startPlaybackProgressTimer();
5488 setPlaying(true);
5489 } else {
5490 schedulePlaybackControlsManagerUpdate();
5491
5492 if (!playerPaused)
5493 m_player->pause();
5494 refreshCachedTime();
5495
5496 m_playbackProgressTimer.stop();
5497 setPlaying(false);
5498 MediaTime time = currentMediaTime();
5499 if (time > m_lastSeekTime)
5500 addPlayedRange(m_lastSeekTime, time);
5501
5502 if (couldPlayIfEnoughData())
5503 prepareToPlay();
5504
5505 if (hasMediaControls())
5506 mediaControls()->playbackStopped();
5507 }
5508
5509 updateMediaController();
5510 updateRenderer();
5511
5512 m_hasEverHadAudio |= hasAudio();
5513 m_hasEverHadVideo |= hasVideo();
5514}
5515
5516void HTMLMediaElement::setPlaying(bool playing)
5517{
5518 if (playing && m_mediaSession)
5519 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
5520
5521 if (m_playing == playing)
5522 return;
5523
5524 m_playing = playing;
5525
5526 if (m_playing)
5527 scheduleNotifyAboutPlaying();
5528
5529#if ENABLE(MEDIA_SESSION)
5530 document().updateIsPlayingMedia(m_elementID);
5531#else
5532 document().updateIsPlayingMedia();
5533#endif
5534
5535#if ENABLE(WIRELESS_PLAYBACK_TARGET)
5536 scheduleUpdateMediaState();
5537#endif
5538}
5539
5540void HTMLMediaElement::setPausedInternal(bool b)
5541{
5542 m_pausedInternal = b;
5543 scheduleUpdatePlayState();
5544}
5545
5546void HTMLMediaElement::stopPeriodicTimers()
5547{
5548 m_progressEventTimer.stop();
5549 m_playbackProgressTimer.stop();
5550}
5551
5552void HTMLMediaElement::cancelPendingTasks()
5553{
5554 m_configureTextTracksTask.cancelTask();
5555 m_checkPlaybackTargetCompatablityTask.cancelTask();
5556 m_updateMediaStateTask.cancelTask();
5557 m_mediaEngineUpdatedTask.cancelTask();
5558 m_updatePlayStateTask.cancelTask();
5559#if PLATFORM(IOS_FAMILY)
5560 m_volumeRevertTaskQueue.cancelTask();
5561#endif
5562}
5563
5564void HTMLMediaElement::userCancelledLoad()
5565{
5566 INFO_LOG(LOGIDENTIFIER);
5567
5568 // FIXME: We should look to reconcile the iOS and non-iOS code (below).
5569#if PLATFORM(IOS_FAMILY)
5570 if (m_networkState == NETWORK_EMPTY || m_readyState >= HAVE_METADATA)
5571 return;
5572#else
5573 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
5574 return;
5575#endif
5576
5577 // If the media data fetching process is aborted by the user:
5578
5579 // 1 - The user agent should cancel the fetching process.
5580 clearMediaPlayer();
5581
5582 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
5583 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
5584
5585 // 3 - Queue a task to fire a simple event named error at the media element.
5586 scheduleEvent(eventNames().abortEvent);
5587
5588#if ENABLE(MEDIA_SOURCE)
5589 detachMediaSource();
5590#endif
5591
5592 // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
5593 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
5594 // simple event named emptied at the element. Otherwise, set the element's networkState
5595 // attribute to the NETWORK_IDLE value.
5596 if (m_readyState == HAVE_NOTHING) {
5597 m_networkState = NETWORK_EMPTY;
5598 scheduleEvent(eventNames().emptiedEvent);
5599 }
5600 else
5601 m_networkState = NETWORK_IDLE;
5602
5603 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
5604 setShouldDelayLoadEvent(false);
5605
5606 // 6 - Abort the overall resource selection algorithm.
5607 m_currentSourceNode = nullptr;
5608
5609 // Reset m_readyState since m_player is gone.
5610 m_readyState = HAVE_NOTHING;
5611 updateMediaController();
5612
5613#if ENABLE(VIDEO_TRACK)
5614 auto* context = scriptExecutionContext();
5615 if (!context || context->activeDOMObjectsAreStopped())
5616 return; // Document is about to be destructed. Avoid updating layout in updateActiveTextTrackCues.
5617
5618 updateActiveTextTrackCues(MediaTime::zeroTime());
5619#endif
5620}
5621
5622void HTMLMediaElement::clearMediaPlayer()
5623{
5624#if ENABLE(MEDIA_STREAM)
5625 if (!m_settingMediaStreamSrcObject)
5626 m_mediaStreamSrcObject = nullptr;
5627#endif
5628
5629#if ENABLE(MEDIA_SOURCE)
5630 detachMediaSource();
5631#endif
5632
5633 m_blob = nullptr;
5634
5635#if ENABLE(VIDEO_TRACK)
5636 forgetResourceSpecificTracks();
5637#endif
5638
5639#if ENABLE(WIRELESS_PLAYBACK_TARGET)
5640 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
5641 m_hasPlaybackTargetAvailabilityListeners = false;
5642 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(false);
5643
5644 // Send an availability event in case scripts want to hide the picker when the element
5645 // doesn't support playback to a target.
5646 enqueuePlaybackTargetAvailabilityChangedEvent();
5647 }
5648
5649 if (m_isPlayingToWirelessTarget)
5650 setIsPlayingToWirelessTarget(false);
5651#endif
5652
5653 if (m_isWaitingUntilMediaCanStart) {
5654 m_isWaitingUntilMediaCanStart = false;
5655 document().removeMediaCanStartListener(*this);
5656 }
5657
5658 if (m_player) {
5659 m_player->invalidate();
5660 m_player = nullptr;
5661 }
5662 schedulePlaybackControlsManagerUpdate();
5663
5664 stopPeriodicTimers();
5665 cancelPendingTasks();
5666
5667 m_loadState = WaitingForSource;
5668
5669#if ENABLE(VIDEO_TRACK)
5670 if (m_textTracks)
5671 configureTextTrackDisplay();
5672#endif
5673
5674 m_mediaSession->clientCharacteristicsChanged();
5675 m_mediaSession->canProduceAudioChanged();
5676
5677 m_resourceSelectionTaskQueue.cancelAllTasks();
5678
5679 updateSleepDisabling();
5680}
5681
5682bool HTMLMediaElement::canSuspendForDocumentSuspension() const
5683{
5684 return true;
5685}
5686
5687const char* HTMLMediaElement::activeDOMObjectName() const
5688{
5689 return "HTMLMediaElement";
5690}
5691
5692void HTMLMediaElement::stopWithoutDestroyingMediaPlayer()
5693{
5694 INFO_LOG(LOGIDENTIFIER);
5695
5696 if (m_videoFullscreenMode != VideoFullscreenModeNone)
5697 exitFullscreen();
5698
5699 setPreparedToReturnVideoLayerToInline(true);
5700
5701 schedulePlaybackControlsManagerUpdate();
5702 setInActiveDocument(false);
5703
5704 // Stop the playback without generating events
5705 setPlaying(false);
5706 setPausedInternal(true);
5707 m_mediaSession->stopSession();
5708
5709 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);
5710
5711 userCancelledLoad();
5712
5713 updateRenderer();
5714
5715 stopPeriodicTimers();
5716
5717 updateSleepDisabling();
5718}
5719
5720void HTMLMediaElement::closeTaskQueues()
5721{
5722 m_configureTextTracksTask.close();
5723 m_checkPlaybackTargetCompatablityTask.close();
5724 m_updateMediaStateTask.close();
5725 m_mediaEngineUpdatedTask.close();
5726 m_updatePlayStateTask.close();
5727 m_resumeTaskQueue.close();
5728 m_seekTaskQueue.close();
5729 m_playbackControlsManagerBehaviorRestrictionsQueue.close();
5730 m_seekTaskQueue.close();
5731 m_resumeTaskQueue.close();
5732 m_promiseTaskQueue.close();
5733 m_pauseAfterDetachedTaskQueue.close();
5734 m_resourceSelectionTaskQueue.close();
5735 m_visibilityChangeTaskQueue.close();
5736#if ENABLE(ENCRYPTED_MEDIA)
5737 m_encryptedMediaQueue.close();
5738#endif
5739 m_asyncEventQueue.close();
5740#if PLATFORM(IOS_FAMILY)
5741 m_volumeRevertTaskQueue.close();
5742#endif
5743}
5744
5745void HTMLMediaElement::contextDestroyed()
5746{
5747 closeTaskQueues();
5748 m_pendingPlayPromises.clear();
5749
5750 ActiveDOMObject::contextDestroyed();
5751}
5752
5753void HTMLMediaElement::stop()
5754{
5755 INFO_LOG(LOGIDENTIFIER);
5756
5757 Ref<HTMLMediaElement> protectedThis(*this);
5758 stopWithoutDestroyingMediaPlayer();
5759 closeTaskQueues();
5760
5761 // Once an active DOM object has been stopped it can not be restarted, so we can deallocate
5762 // the media player now. Note that userCancelledLoad will already called clearMediaPlayer
5763 // if the media was not fully loaded, but we need the same cleanup if the file was completely
5764 // loaded and calling it again won't cause any problems.
5765 clearMediaPlayer();
5766
5767 m_mediaSession->stopSession();
5768}
5769
5770void HTMLMediaElement::suspend(ReasonForSuspension reason)
5771{
5772 INFO_LOG(LOGIDENTIFIER);
5773 Ref<HTMLMediaElement> protectedThis(*this);
5774
5775 m_resumeTaskQueue.cancelTask();
5776
5777 switch (reason) {
5778 case ReasonForSuspension::PageCache:
5779 stopWithoutDestroyingMediaPlayer();
5780 m_asyncEventQueue.suspend();
5781 setBufferingPolicy(BufferingPolicy::MakeResourcesPurgeable);
5782 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToResumeMedia);
5783 break;
5784 case ReasonForSuspension::PageWillBeSuspended:
5785 case ReasonForSuspension::JavaScriptDebuggerPaused:
5786 case ReasonForSuspension::WillDeferLoading:
5787 // Do nothing, we don't pause media playback in these cases.
5788 break;
5789 }
5790}
5791
5792void HTMLMediaElement::resume()
5793{
5794 INFO_LOG(LOGIDENTIFIER);
5795
5796 setInActiveDocument(true);
5797
5798 m_asyncEventQueue.resume();
5799
5800 if (!m_mediaSession->pageAllowsPlaybackAfterResuming())
5801 document().addMediaCanStartListener(*this);
5802 else
5803 setPausedInternal(false);
5804
5805 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToResumeMedia);
5806 m_mediaSession->updateBufferingPolicy();
5807
5808 if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED && !m_resumeTaskQueue.hasPendingTask()) {
5809 // Restart the load if it was aborted in the middle by moving the document to the page cache.
5810 // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to
5811 // MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards).
5812 // This behavior is not specified but it seems like a sensible thing to do.
5813 // As it is not safe to immedately start loading now, let's schedule a load.
5814 m_resumeTaskQueue.scheduleTask(std::bind(&HTMLMediaElement::prepareForLoad, this));
5815 }
5816
5817 updateRenderer();
5818}
5819
5820bool HTMLMediaElement::hasPendingActivity() const
5821{
5822 return (hasAudio() && isPlaying()) || m_asyncEventQueue.hasPendingEvents() || m_creatingControls;
5823}
5824
5825void HTMLMediaElement::mediaVolumeDidChange()
5826{
5827 INFO_LOG(LOGIDENTIFIER);
5828 updateVolume();
5829}
5830
5831void HTMLMediaElement::visibilityStateChanged()
5832{
5833 bool elementIsHidden = document().hidden() && m_videoFullscreenMode != VideoFullscreenModePictureInPicture;
5834 if (elementIsHidden == m_elementIsHidden)
5835 return;
5836
5837 m_elementIsHidden = elementIsHidden;
5838 INFO_LOG(LOGIDENTIFIER, "visible = ", !m_elementIsHidden);
5839
5840 updateSleepDisabling();
5841 m_mediaSession->visibilityChanged();
5842 if (m_player)
5843 m_player->setVisible(!m_elementIsHidden);
5844
5845 bool isPlayingAudio = isPlaying() && hasAudio() && !muted() && volume();
5846 if (!isPlayingAudio) {
5847 if (m_elementIsHidden) {
5848 ALWAYS_LOG(LOGIDENTIFIER, "Suspending playback after going to the background");
5849 m_mediaSession->beginInterruption(PlatformMediaSession::EnteringBackground);
5850 } else {
5851 ALWAYS_LOG(LOGIDENTIFIER, "Resuming playback after entering foreground");
5852 m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying);
5853 }
5854 }
5855}
5856
5857#if ENABLE(VIDEO_TRACK)
5858bool HTMLMediaElement::requiresTextTrackRepresentation() const
5859{
5860 return (m_videoFullscreenMode != VideoFullscreenModeNone) && m_player ? m_player->requiresTextTrackRepresentation() : false;
5861}
5862
5863void HTMLMediaElement::setTextTrackRepresentation(TextTrackRepresentation* representation)
5864{
5865 if (m_player)
5866 m_player->setTextTrackRepresentation(representation);
5867}
5868
5869void HTMLMediaElement::syncTextTrackBounds()
5870{
5871 if (m_player)
5872 m_player->syncTextTrackBounds();
5873}
5874#endif // ENABLE(VIDEO_TRACK)
5875
5876#if ENABLE(WIRELESS_PLAYBACK_TARGET)
5877void HTMLMediaElement::webkitShowPlaybackTargetPicker()
5878{
5879 ALWAYS_LOG(LOGIDENTIFIER);
5880 if (processingUserGestureForMedia())
5881 removeBehaviorRestrictionsAfterFirstUserGesture();
5882 m_mediaSession->showPlaybackTargetPicker();
5883}
5884
5885void HTMLMediaElement::wirelessRoutesAvailableDidChange()
5886{
5887 enqueuePlaybackTargetAvailabilityChangedEvent();
5888}
5889
5890void HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(MediaPlayer*)
5891{
5892 setIsPlayingToWirelessTarget(m_player && m_player->isCurrentPlaybackTargetWireless());
5893}
5894
5895void HTMLMediaElement::setIsPlayingToWirelessTarget(bool isPlayingToWirelessTarget)
5896{
5897 m_playbackTargetIsWirelessQueue.enqueueTask([this, isPlayingToWirelessTarget] {
5898 if (isPlayingToWirelessTarget == m_isPlayingToWirelessTarget)
5899 return;
5900 m_isPlayingToWirelessTarget = m_player && m_player->isCurrentPlaybackTargetWireless();
5901
5902 ALWAYS_LOG(LOGIDENTIFIER, m_isPlayingToWirelessTarget);
5903 configureMediaControls();
5904 m_mediaSession->isPlayingToWirelessPlaybackTargetChanged(m_isPlayingToWirelessTarget);
5905 m_mediaSession->canProduceAudioChanged();
5906 scheduleUpdateMediaState();
5907 updateSleepDisabling();
5908
5909 m_failedToPlayToWirelessTarget = false;
5910 scheduleCheckPlaybackTargetCompatability();
5911
5912 dispatchEvent(Event::create(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent, Event::CanBubble::No, Event::IsCancelable::Yes));
5913 });
5914}
5915
5916void HTMLMediaElement::dispatchEvent(Event& event)
5917{
5918 DEBUG_LOG(LOGIDENTIFIER, event.type());
5919
5920 if (m_removedBehaviorRestrictionsAfterFirstUserGesture && event.type() == eventNames().endedEvent)
5921 document().userActivatedMediaFinishedPlaying();
5922
5923 HTMLElement::dispatchEvent(event);
5924}
5925
5926bool HTMLMediaElement::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
5927{
5928 if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
5929 return Node::addEventListener(eventType, WTFMove(listener), options);
5930
5931 bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
5932 if (!Node::addEventListener(eventType, WTFMove(listener), options))
5933 return false;
5934
5935 if (isFirstAvailabilityChangedListener) {
5936 m_hasPlaybackTargetAvailabilityListeners = true;
5937 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(true);
5938 }
5939
5940 INFO_LOG(LOGIDENTIFIER, "'webkitplaybacktargetavailabilitychanged'");
5941
5942 enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event.
5943 return true;
5944}
5945
5946bool HTMLMediaElement::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options)
5947{
5948 if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
5949 return Node::removeEventListener(eventType, listener, options);
5950
5951 if (!Node::removeEventListener(eventType, listener, options))
5952 return false;
5953
5954 bool didRemoveLastAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
5955 INFO_LOG(LOGIDENTIFIER, "removed last listener = ", didRemoveLastAvailabilityChangedListener);
5956 if (didRemoveLastAvailabilityChangedListener) {
5957 m_hasPlaybackTargetAvailabilityListeners = false;
5958 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(false);
5959 scheduleUpdateMediaState();
5960 }
5961
5962 return true;
5963}
5964
5965void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent()
5966{
5967 bool hasTargets = m_mediaSession->hasWirelessPlaybackTargets();
5968 INFO_LOG(LOGIDENTIFIER, "hasTargets = ", hasTargets);
5969 auto event = WebKitPlaybackTargetAvailabilityEvent::create(eventNames().webkitplaybacktargetavailabilitychangedEvent, hasTargets);
5970 event->setTarget(this);
5971 m_asyncEventQueue.enqueueEvent(WTFMove(event));
5972 scheduleUpdateMediaState();
5973}
5974
5975void HTMLMediaElement::setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
5976{
5977 ALWAYS_LOG(LOGIDENTIFIER);
5978 if (m_player)
5979 m_player->setWirelessPlaybackTarget(WTFMove(device));
5980}
5981
5982void HTMLMediaElement::setShouldPlayToPlaybackTarget(bool shouldPlay)
5983{
5984 ALWAYS_LOG(LOGIDENTIFIER, shouldPlay);
5985
5986 if (m_player)
5987 m_player->setShouldPlayToPlaybackTarget(shouldPlay);
5988}
5989
5990#endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
5991
5992bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const
5993{
5994 INFO_LOG(LOGIDENTIFIER, m_isPlayingToWirelessTarget);
5995 return m_isPlayingToWirelessTarget;
5996}
5997
5998void HTMLMediaElement::setPlayingOnSecondScreen(bool value)
5999{
6000 if (value == m_playingOnSecondScreen)
6001 return;
6002
6003 m_playingOnSecondScreen = value;
6004
6005#if ENABLE(WIRELESS_PLAYBACK_TARGET)
6006 scheduleUpdateMediaState();
6007#endif
6008}
6009
6010double HTMLMediaElement::minFastReverseRate() const
6011{
6012 return m_player ? m_player->minFastReverseRate() : 0;
6013}
6014
6015double HTMLMediaElement::maxFastForwardRate() const
6016{
6017 return m_player ? m_player->maxFastForwardRate() : 0;
6018}
6019
6020bool HTMLMediaElement::isFullscreen() const
6021{
6022 if (m_videoFullscreenMode != VideoFullscreenModeNone)
6023 return true;
6024
6025#if ENABLE(FULLSCREEN_API)
6026 if (document().fullscreenManager().isFullscreen() && document().fullscreenManager().currentFullscreenElement() == this)
6027 return true;
6028#endif
6029
6030 return false;
6031}
6032
6033bool HTMLMediaElement::isStandardFullscreen() const
6034{
6035#if ENABLE(FULLSCREEN_API)
6036 if (document().fullscreenManager().isFullscreen() && document().fullscreenManager().currentFullscreenElement() == this)
6037 return true;
6038#endif
6039
6040 return m_videoFullscreenMode == VideoFullscreenModeStandard;
6041}
6042
6043void HTMLMediaElement::toggleStandardFullscreenState()
6044{
6045 if (isStandardFullscreen())
6046 exitFullscreen();
6047 else
6048 enterFullscreen();
6049}
6050
6051void HTMLMediaElement::enterFullscreen(VideoFullscreenMode mode)
6052{
6053 INFO_LOG(LOGIDENTIFIER);
6054 ASSERT(mode != VideoFullscreenModeNone);
6055
6056 if (m_videoFullscreenMode == mode)
6057 return;
6058
6059 m_temporarilyAllowingInlinePlaybackAfterFullscreen = false;
6060 m_waitingToEnterFullscreen = true;
6061
6062#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO_USES_ELEMENT_FULLSCREEN)
6063 if (document().settings().fullScreenEnabled() && mode == VideoFullscreenModeStandard) {
6064 document().fullscreenManager().requestFullscreenForElement(this, FullscreenManager::ExemptIFrameAllowFullscreenRequirement);
6065 return;
6066 }
6067#endif
6068
6069 m_fullscreenTaskQueue.enqueueTask([this, mode] {
6070 if (document().hidden()) {
6071 ALWAYS_LOG(LOGIDENTIFIER, " returning because document is hidden");
6072 return;
6073 }
6074
6075 fullscreenModeChanged(mode);
6076 configureMediaControls();
6077 if (hasMediaControls())
6078 mediaControls()->enteredFullscreen();
6079 if (is<HTMLVideoElement>(*this)) {
6080 HTMLVideoElement& asVideo = downcast<HTMLVideoElement>(*this);
6081 if (document().page()->chrome().client().supportsVideoFullscreen(m_videoFullscreenMode)) {
6082 document().page()->chrome().client().enterVideoFullscreenForVideoElement(asVideo, m_videoFullscreenMode, m_videoFullscreenStandby);
6083 scheduleEvent(eventNames().webkitbeginfullscreenEvent);
6084 }
6085 }
6086 });
6087}
6088
6089void HTMLMediaElement::enterFullscreen()
6090{
6091 enterFullscreen(VideoFullscreenModeStandard);
6092}
6093
6094void HTMLMediaElement::exitFullscreen()
6095{
6096 INFO_LOG(LOGIDENTIFIER);
6097
6098 m_waitingToEnterFullscreen = false;
6099
6100#if ENABLE(FULLSCREEN_API)
6101 if (document().settings().fullScreenEnabled() && document().fullscreenManager().currentFullscreenElement() == this) {
6102 if (document().fullscreenManager().isFullscreen())
6103 document().fullscreenManager().cancelFullscreen();
6104
6105 if (m_videoFullscreenMode == VideoFullscreenModeStandard)
6106 return;
6107 }
6108#endif
6109
6110 ASSERT(m_videoFullscreenMode != VideoFullscreenModeNone);
6111 VideoFullscreenMode oldVideoFullscreenMode = m_videoFullscreenMode;
6112 fullscreenModeChanged(VideoFullscreenModeNone);
6113#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6114 Ref<HTMLMediaElement> protectedThis(*this); // updateMediaControlsAfterPresentationModeChange calls methods that can trigger arbitrary DOM mutations.
6115 updateMediaControlsAfterPresentationModeChange();
6116#endif
6117 if (hasMediaControls())
6118 mediaControls()->exitedFullscreen();
6119
6120 if (!document().page() || !is<HTMLVideoElement>(*this))
6121 return;
6122
6123 if (!paused() && m_mediaSession->requiresFullscreenForVideoPlayback()) {
6124 if (!document().settings().allowsInlineMediaPlaybackAfterFullscreen() || isVideoTooSmallForInlinePlayback())
6125 pauseInternal();
6126 else {
6127 // Allow inline playback, but set a flag so pausing and starting again (e.g. when scrubbing or looping) won't go back to fullscreen.
6128 // Also set the controls attribute so the user will be able to control playback.
6129 m_temporarilyAllowingInlinePlaybackAfterFullscreen = true;
6130 setControls(true);
6131 }
6132 }
6133
6134#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
6135 if (document().activeDOMObjectsAreSuspended() || document().activeDOMObjectsAreStopped())
6136 document().page()->chrome().client().exitVideoFullscreenToModeWithoutAnimation(downcast<HTMLVideoElement>(*this), VideoFullscreenModeNone);
6137 else
6138#endif
6139 if (document().page()->chrome().client().supportsVideoFullscreen(oldVideoFullscreenMode)) {
6140 if (m_videoFullscreenStandby)
6141 document().page()->chrome().client().enterVideoFullscreenForVideoElement(downcast<HTMLVideoElement>(*this), m_videoFullscreenMode, m_videoFullscreenStandby);
6142 else
6143 document().page()->chrome().client().exitVideoFullscreenForVideoElement(downcast<HTMLVideoElement>(*this));
6144 scheduleEvent(eventNames().webkitendfullscreenEvent);
6145 scheduleEvent(eventNames().webkitpresentationmodechangedEvent);
6146 }
6147}
6148
6149WEBCORE_EXPORT void HTMLMediaElement::setVideoFullscreenStandby(bool value)
6150{
6151 ASSERT(is<HTMLVideoElement>(*this));
6152 if (m_videoFullscreenStandby == value)
6153 return;
6154
6155 if (!document().page())
6156 return;
6157
6158 if (!document().page()->chrome().client().supportsVideoFullscreenStandby())
6159 return;
6160
6161 m_videoFullscreenStandby = value;
6162
6163#if PLATFORM(IOS_FAMILY) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
6164 if (m_player)
6165 m_player->videoFullscreenStandbyChanged();
6166#endif
6167
6168 if (m_videoFullscreenStandby || m_videoFullscreenMode != VideoFullscreenModeNone)
6169 document().page()->chrome().client().enterVideoFullscreenForVideoElement(downcast<HTMLVideoElement>(*this), m_videoFullscreenMode, m_videoFullscreenStandby);
6170 else
6171 document().page()->chrome().client().exitVideoFullscreenForVideoElement(downcast<HTMLVideoElement>(*this));
6172}
6173
6174void HTMLMediaElement::willBecomeFullscreenElement()
6175{
6176#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
6177 HTMLMediaElementEnums::VideoFullscreenMode oldVideoFullscreenMode = m_videoFullscreenMode;
6178#endif
6179
6180 fullscreenModeChanged(VideoFullscreenModeStandard);
6181
6182#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
6183 switch (oldVideoFullscreenMode) {
6184 case VideoFullscreenModeNone:
6185 case VideoFullscreenModeStandard:
6186 // Don't need to do anything if we are not in any special fullscreen mode or it's already
6187 // in standard fullscreen mode.
6188 break;
6189 case VideoFullscreenModePictureInPicture:
6190 if (is<HTMLVideoElement>(*this))
6191 downcast<HTMLVideoElement>(this)->exitToFullscreenModeWithoutAnimationIfPossible(oldVideoFullscreenMode, VideoFullscreenModeStandard);
6192 break;
6193 }
6194#endif
6195
6196 Element::willBecomeFullscreenElement();
6197}
6198
6199void HTMLMediaElement::didBecomeFullscreenElement()
6200{
6201 m_waitingToEnterFullscreen = false;
6202 if (hasMediaControls())
6203 mediaControls()->enteredFullscreen();
6204 scheduleUpdatePlayState();
6205}
6206
6207void HTMLMediaElement::willStopBeingFullscreenElement()
6208{
6209 if (hasMediaControls())
6210 mediaControls()->exitedFullscreen();
6211
6212 if (fullscreenMode() == VideoFullscreenModeStandard)
6213 fullscreenModeChanged(VideoFullscreenModeNone);
6214}
6215
6216PlatformLayer* HTMLMediaElement::platformLayer() const
6217{
6218 return m_player ? m_player->platformLayer() : nullptr;
6219}
6220
6221void HTMLMediaElement::setPreparedToReturnVideoLayerToInline(bool value)
6222{
6223 m_preparedForInline = value;
6224 if (m_preparedForInline && m_preparedForInlineCompletionHandler) {
6225 m_preparedForInlineCompletionHandler();
6226 m_preparedForInlineCompletionHandler = nullptr;
6227 }
6228}
6229
6230void HTMLMediaElement::waitForPreparedForInlineThen(WTF::Function<void()>&& completionHandler)
6231{
6232 ASSERT(!m_preparedForInlineCompletionHandler);
6233 if (m_preparedForInline) {
6234 completionHandler();
6235 return;
6236 }
6237
6238 m_preparedForInlineCompletionHandler = WTFMove(completionHandler);
6239}
6240
6241#if PLATFORM(IOS_FAMILY) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
6242
6243void HTMLMediaElement::willExitFullscreen()
6244{
6245 if (m_player)
6246 m_player->updateVideoFullscreenInlineImage();
6247}
6248
6249bool HTMLMediaElement::isVideoLayerInline()
6250{
6251 return !m_videoFullscreenLayer;
6252};
6253
6254void HTMLMediaElement::setVideoFullscreenLayer(PlatformLayer* platformLayer, WTF::Function<void()>&& completionHandler)
6255{
6256 m_videoFullscreenLayer = platformLayer;
6257 if (!m_player) {
6258 completionHandler();
6259 return;
6260 }
6261
6262 m_player->setVideoFullscreenLayer(platformLayer, WTFMove(completionHandler));
6263 invalidateStyleAndLayerComposition();
6264#if ENABLE(VIDEO_TRACK)
6265 updateTextTrackDisplay();
6266#endif
6267}
6268
6269void HTMLMediaElement::setVideoFullscreenFrame(FloatRect frame)
6270{
6271 m_videoFullscreenFrame = frame;
6272 if (m_player)
6273 m_player->setVideoFullscreenFrame(frame);
6274}
6275
6276void HTMLMediaElement::setVideoFullscreenGravity(MediaPlayer::VideoGravity gravity)
6277{
6278 m_videoFullscreenGravity = gravity;
6279 if (m_player)
6280 m_player->setVideoFullscreenGravity(gravity);
6281}
6282
6283#else
6284
6285bool HTMLMediaElement::isVideoLayerInline()
6286{
6287 return true;
6288};
6289
6290#endif
6291
6292bool HTMLMediaElement::hasClosedCaptions() const
6293{
6294 if (m_player && m_player->hasClosedCaptions())
6295 return true;
6296
6297#if ENABLE(VIDEO_TRACK)
6298 if (!m_textTracks)
6299 return false;
6300
6301 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
6302 auto& track = *m_textTracks->item(i);
6303 if (track.readinessState() == TextTrack::FailedToLoad)
6304 continue;
6305 if (track.kind() == TextTrack::Kind::Captions || track.kind() == TextTrack::Kind::Subtitles)
6306 return true;
6307 }
6308#endif
6309
6310 return false;
6311}
6312
6313bool HTMLMediaElement::closedCaptionsVisible() const
6314{
6315 return m_closedCaptionsVisible;
6316}
6317
6318#if ENABLE(VIDEO_TRACK)
6319
6320void HTMLMediaElement::updateTextTrackDisplay()
6321{
6322#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6323 ensureMediaControlsShadowRoot();
6324 if (!m_mediaControlsHost)
6325 m_mediaControlsHost = MediaControlsHost::create(this);
6326 m_mediaControlsHost->updateTextTrackContainer();
6327#else
6328 if (!hasMediaControls() && !createMediaControls())
6329 return;
6330
6331 mediaControls()->updateTextTrackDisplay();
6332#endif
6333}
6334
6335#endif
6336
6337void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
6338{
6339 INFO_LOG(LOGIDENTIFIER, closedCaptionVisible);
6340
6341 m_closedCaptionsVisible = false;
6342
6343 if (!m_player || !hasClosedCaptions())
6344 return;
6345
6346 m_closedCaptionsVisible = closedCaptionVisible;
6347 m_player->setClosedCaptionsVisible(closedCaptionVisible);
6348
6349#if ENABLE(VIDEO_TRACK)
6350 markCaptionAndSubtitleTracksAsUnconfigured(Immediately);
6351 updateTextTrackDisplay();
6352#else
6353 if (hasMediaControls())
6354 mediaControls()->changedClosedCaptionsVisibility();
6355#endif
6356}
6357
6358void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible)
6359{
6360 m_webkitLegacyClosedCaptionOverride = visible;
6361 setClosedCaptionsVisible(visible);
6362}
6363
6364bool HTMLMediaElement::webkitClosedCaptionsVisible() const
6365{
6366 return m_webkitLegacyClosedCaptionOverride && m_closedCaptionsVisible;
6367}
6368
6369
6370bool HTMLMediaElement::webkitHasClosedCaptions() const
6371{
6372 return hasClosedCaptions();
6373}
6374
6375#if ENABLE(MEDIA_STATISTICS)
6376unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
6377{
6378 if (!m_player)
6379 return 0;
6380 return m_player->audioDecodedByteCount();
6381}
6382
6383unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
6384{
6385 if (!m_player)
6386 return 0;
6387 return m_player->videoDecodedByteCount();
6388}
6389#endif
6390
6391void HTMLMediaElement::mediaCanStart(Document& document)
6392{
6393 ASSERT_UNUSED(document, &document == &this->document());
6394 INFO_LOG(LOGIDENTIFIER, "m_isWaitingUntilMediaCanStart = ", m_isWaitingUntilMediaCanStart, ", m_pausedInternal = ", m_pausedInternal);
6395
6396 ASSERT(m_isWaitingUntilMediaCanStart || m_pausedInternal);
6397 if (m_isWaitingUntilMediaCanStart) {
6398 m_isWaitingUntilMediaCanStart = false;
6399 selectMediaResource();
6400 }
6401 if (m_pausedInternal)
6402 setPausedInternal(false);
6403}
6404
6405bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
6406{
6407 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
6408}
6409
6410void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
6411{
6412 if (m_shouldDelayLoadEvent == shouldDelay)
6413 return;
6414
6415 INFO_LOG(LOGIDENTIFIER, shouldDelay);
6416
6417 m_shouldDelayLoadEvent = shouldDelay;
6418 if (shouldDelay)
6419 document().incrementLoadEventDelayCount();
6420 else
6421 document().decrementLoadEventDelayCount();
6422}
6423
6424static String& sharedMediaCacheDirectory()
6425{
6426 static NeverDestroyed<String> sharedMediaCacheDirectory;
6427 return sharedMediaCacheDirectory;
6428}
6429
6430void HTMLMediaElement::setMediaCacheDirectory(const String& path)
6431{
6432 sharedMediaCacheDirectory() = path;
6433}
6434
6435const String& HTMLMediaElement::mediaCacheDirectory()
6436{
6437 return sharedMediaCacheDirectory();
6438}
6439
6440HashSet<RefPtr<SecurityOrigin>> HTMLMediaElement::originsInMediaCache(const String& path)
6441{
6442 return MediaPlayer::originsInMediaCache(path);
6443}
6444
6445void HTMLMediaElement::clearMediaCache(const String& path, WallTime modifiedSince)
6446{
6447 MediaPlayer::clearMediaCache(path, modifiedSince);
6448}
6449
6450void HTMLMediaElement::clearMediaCacheForOrigins(const String& path, const HashSet<RefPtr<SecurityOrigin>>& origins)
6451{
6452 MediaPlayer::clearMediaCacheForOrigins(path, origins);
6453}
6454
6455void HTMLMediaElement::resetMediaEngines()
6456{
6457 MediaPlayer::resetMediaEngines();
6458}
6459
6460void HTMLMediaElement::privateBrowsingStateDidChange()
6461{
6462 if (!m_player)
6463 return;
6464
6465 bool privateMode = document().page() && document().page()->usesEphemeralSession();
6466 m_player->setPrivateBrowsingMode(privateMode);
6467}
6468
6469MediaControls* HTMLMediaElement::mediaControls() const
6470{
6471#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6472 return nullptr;
6473#else
6474 auto root = userAgentShadowRoot();
6475 if (!root)
6476 return nullptr;
6477
6478 return childrenOfType<MediaControls>(*root).first();
6479#endif
6480}
6481
6482bool HTMLMediaElement::hasMediaControls() const
6483{
6484#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6485 return false;
6486#else
6487
6488 if (auto userAgent = userAgentShadowRoot()) {
6489 RefPtr<Node> node = childrenOfType<MediaControls>(*root).first();
6490 ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls());
6491 return node;
6492 }
6493
6494 return false;
6495#endif
6496}
6497
6498bool HTMLMediaElement::createMediaControls()
6499{
6500#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6501 ensureMediaControlsShadowRoot();
6502 return false;
6503#else
6504 if (hasMediaControls())
6505 return true;
6506
6507 auto mediaControls = MediaControls::create(document());
6508 if (!mediaControls)
6509 return false;
6510
6511 mediaControls->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
6512 mediaControls->reset();
6513 if (isFullscreen())
6514 mediaControls->enteredFullscreen();
6515
6516 ensureUserAgentShadowRoot().appendChild(mediaControls);
6517
6518 if (!controls() || !isConnected())
6519 mediaControls->hide();
6520
6521 return true;
6522#endif
6523}
6524
6525bool HTMLMediaElement::shouldForceControlsDisplay() const
6526{
6527 // Always create controls for autoplay video that requires user gesture due to being in low power mode.
6528 return isVideo() && autoplay() && m_mediaSession->hasBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoDueToLowPowerMode);
6529}
6530
6531void HTMLMediaElement::configureMediaControls()
6532{
6533 bool requireControls = controls();
6534
6535 // Always create controls for video when fullscreen playback is required.
6536 if (isVideo() && m_mediaSession->requiresFullscreenForVideoPlayback())
6537 requireControls = true;
6538
6539 if (shouldForceControlsDisplay())
6540 requireControls = true;
6541
6542 // Always create controls when in full screen mode.
6543 if (isFullscreen())
6544 requireControls = true;
6545
6546#if ENABLE(WIRELESS_PLAYBACK_TARGET)
6547 if (m_isPlayingToWirelessTarget)
6548 requireControls = true;
6549#endif
6550
6551#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6552 if (!requireControls || !isConnected() || !inActiveDocument())
6553 return;
6554
6555 ensureMediaControlsShadowRoot();
6556#else
6557 if (!requireControls || !isConnected() || !inActiveDocument()) {
6558 if (hasMediaControls())
6559 mediaControls()->hide();
6560 return;
6561 }
6562
6563 if (!hasMediaControls() && !createMediaControls())
6564 return;
6565
6566 mediaControls()->show();
6567#endif
6568}
6569
6570#if ENABLE(VIDEO_TRACK)
6571void HTMLMediaElement::configureTextTrackDisplay(TextTrackVisibilityCheckType checkType)
6572{
6573 ALWAYS_LOG(LOGIDENTIFIER);
6574 ASSERT(m_textTracks);
6575
6576 if (m_processingPreferenceChange)
6577 return;
6578
6579 if (document().activeDOMObjectsAreStopped())
6580 return;
6581
6582 bool haveVisibleTextTrack = false;
6583 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
6584 if (m_textTracks->item(i)->mode() == TextTrack::Mode::Showing) {
6585 haveVisibleTextTrack = true;
6586 break;
6587 }
6588 }
6589
6590 if (checkType == CheckTextTrackVisibility && m_haveVisibleTextTrack == haveVisibleTextTrack) {
6591 updateActiveTextTrackCues(currentMediaTime());
6592 return;
6593 }
6594
6595 m_haveVisibleTextTrack = haveVisibleTextTrack;
6596 m_closedCaptionsVisible = m_haveVisibleTextTrack;
6597
6598#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6599 if (!m_haveVisibleTextTrack)
6600 return;
6601
6602 ensureMediaControlsShadowRoot();
6603#else
6604 if (!m_haveVisibleTextTrack && !hasMediaControls())
6605 return;
6606 if (!hasMediaControls() && !createMediaControls())
6607 return;
6608
6609 mediaControls()->changedClosedCaptionsVisibility();
6610
6611 updateTextTrackDisplay();
6612 updateActiveTextTrackCues(currentMediaTime());
6613#endif
6614}
6615
6616void HTMLMediaElement::captionPreferencesChanged()
6617{
6618 if (!isVideo())
6619 return;
6620
6621 if (hasMediaControls())
6622 mediaControls()->textTrackPreferencesChanged();
6623
6624#if ENABLE(MEDIA_CONTROLS_SCRIPT)
6625 if (m_mediaControlsHost)
6626 m_mediaControlsHost->updateCaptionDisplaySizes();
6627#endif
6628
6629 if (m_player)
6630 m_player->tracksChanged();
6631
6632 if (!document().page())
6633 return;
6634
6635 CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences().captionDisplayMode();
6636 if (captionDisplayMode() == displayMode)
6637 return;
6638
6639 m_captionDisplayMode = displayMode;
6640 setWebkitClosedCaptionsVisible(captionDisplayMode() == CaptionUserPreferences::AlwaysOn);
6641}
6642
6643CaptionUserPreferences::CaptionDisplayMode HTMLMediaElement::captionDisplayMode()
6644{
6645 if (!m_captionDisplayMode.hasValue()) {
6646 if (document().page())
6647 m_captionDisplayMode = document().page()->group().captionPreferences().captionDisplayMode();
6648 else
6649 m_captionDisplayMode = CaptionUserPreferences::Automatic;
6650 }
6651
6652 return m_captionDisplayMode.value();
6653}
6654
6655void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMode mode)
6656{
6657 if (!m_textTracks)
6658 return;
6659
6660 INFO_LOG(LOGIDENTIFIER);
6661
6662 // Mark all tracks as not "configured" so that configureTextTracks()
6663 // will reconsider which tracks to display in light of new user preferences
6664 // (e.g. default tracks should not be displayed if the user has turned off
6665 // captions and non-default tracks should be displayed based on language
6666 // preferences if the user has turned captions on).
6667 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
6668 auto& track = *m_textTracks->item(i);
6669 auto kind = track.kind();
6670 if (kind == TextTrack::Kind::Subtitles || kind == TextTrack::Kind::Captions)
6671 track.setHasBeenConfigured(false);
6672 }
6673
6674 m_processingPreferenceChange = true;
6675 m_configureTextTracksTask.cancelTask();
6676 if (mode == Immediately) {
6677 Ref<HTMLMediaElement> protectedThis(*this); // configureTextTracks calls methods that can trigger arbitrary DOM mutations.
6678 configureTextTracks();
6679 }
6680 else
6681 scheduleConfigureTextTracks();
6682}
6683
6684#endif
6685
6686void HTMLMediaElement::createMediaPlayer()
6687{
6688 INFO_LOG(LOGIDENTIFIER);
6689
6690#if ENABLE(WEB_AUDIO)
6691 if (m_audioSourceNode)
6692 m_audioSourceNode->lock();
6693#endif
6694
6695#if ENABLE(MEDIA_SOURCE)
6696 detachMediaSource();
6697#endif
6698
6699#if ENABLE(VIDEO_TRACK)
6700 forgetResourceSpecificTracks();
6701#endif
6702
6703#if ENABLE(WIRELESS_PLAYBACK_TARGET)
6704 if (m_isPlayingToWirelessTarget)
6705 setIsPlayingToWirelessTarget(false);
6706#endif
6707
6708 m_player = MediaPlayer::create(*this);
6709 m_player->setBufferingPolicy(m_bufferingPolicy);
6710 schedulePlaybackControlsManagerUpdate();
6711
6712#if ENABLE(WEB_AUDIO)
6713 if (m_audioSourceNode) {
6714 // When creating the player, make sure its AudioSourceProvider knows about the MediaElementAudioSourceNode.
6715 if (audioSourceProvider())
6716 audioSourceProvider()->setClient(m_audioSourceNode);
6717
6718 m_audioSourceNode->unlock();
6719 }
6720#endif
6721
6722#if ENABLE(WIRELESS_PLAYBACK_TARGET)
6723 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
6724 m_hasPlaybackTargetAvailabilityListeners = true;
6725 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(true);
6726 enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event.
6727 }
6728#endif
6729
6730 updateSleepDisabling();
6731}
6732
6733#if ENABLE(WEB_AUDIO)
6734void HTMLMediaElement::setAudioSourceNode(MediaElementAudioSourceNode* sourceNode)
6735{
6736 m_audioSourceNode = sourceNode;
6737
6738 if (audioSourceProvider())
6739 audioSourceProvider()->setClient(m_audioSourceNode);
6740}
6741
6742AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
6743{
6744 if (m_player)
6745 return m_player->audioSourceProvider();
6746
6747 return 0;
6748}
6749#endif
6750
6751const String& HTMLMediaElement::mediaGroup() const
6752{
6753 return m_mediaGroup;
6754}
6755
6756void HTMLMediaElement::setMediaGroup(const String& group)
6757{
6758 if (m_mediaGroup == group)
6759 return;
6760 m_mediaGroup = group;
6761
6762 // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
6763 // attribute is set, changed, or removed, the user agent must run the following steps:
6764 // 1. Let m [this] be the media element in question.
6765 // 2. Let m have no current media controller, if it currently has one.
6766 setController(nullptr);
6767
6768 // 3. If m's mediagroup attribute is being removed, then abort these steps.
6769 if (group.isEmpty())
6770 return;
6771
6772 // 4. If there is another media element whose Document is the same as m's Document (even if one or both
6773 // of these elements are not actually in the Document),
6774 HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(&document());
6775 for (auto& element : elements) {
6776 if (element == this)
6777 continue;
6778
6779 // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
6780 // the new value of m's mediagroup attribute,
6781 if (element->mediaGroup() == group) {
6782 // then let controller be that media element's current media controller.
6783 setController(element->controller());
6784 return;
6785 }
6786 }
6787
6788 // Otherwise, let controller be a newly created MediaController.
6789 setController(MediaController::create(document()));
6790}
6791
6792MediaController* HTMLMediaElement::controller() const
6793{
6794 return m_mediaController.get();
6795}
6796
6797void HTMLMediaElement::setController(RefPtr<MediaController>&& controller)
6798{
6799 if (m_mediaController)
6800 m_mediaController->removeMediaElement(*this);
6801
6802 m_mediaController = WTFMove(controller);
6803
6804 if (m_mediaController)
6805 m_mediaController->addMediaElement(*this);
6806
6807 if (hasMediaControls())
6808 mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
6809}
6810
6811void HTMLMediaElement::setControllerForBindings(MediaController* controller)
6812{
6813 // 4.8.10.11.2 Media controllers: controller attribute.
6814 // On setting, it must first remove the element's mediagroup attribute, if any,
6815 setMediaGroup({ });
6816 // and then set the current media controller to the given value.
6817 setController(controller);
6818}
6819
6820void HTMLMediaElement::updateMediaController()
6821{
6822 if (m_mediaController)
6823 m_mediaController->reportControllerState();
6824}
6825
6826bool HTMLMediaElement::isBlocked() const
6827{
6828 // A media element is a blocked media element if its readyState attribute is in the
6829 // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
6830 if (m_readyState <= HAVE_CURRENT_DATA)
6831 return true;
6832
6833 // or if the element has paused for user interaction.
6834 return pausedForUserInteraction();
6835}
6836
6837bool HTMLMediaElement::isBlockedOnMediaController() const
6838{
6839 if (!m_mediaController)
6840 return false;
6841
6842 // A media element is blocked on its media controller if the MediaController is a blocked
6843 // media controller,
6844 if (m_mediaController->isBlocked())
6845 return true;
6846
6847 // or if its media controller position is either before the media resource's earliest possible
6848 // position relative to the MediaController's timeline or after the end of the media resource
6849 // relative to the MediaController's timeline.
6850 double mediaControllerPosition = m_mediaController->currentTime();
6851 if (mediaControllerPosition < 0 || mediaControllerPosition > duration())
6852 return true;
6853
6854 return false;
6855}
6856
6857void HTMLMediaElement::prepareMediaFragmentURI()
6858{
6859 MediaFragmentURIParser fragmentParser(m_currentSrc);
6860 MediaTime dur = durationMediaTime();
6861
6862 MediaTime start = fragmentParser.startTime();
6863 if (start.isValid() && start > MediaTime::zeroTime()) {
6864 m_fragmentStartTime = start;
6865 if (m_fragmentStartTime > dur)
6866 m_fragmentStartTime = dur;
6867 } else
6868 m_fragmentStartTime = MediaTime::invalidTime();
6869
6870 MediaTime end = fragmentParser.endTime();
6871 if (end.isValid() && end > MediaTime::zeroTime() && (!m_fragmentStartTime.isValid() || end > m_fragmentStartTime)) {
6872 m_fragmentEndTime = end;
6873 if (m_fragmentEndTime > dur)
6874 m_fragmentEndTime = dur;
6875 } else
6876 m_fragmentEndTime = MediaTime::invalidTime();
6877
6878 if (m_fragmentStartTime.isValid() && m_readyState < HAVE_FUTURE_DATA)
6879 prepareToPlay();
6880}
6881
6882void HTMLMediaElement::applyMediaFragmentURI()
6883{
6884 if (m_fragmentStartTime.isValid()) {
6885 m_sentEndEvent = false;
6886 seek(m_fragmentStartTime);
6887 }
6888}
6889
6890void HTMLMediaElement::updateSleepDisabling()
6891{
6892 SleepType shouldDisableSleep = this->shouldDisableSleep();
6893 if (shouldDisableSleep == SleepType::None && m_sleepDisabler)
6894 m_sleepDisabler = nullptr;
6895 else if (shouldDisableSleep != SleepType::None) {
6896 auto type = shouldDisableSleep == SleepType::Display ? PAL::SleepDisabler::Type::Display : PAL::SleepDisabler::Type::System;
6897 if (!m_sleepDisabler || m_sleepDisabler->type() != type)
6898 m_sleepDisabler = PAL::SleepDisabler::create("com.apple.WebCore: HTMLMediaElement playback", type);
6899 }
6900
6901 if (m_player)
6902 m_player->setShouldDisableSleep(shouldDisableSleep == SleepType::Display);
6903}
6904
6905#if ENABLE(MEDIA_STREAM)
6906static inline bool isRemoteMediaStreamVideoTrack(RefPtr<MediaStreamTrack>& item)
6907{
6908 auto* track = item.get();
6909 return track->privateTrack().type() == RealtimeMediaSource::Type::Video && !track->isCaptureTrack() && !track->isCanvas();
6910}
6911#endif
6912
6913HTMLMediaElement::SleepType HTMLMediaElement::shouldDisableSleep() const
6914{
6915#if !PLATFORM(COCOA) && !PLATFORM(GTK) && !PLATFORM(WPE)
6916 return SleepType::None;
6917#endif
6918 if (!m_player || m_player->paused() || loop())
6919 return SleepType::None;
6920
6921#if ENABLE(WIRELESS_PLAYBACK_TARGET)
6922 // If the media is playing remotely, we can't know definitively whether it has audio or video tracks.
6923 if (m_isPlayingToWirelessTarget)
6924 return SleepType::System;
6925#endif
6926
6927 bool shouldBeAbleToSleep = !hasVideo() || !hasAudio();
6928#if ENABLE(MEDIA_STREAM)
6929 // Remote media stream video tracks may have their corresponding audio tracks being played outside of the media element. Let's ensure to not IDLE the screen in that case.
6930 // FIXME: We should check that audio is being/to be played. Ideally, we would come up with a media stream agnostic heuristisc.
6931 shouldBeAbleToSleep = shouldBeAbleToSleep && !(m_mediaStreamSrcObject && WTF::anyOf(m_mediaStreamSrcObject->getTracks(), isRemoteMediaStreamVideoTrack));
6932#endif
6933
6934 if (shouldBeAbleToSleep)
6935 return SleepType::None;
6936
6937 if (m_elementIsHidden)
6938 return SleepType::System;
6939
6940 return SleepType::Display;
6941}
6942
6943String HTMLMediaElement::mediaPlayerReferrer() const
6944{
6945 RefPtr<Frame> frame = document().frame();
6946 if (!frame)
6947 return String();
6948
6949 return SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), m_currentSrc, frame->loader().outgoingReferrer());
6950}
6951
6952String HTMLMediaElement::mediaPlayerUserAgent() const
6953{
6954 RefPtr<Frame> frame = document().frame();
6955 if (!frame)
6956 return String();
6957
6958 return frame->loader().userAgent(m_currentSrc);
6959}
6960
6961#if ENABLE(AVF_CAPTIONS)
6962
6963static inline PlatformTextTrack::TrackKind toPlatform(TextTrack::Kind kind)
6964{
6965 switch (kind) {
6966 case TextTrack::Kind::Captions:
6967 return PlatformTextTrack::Caption;
6968 case TextTrack::Kind::Chapters:
6969 return PlatformTextTrack::Chapter;
6970 case TextTrack::Kind::Descriptions:
6971 return PlatformTextTrack::Description;
6972 case TextTrack::Kind::Forced:
6973 return PlatformTextTrack::Forced;
6974 case TextTrack::Kind::Metadata:
6975 return PlatformTextTrack::MetaData;
6976 case TextTrack::Kind::Subtitles:
6977 return PlatformTextTrack::Subtitle;
6978 }
6979 ASSERT_NOT_REACHED();
6980 return PlatformTextTrack::Caption;
6981}
6982
6983static inline PlatformTextTrack::TrackMode toPlatform(TextTrack::Mode mode)
6984{
6985 switch (mode) {
6986 case TextTrack::Mode::Disabled:
6987 return PlatformTextTrack::Disabled;
6988 case TextTrack::Mode::Hidden:
6989 return PlatformTextTrack::Hidden;
6990 case TextTrack::Mode::Showing:
6991 return PlatformTextTrack::Showing;
6992 }
6993 ASSERT_NOT_REACHED();
6994 return PlatformTextTrack::Disabled;
6995}
6996
6997Vector<RefPtr<PlatformTextTrack>> HTMLMediaElement::outOfBandTrackSources()
6998{
6999 Vector<RefPtr<PlatformTextTrack>> outOfBandTrackSources;
7000 for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) {
7001 URL url = trackElement.getNonEmptyURLAttribute(srcAttr);
7002 if (url.isEmpty())
7003 continue;
7004
7005 if (!isAllowedToLoadMediaURL(*this, url, trackElement.isInUserAgentShadowTree()))
7006 continue;
7007
7008 auto& track = trackElement.track();
7009 auto kind = track.kind();
7010
7011 // FIXME: The switch statement below preserves existing behavior where we ignore chapters and metadata tracks.
7012 // If we confirm this behavior is valuable, we should remove this comment. Otherwise, remove both comment and switch.
7013 switch (kind) {
7014 case TextTrack::Kind::Captions:
7015 case TextTrack::Kind::Descriptions:
7016 case TextTrack::Kind::Forced:
7017 case TextTrack::Kind::Subtitles:
7018 break;
7019 case TextTrack::Kind::Chapters:
7020 case TextTrack::Kind::Metadata:
7021 continue;
7022 }
7023
7024 outOfBandTrackSources.append(PlatformTextTrack::createOutOfBand(trackElement.label(), trackElement.srclang(), url.string(), toPlatform(track.mode()), toPlatform(kind), track.uniqueId(), trackElement.isDefault()));
7025 }
7026
7027 return outOfBandTrackSources;
7028}
7029
7030#endif
7031
7032void HTMLMediaElement::mediaPlayerEnterFullscreen()
7033{
7034 enterFullscreen();
7035}
7036
7037void HTMLMediaElement::mediaPlayerExitFullscreen()
7038{
7039 exitFullscreen();
7040}
7041
7042bool HTMLMediaElement::mediaPlayerIsFullscreen() const
7043{
7044 return isFullscreen();
7045}
7046
7047bool HTMLMediaElement::mediaPlayerIsFullscreenPermitted() const
7048{
7049 return m_mediaSession->fullscreenPermitted();
7050}
7051
7052bool HTMLMediaElement::mediaPlayerIsVideo() const
7053{
7054 return isVideo();
7055}
7056
7057LayoutRect HTMLMediaElement::mediaPlayerContentBoxRect() const
7058{
7059 auto* renderer = this->renderer();
7060 if (!renderer)
7061 return { };
7062 return renderer->enclosingBox().contentBoxRect();
7063}
7064
7065float HTMLMediaElement::mediaPlayerContentsScale() const
7066{
7067 if (auto page = document().page())
7068 return page->pageScaleFactor() * page->deviceScaleFactor();
7069 return 1;
7070}
7071
7072void HTMLMediaElement::mediaPlayerSetSize(const IntSize& size)
7073{
7074 setIntegralAttribute(widthAttr, size.width());
7075 setIntegralAttribute(heightAttr, size.height());
7076}
7077
7078void HTMLMediaElement::mediaPlayerPause()
7079{
7080 pause();
7081}
7082
7083void HTMLMediaElement::mediaPlayerPlay()
7084{
7085 play();
7086}
7087
7088bool HTMLMediaElement::mediaPlayerPlatformVolumeConfigurationRequired() const
7089{
7090 return !m_volumeInitialized;
7091}
7092
7093bool HTMLMediaElement::mediaPlayerIsPaused() const
7094{
7095 return paused();
7096}
7097
7098bool HTMLMediaElement::mediaPlayerIsLooping() const
7099{
7100 return loop();
7101}
7102
7103CachedResourceLoader* HTMLMediaElement::mediaPlayerCachedResourceLoader()
7104{
7105 return &document().cachedResourceLoader();
7106}
7107
7108RefPtr<PlatformMediaResourceLoader> HTMLMediaElement::mediaPlayerCreateResourceLoader()
7109{
7110 auto mediaResourceLoader = adoptRef(*new MediaResourceLoader(document(), *this, crossOrigin()));
7111
7112 m_lastMediaResourceLoaderForTesting = makeWeakPtr(mediaResourceLoader.get());
7113
7114 return mediaResourceLoader;
7115}
7116
7117const MediaResourceLoader* HTMLMediaElement::lastMediaResourceLoaderForTesting() const
7118{
7119 return m_lastMediaResourceLoaderForTesting.get();
7120}
7121
7122bool HTMLMediaElement::mediaPlayerShouldUsePersistentCache() const
7123{
7124 if (Page* page = document().page())
7125 return !page->usesEphemeralSession() && !page->isResourceCachingDisabled();
7126
7127 return false;
7128}
7129
7130const String& HTMLMediaElement::mediaPlayerMediaCacheDirectory() const
7131{
7132 return mediaCacheDirectory();
7133}
7134
7135String HTMLMediaElement::sourceApplicationIdentifier() const
7136{
7137 if (RefPtr<Frame> frame = document().frame()) {
7138 if (NetworkingContext* networkingContext = frame->loader().networkingContext())
7139 return networkingContext->sourceApplicationIdentifier();
7140 }
7141 return emptyString();
7142}
7143
7144Vector<String> HTMLMediaElement::mediaPlayerPreferredAudioCharacteristics() const
7145{
7146 if (Page* page = document().page())
7147 return page->group().captionPreferences().preferredAudioCharacteristics();
7148 return Vector<String>();
7149}
7150
7151#if PLATFORM(IOS_FAMILY)
7152String HTMLMediaElement::mediaPlayerNetworkInterfaceName() const
7153{
7154 return DeprecatedGlobalSettings::networkInterfaceName();
7155}
7156
7157bool HTMLMediaElement::mediaPlayerGetRawCookies(const URL& url, Vector<Cookie>& cookies) const
7158{
7159 if (auto* page = document().page())
7160 return page->cookieJar().getRawCookies(document(), url, cookies);
7161 return false;
7162}
7163#endif
7164
7165bool HTMLMediaElement::mediaPlayerIsInMediaDocument() const
7166{
7167 return document().isMediaDocument();
7168}
7169
7170void HTMLMediaElement::mediaPlayerEngineFailedToLoad() const
7171{
7172 if (!m_player)
7173 return;
7174
7175 if (auto* page = document().page())
7176 page->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::engineFailedToLoadKey(), m_player->engineDescription(), m_player->platformErrorCode(), 4, ShouldSample::No);
7177}
7178
7179double HTMLMediaElement::mediaPlayerRequestedPlaybackRate() const
7180{
7181 return potentiallyPlaying() ? requestedPlaybackRate() : 0;
7182}
7183
7184const Vector<ContentType>& HTMLMediaElement::mediaContentTypesRequiringHardwareSupport() const
7185{
7186 return document().settings().mediaContentTypesRequiringHardwareSupport();
7187}
7188
7189bool HTMLMediaElement::mediaPlayerShouldCheckHardwareSupport() const
7190{
7191 if (!document().settings().allowMediaContentTypesRequiringHardwareSupportAsFallback())
7192 return true;
7193
7194 if (m_loadState == LoadingFromSourceElement && m_currentSourceNode && !m_nextChildNodeToConsider)
7195 return false;
7196
7197 if (m_loadState == LoadingFromSrcAttr)
7198 return false;
7199
7200 return true;
7201}
7202
7203#if USE(GSTREAMER)
7204void HTMLMediaElement::requestInstallMissingPlugins(const String& details, const String& description, MediaPlayerRequestInstallMissingPluginsCallback& callback)
7205{
7206 if (!document().page())
7207 return;
7208
7209 document().page()->chrome().client().requestInstallMissingMediaPlugins(details, description, callback);
7210}
7211#endif
7212
7213void HTMLMediaElement::removeBehaviorRestrictionsAfterFirstUserGesture(MediaElementSession::BehaviorRestrictions mask)
7214{
7215 MediaElementSession::BehaviorRestrictions restrictionsToRemove = mask &
7216 (MediaElementSession::RequireUserGestureForLoad
7217#if ENABLE(WIRELESS_PLAYBACK_TARGET)
7218 | MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker
7219 | MediaElementSession::RequireUserGestureToAutoplayToExternalDevice
7220#endif
7221 | MediaElementSession::RequireUserGestureForVideoRateChange
7222 | MediaElementSession::RequireUserGestureForAudioRateChange
7223 | MediaElementSession::RequireUserGestureForFullscreen
7224 | MediaElementSession::RequireUserGestureForVideoDueToLowPowerMode
7225 | MediaElementSession::InvisibleAutoplayNotPermitted
7226 | MediaElementSession::RequireUserGestureToControlControlsManager);
7227
7228 m_removedBehaviorRestrictionsAfterFirstUserGesture = true;
7229
7230 m_mediaSession->removeBehaviorRestriction(restrictionsToRemove);
7231 document().topDocument().noteUserInteractionWithMediaElement();
7232}
7233
7234void HTMLMediaElement::updateRateChangeRestrictions()
7235{
7236 const auto& document = this->document();
7237 if (!document.ownerElement() && document.isMediaDocument())
7238 return;
7239
7240 const auto& topDocument = document.topDocument();
7241 if (topDocument.videoPlaybackRequiresUserGesture())
7242 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
7243 else
7244 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
7245
7246 if (topDocument.audioPlaybackRequiresUserGesture())
7247 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange);
7248 else
7249 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange);
7250}
7251
7252RefPtr<VideoPlaybackQuality> HTMLMediaElement::getVideoPlaybackQuality()
7253{
7254 RefPtr<DOMWindow> domWindow = document().domWindow();
7255 double timestamp = domWindow ? 1000 * domWindow->nowTimestamp() : 0;
7256
7257 auto metrics = m_player ? m_player->videoPlaybackQualityMetrics() : WTF::nullopt;
7258 if (!metrics)
7259 return VideoPlaybackQuality::create(timestamp, { });
7260
7261#if ENABLE(MEDIA_SOURCE)
7262 metrics.value().totalVideoFrames += m_droppedVideoFrames;
7263 metrics.value().droppedVideoFrames += m_droppedVideoFrames;
7264#endif
7265
7266 return VideoPlaybackQuality::create(timestamp, metrics.value());
7267}
7268
7269#if ENABLE(MEDIA_CONTROLS_SCRIPT)
7270DOMWrapperWorld& HTMLMediaElement::ensureIsolatedWorld()
7271{
7272 if (!m_isolatedWorld)
7273 m_isolatedWorld = DOMWrapperWorld::create(commonVM());
7274 return *m_isolatedWorld;
7275}
7276
7277bool HTMLMediaElement::ensureMediaControlsInjectedScript()
7278{
7279 INFO_LOG(LOGIDENTIFIER);
7280
7281 Page* page = document().page();
7282 if (!page)
7283 return false;
7284
7285 String mediaControlsScript = RenderTheme::singleton().mediaControlsScript();
7286 if (!mediaControlsScript.length())
7287 return false;
7288
7289 return setupAndCallJS([mediaControlsScript](JSDOMGlobalObject& globalObject, JSC::ExecState& exec, ScriptController& scriptController, DOMWrapperWorld& world) {
7290 auto& vm = globalObject.vm();
7291 auto scope = DECLARE_CATCH_SCOPE(vm);
7292
7293 auto functionValue = globalObject.get(&exec, JSC::Identifier::fromString(&exec, "createControls"));
7294 if (functionValue.isFunction(vm))
7295 return true;
7296
7297#ifndef NDEBUG
7298 // Setting a scriptURL allows the source to be debuggable in the inspector.
7299 URL scriptURL = URL({ }, "mediaControlsScript"_s);
7300#else
7301 URL scriptURL;
7302#endif
7303 scriptController.evaluateInWorld(ScriptSourceCode(mediaControlsScript, WTFMove(scriptURL)), world);
7304 if (UNLIKELY(scope.exception())) {
7305 scope.clearException();
7306 return false;
7307 }
7308
7309 return true;
7310 });
7311}
7312
7313void HTMLMediaElement::updatePageScaleFactorJSProperty()
7314{
7315 Page* page = document().page();
7316 if (!page)
7317 return;
7318
7319 setControllerJSProperty("pageScaleFactor", JSC::jsNumber(page->pageScaleFactor()));
7320}
7321
7322void HTMLMediaElement::updateUsesLTRUserInterfaceLayoutDirectionJSProperty()
7323{
7324 Page* page = document().page();
7325 if (!page)
7326 return;
7327
7328 bool usesLTRUserInterfaceLayoutDirectionProperty = page->userInterfaceLayoutDirection() == UserInterfaceLayoutDirection::LTR;
7329 setControllerJSProperty("usesLTRUserInterfaceLayoutDirection", JSC::jsBoolean(usesLTRUserInterfaceLayoutDirectionProperty));
7330}
7331
7332void HTMLMediaElement::setControllerJSProperty(const char* propertyName, JSC::JSValue propertyValue)
7333{
7334 setupAndCallJS([this, propertyName, propertyValue](JSDOMGlobalObject& globalObject, JSC::ExecState& exec, ScriptController&, DOMWrapperWorld&) {
7335 auto& vm = globalObject.vm();
7336 auto controllerValue = controllerJSValue(exec, globalObject, *this);
7337 if (controllerValue.isNull())
7338 return false;
7339
7340 JSC::PutPropertySlot propertySlot(controllerValue);
7341 auto* controllerObject = controllerValue.toObject(&exec);
7342 if (!controllerObject)
7343 return false;
7344
7345 controllerObject->methodTable(vm)->put(controllerObject, &exec, JSC::Identifier::fromString(&exec, propertyName), propertyValue, propertySlot);
7346
7347 return true;
7348 });
7349}
7350
7351void HTMLMediaElement::didAddUserAgentShadowRoot(ShadowRoot& root)
7352{
7353 INFO_LOG(LOGIDENTIFIER);
7354
7355 if (!ensureMediaControlsInjectedScript())
7356 return;
7357
7358 setupAndCallJS([this, &root](JSDOMGlobalObject& globalObject, JSC::ExecState& exec, ScriptController&, DOMWrapperWorld&) {
7359 auto& vm = globalObject.vm();
7360 auto scope = DECLARE_CATCH_SCOPE(vm);
7361
7362 // The media controls script must provide a method with the following details.
7363 // Name: createControls
7364 // Parameters:
7365 // 1. The ShadowRoot element that will hold the controls.
7366 // 2. This object (and HTMLMediaElement).
7367 // 3. The MediaControlsHost object.
7368 // Return value:
7369 // A reference to the created media controller instance.
7370
7371 auto functionValue = globalObject.get(&exec, JSC::Identifier::fromString(&exec, "createControls"));
7372 if (functionValue.isUndefinedOrNull())
7373 return false;
7374
7375 if (!m_mediaControlsHost)
7376 m_mediaControlsHost = MediaControlsHost::create(this);
7377
7378 auto mediaJSWrapper = toJS(&exec, &globalObject, *this);
7379 auto mediaControlsHostJSWrapper = toJS(&exec, &globalObject, *m_mediaControlsHost);
7380
7381 JSC::MarkedArgumentBuffer argList;
7382 argList.append(toJS(&exec, &globalObject, root));
7383 argList.append(mediaJSWrapper);
7384 argList.append(mediaControlsHostJSWrapper);
7385 ASSERT(!argList.hasOverflowed());
7386
7387 auto* function = functionValue.toObject(&exec);
7388 scope.assertNoException();
7389 JSC::CallData callData;
7390 auto callType = function->methodTable(vm)->getCallData(function, callData);
7391 if (callType == JSC::CallType::None)
7392 return false;
7393
7394 auto controllerValue = JSC::call(&exec, function, callType, callData, &globalObject, argList);
7395 scope.clearException();
7396 auto* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, controllerValue);
7397 if (!controllerObject)
7398 return false;
7399
7400 // Connect the Media, MediaControllerHost, and Controller so the GC knows about their relationship
7401 auto* mediaJSWrapperObject = mediaJSWrapper.toObject(&exec);
7402 scope.assertNoException();
7403 auto controlsHost = JSC::Identifier::fromString(&exec.vm(), "controlsHost");
7404
7405 ASSERT(!mediaJSWrapperObject->hasProperty(&exec, controlsHost));
7406
7407 mediaJSWrapperObject->putDirect(exec.vm(), controlsHost, mediaControlsHostJSWrapper, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly);
7408
7409 auto* mediaControlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, mediaControlsHostJSWrapper);
7410 if (!mediaControlsHostJSWrapperObject)
7411 return false;
7412
7413 auto controller = JSC::Identifier::fromString(&exec.vm(), "controller");
7414
7415 ASSERT(!controllerObject->hasProperty(&exec, controller));
7416
7417 mediaControlsHostJSWrapperObject->putDirect(exec.vm(), controller, controllerValue, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly);
7418
7419 updatePageScaleFactorJSProperty();
7420 updateUsesLTRUserInterfaceLayoutDirectionJSProperty();
7421
7422 if (UNLIKELY(scope.exception()))
7423 scope.clearException();
7424
7425 return true;
7426 });
7427}
7428
7429void HTMLMediaElement::setMediaControlsDependOnPageScaleFactor(bool dependsOnPageScale)
7430{
7431 INFO_LOG(LOGIDENTIFIER, dependsOnPageScale);
7432
7433 if (document().settings().mediaControlsScaleWithPageZoom()) {
7434 INFO_LOG(LOGIDENTIFIER, "forced to false by Settings value");
7435 m_mediaControlsDependOnPageScaleFactor = false;
7436 return;
7437 }
7438
7439 if (m_mediaControlsDependOnPageScaleFactor == dependsOnPageScale)
7440 return;
7441
7442 m_mediaControlsDependOnPageScaleFactor = dependsOnPageScale;
7443
7444 if (m_mediaControlsDependOnPageScaleFactor)
7445 document().registerForPageScaleFactorChangedCallbacks(*this);
7446 else
7447 document().unregisterForPageScaleFactorChangedCallbacks(*this);
7448}
7449
7450void HTMLMediaElement::updateMediaControlsAfterPresentationModeChange()
7451{
7452 // Don't execute script if the controls script hasn't been injected yet, or we have
7453 // stopped/suspended the object.
7454 if (!m_mediaControlsHost || document().activeDOMObjectsAreSuspended() || document().activeDOMObjectsAreStopped())
7455 return;
7456
7457 setupAndCallJS([this](JSDOMGlobalObject& globalObject, JSC::ExecState& exec, ScriptController&, DOMWrapperWorld&) {
7458 auto& vm = globalObject.vm();
7459 auto scope = DECLARE_THROW_SCOPE(vm);
7460
7461 auto controllerValue = controllerJSValue(exec, globalObject, *this);
7462 auto* controllerObject = controllerValue.toObject(&exec);
7463
7464 RETURN_IF_EXCEPTION(scope, false);
7465
7466 auto functionValue = controllerObject->get(&exec, JSC::Identifier::fromString(&exec, "handlePresentationModeChange"));
7467 if (UNLIKELY(scope.exception()) || functionValue.isUndefinedOrNull())
7468 return false;
7469
7470 auto* function = functionValue.toObject(&exec);
7471 scope.assertNoException();
7472 JSC::CallData callData;
7473 auto callType = function->methodTable(vm)->getCallData(function, callData);
7474 if (callType == JSC::CallType::None)
7475 return false;
7476
7477 JSC::MarkedArgumentBuffer argList;
7478 ASSERT(!argList.hasOverflowed());
7479 JSC::call(&exec, function, callType, callData, controllerObject, argList);
7480
7481 return true;
7482 });
7483}
7484
7485void HTMLMediaElement::pageScaleFactorChanged()
7486{
7487 updatePageScaleFactorJSProperty();
7488}
7489
7490void HTMLMediaElement::userInterfaceLayoutDirectionChanged()
7491{
7492 updateUsesLTRUserInterfaceLayoutDirectionJSProperty();
7493}
7494
7495String HTMLMediaElement::getCurrentMediaControlsStatus()
7496{
7497 ensureMediaControlsShadowRoot();
7498
7499 String status;
7500 setupAndCallJS([this, &status](JSDOMGlobalObject& globalObject, JSC::ExecState& exec, ScriptController&, DOMWrapperWorld&) {
7501 auto& vm = globalObject.vm();
7502 auto scope = DECLARE_THROW_SCOPE(vm);
7503
7504 auto controllerValue = controllerJSValue(exec, globalObject, *this);
7505 auto* controllerObject = controllerValue.toObject(&exec);
7506
7507 RETURN_IF_EXCEPTION(scope, false);
7508
7509 auto functionValue = controllerObject->get(&exec, JSC::Identifier::fromString(&exec, "getCurrentControlsStatus"));
7510 if (UNLIKELY(scope.exception()) || functionValue.isUndefinedOrNull())
7511 return false;
7512
7513 auto* function = functionValue.toObject(&exec);
7514 scope.assertNoException();
7515 JSC::CallData callData;
7516 auto callType = function->methodTable(vm)->getCallData(function, callData);
7517 JSC::MarkedArgumentBuffer argList;
7518 ASSERT(!argList.hasOverflowed());
7519 if (callType == JSC::CallType::None)
7520 return false;
7521
7522 auto outputValue = JSC::call(&exec, function, callType, callData, controllerObject, argList);
7523
7524 RETURN_IF_EXCEPTION(scope, false);
7525
7526 status = outputValue.getString(&exec);
7527 return true;
7528 });
7529
7530 return status;
7531}
7532#endif // ENABLE(MEDIA_CONTROLS_SCRIPT)
7533
7534unsigned long long HTMLMediaElement::fileSize() const
7535{
7536 if (m_player)
7537 return m_player->fileSize();
7538
7539 return 0;
7540}
7541
7542PlatformMediaSession::MediaType HTMLMediaElement::mediaType() const
7543{
7544 if (m_player && m_readyState >= HAVE_METADATA) {
7545 if (hasVideo() && hasAudio() && !muted())
7546 return PlatformMediaSession::VideoAudio;
7547 return hasVideo() ? PlatformMediaSession::Video : PlatformMediaSession::Audio;
7548 }
7549
7550 return presentationType();
7551}
7552
7553PlatformMediaSession::MediaType HTMLMediaElement::presentationType() const
7554{
7555 if (hasTagName(HTMLNames::videoTag))
7556 return muted() ? PlatformMediaSession::Video : PlatformMediaSession::VideoAudio;
7557
7558 return PlatformMediaSession::Audio;
7559}
7560
7561PlatformMediaSession::DisplayType HTMLMediaElement::displayType() const
7562{
7563 if (m_videoFullscreenMode == VideoFullscreenModeStandard)
7564 return PlatformMediaSession::Fullscreen;
7565 if (m_videoFullscreenMode & VideoFullscreenModePictureInPicture)
7566 return PlatformMediaSession::Optimized;
7567 if (m_videoFullscreenMode == VideoFullscreenModeNone)
7568 return PlatformMediaSession::Normal;
7569
7570 ASSERT_NOT_REACHED();
7571 return PlatformMediaSession::Normal;
7572}
7573
7574PlatformMediaSession::CharacteristicsFlags HTMLMediaElement::characteristics() const
7575{
7576 if (m_readyState < HAVE_METADATA)
7577 return PlatformMediaSession::HasNothing;
7578
7579 PlatformMediaSession::CharacteristicsFlags state = PlatformMediaSession::HasNothing;
7580 if (isVideo() && hasVideo())
7581 state |= PlatformMediaSession::HasVideo;
7582 if (this->hasAudio())
7583 state |= PlatformMediaSession::HasAudio;
7584
7585 return state;
7586}
7587
7588bool HTMLMediaElement::canProduceAudio() const
7589{
7590#if ENABLE(WIRELESS_PLAYBACK_TARGET)
7591 // Because the remote target could unmute playback without notifying us, we must assume
7592 // that we may be playing audio.
7593 if (m_isPlayingToWirelessTarget)
7594 return true;
7595#endif
7596
7597 if (muted())
7598 return false;
7599
7600 return m_player && m_readyState >= HAVE_METADATA && hasAudio();
7601}
7602
7603bool HTMLMediaElement::isSuspended() const
7604{
7605 return document().activeDOMObjectsAreSuspended() || document().activeDOMObjectsAreStopped();
7606}
7607
7608#if ENABLE(MEDIA_SOURCE)
7609size_t HTMLMediaElement::maximumSourceBufferSize(const SourceBuffer& buffer) const
7610{
7611 return m_mediaSession->maximumMediaSourceBufferSize(buffer);
7612}
7613#endif
7614
7615void HTMLMediaElement::suspendPlayback()
7616{
7617 INFO_LOG(LOGIDENTIFIER, "paused = ", paused());
7618 if (!paused())
7619 pause();
7620}
7621
7622void HTMLMediaElement::resumeAutoplaying()
7623{
7624 INFO_LOG(LOGIDENTIFIER, "paused = ", paused());
7625 m_autoplaying = true;
7626
7627 if (canTransitionFromAutoplayToPlay())
7628 play();
7629}
7630
7631void HTMLMediaElement::mayResumePlayback(bool shouldResume)
7632{
7633 INFO_LOG(LOGIDENTIFIER, "paused = ", paused());
7634 if (paused() && shouldResume)
7635 play();
7636}
7637
7638String HTMLMediaElement::mediaSessionTitle() const
7639{
7640 if (!document().page() || document().page()->usesEphemeralSession())
7641 return emptyString();
7642
7643 auto title = String(attributeWithoutSynchronization(titleAttr)).stripWhiteSpace().simplifyWhiteSpace();
7644 if (!title.isEmpty())
7645 return title;
7646
7647 title = document().title().stripWhiteSpace().simplifyWhiteSpace();
7648 if (!title.isEmpty())
7649 return title;
7650
7651 title = m_currentSrc.host().toString();
7652#if PLATFORM(MAC) || PLATFORM(IOS_FAMILY)
7653 if (!title.isEmpty())
7654 title = decodeHostName(title);
7655#endif
7656 if (!title.isEmpty()) {
7657 auto domain = RegistrableDomain { m_currentSrc };
7658 if (!domain.isEmpty())
7659 title = domain.string();
7660 }
7661
7662 return title;
7663}
7664
7665uint64_t HTMLMediaElement::mediaSessionUniqueIdentifier() const
7666{
7667 auto& url = m_currentSrc.string();
7668 return url.impl() ? url.impl()->hash() : 0;
7669}
7670
7671void HTMLMediaElement::didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType command, const PlatformMediaSession::RemoteCommandArgument* argument)
7672{
7673 INFO_LOG(LOGIDENTIFIER, command);
7674
7675 UserGestureIndicator remoteControlUserGesture(ProcessingUserGesture, &document());
7676 switch (command) {
7677 case PlatformMediaSession::PlayCommand:
7678 play();
7679 break;
7680 case PlatformMediaSession::StopCommand:
7681 case PlatformMediaSession::PauseCommand:
7682 pause();
7683 break;
7684 case PlatformMediaSession::TogglePlayPauseCommand:
7685 canPlay() ? play() : pause();
7686 break;
7687 case PlatformMediaSession::BeginSeekingBackwardCommand:
7688 beginScanning(Backward);
7689 break;
7690 case PlatformMediaSession::BeginSeekingForwardCommand:
7691 beginScanning(Forward);
7692 break;
7693 case PlatformMediaSession::EndSeekingBackwardCommand:
7694 case PlatformMediaSession::EndSeekingForwardCommand:
7695 endScanning();
7696 break;
7697 case PlatformMediaSession::SeekToPlaybackPositionCommand:
7698 ASSERT(argument);
7699 if (argument)
7700 handleSeekToPlaybackPosition(argument->asDouble);
7701 break;
7702 default:
7703 { } // Do nothing
7704 }
7705}
7706
7707bool HTMLMediaElement::supportsSeeking() const
7708{
7709 return !document().quirks().needsSeekingSupportDisabled() && !isLiveStream();
7710}
7711
7712bool HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(PlatformMediaSession::InterruptionType type) const
7713{
7714 if (type == PlatformMediaSession::EnteringBackground) {
7715 if (isPlayingToExternalTarget()) {
7716 INFO_LOG(LOGIDENTIFIER, "returning true because isPlayingToExternalTarget() is true");
7717 return true;
7718 }
7719 if (m_videoFullscreenMode & VideoFullscreenModePictureInPicture)
7720 return true;
7721#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
7722 if (((m_videoFullscreenMode == VideoFullscreenModeStandard) || m_videoFullscreenStandby) && supportsPictureInPicture() && isPlaying())
7723 return true;
7724#endif
7725 } else if (type == PlatformMediaSession::SuspendedUnderLock) {
7726 if (isPlayingToExternalTarget()) {
7727 INFO_LOG(LOGIDENTIFIER, "returning true because isPlayingToExternalTarget() is true");
7728 return true;
7729 }
7730 }
7731 return false;
7732}
7733
7734bool HTMLMediaElement::processingUserGestureForMedia() const
7735{
7736 return document().processingUserGestureForMedia();
7737}
7738#if ENABLE(WIRELESS_PLAYBACK_TARGET)
7739
7740void HTMLMediaElement::scheduleUpdateMediaState()
7741{
7742 if (m_updateMediaStateTask.hasPendingTask())
7743 return;
7744
7745 auto logSiteIdentifier = LOGIDENTIFIER;
7746 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
7747 m_updateMediaStateTask.scheduleTask([this, logSiteIdentifier] {
7748 UNUSED_PARAM(logSiteIdentifier);
7749 ALWAYS_LOG(logSiteIdentifier, "lambda(), task fired");
7750 Ref<HTMLMediaElement> protectedThis(*this); // updateMediaState calls methods that can trigger arbitrary DOM mutations.
7751 updateMediaState();
7752 });
7753}
7754
7755void HTMLMediaElement::updateMediaState()
7756{
7757 MediaProducer::MediaStateFlags state = mediaState();
7758 if (m_mediaState == state)
7759 return;
7760
7761 m_mediaState = state;
7762 m_mediaSession->mediaStateDidChange(m_mediaState);
7763#if ENABLE(MEDIA_SESSION)
7764 document().updateIsPlayingMedia(m_elementID);
7765#else
7766 document().updateIsPlayingMedia();
7767#endif
7768}
7769#endif
7770
7771MediaProducer::MediaStateFlags HTMLMediaElement::mediaState() const
7772{
7773 MediaStateFlags state = IsNotPlaying;
7774
7775 bool hasActiveVideo = isVideo() && hasVideo();
7776 bool hasAudio = this->hasAudio();
7777 if (isPlayingToExternalTarget())
7778 state |= IsPlayingToExternalDevice;
7779
7780#if ENABLE(WIRELESS_PLAYBACK_TARGET)
7781 if (m_hasPlaybackTargetAvailabilityListeners) {
7782 state |= HasPlaybackTargetAvailabilityListener;
7783 if (!m_mediaSession->wirelessVideoPlaybackDisabled())
7784 state |= RequiresPlaybackTargetMonitoring;
7785 }
7786
7787 bool requireUserGesture = m_mediaSession->hasBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice);
7788 if (m_readyState >= HAVE_METADATA && !requireUserGesture && !m_failedToPlayToWirelessTarget)
7789 state |= ExternalDeviceAutoPlayCandidate;
7790
7791 if (hasActiveVideo || hasAudio)
7792 state |= HasAudioOrVideo;
7793
7794 if (hasActiveVideo && endedPlayback())
7795 state |= DidPlayToEnd;
7796#endif
7797
7798 if (!isPlaying())
7799 return state;
7800
7801 if (hasAudio && !muted() && volume())
7802 state |= IsPlayingAudio;
7803
7804 if (hasActiveVideo)
7805 state |= IsPlayingVideo;
7806
7807 return state;
7808}
7809
7810void HTMLMediaElement::handleAutoplayEvent(AutoplayEvent event)
7811{
7812 if (Page* page = document().page()) {
7813 bool hasAudio = this->hasAudio() && !muted() && volume();
7814 bool wasPlaybackPrevented = m_autoplayEventPlaybackState == AutoplayEventPlaybackState::PreventedAutoplay;
7815 bool hasMainContent = m_mediaSession && m_mediaSession->isMainContentForPurposesOfAutoplayEvents();
7816 ALWAYS_LOG(LOGIDENTIFIER, "hasAudio = ", hasAudio, " wasPlaybackPrevented = ", wasPlaybackPrevented, " hasMainContent = ", hasMainContent);
7817
7818 OptionSet<AutoplayEventFlags> flags;
7819 if (hasAudio)
7820 flags.add(AutoplayEventFlags::HasAudio);
7821 if (wasPlaybackPrevented)
7822 flags.add(AutoplayEventFlags::PlaybackWasPrevented);
7823 if (hasMainContent)
7824 flags.add(AutoplayEventFlags::MediaIsMainContent);
7825
7826 page->chrome().client().handleAutoplayEvent(event, flags);
7827 }
7828}
7829
7830void HTMLMediaElement::userDidInterfereWithAutoplay()
7831{
7832 if (m_autoplayEventPlaybackState != AutoplayEventPlaybackState::StartedWithoutUserGesture)
7833 return;
7834
7835 // Only consider interference in the first 10 seconds of automatic playback.
7836 if (currentTime() - playbackStartedTime() > AutoplayInterferenceTimeThreshold)
7837 return;
7838
7839 ALWAYS_LOG(LOGIDENTIFIER);
7840 handleAutoplayEvent(AutoplayEvent::UserDidInterfereWithPlayback);
7841 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::None);
7842}
7843
7844void HTMLMediaElement::setAutoplayEventPlaybackState(AutoplayEventPlaybackState reason)
7845{
7846 ALWAYS_LOG(LOGIDENTIFIER, reason);
7847
7848 m_autoplayEventPlaybackState = reason;
7849
7850 if (reason == AutoplayEventPlaybackState::PreventedAutoplay) {
7851 dispatchPlayPauseEventsIfNeedsQuirks();
7852 handleAutoplayEvent(AutoplayEvent::DidPreventMediaFromPlaying);
7853 }
7854}
7855
7856void HTMLMediaElement::pageMutedStateDidChange()
7857{
7858 updateVolume();
7859
7860 if (Page* page = document().page()) {
7861 if (hasAudio() && !muted() && page->isAudioMuted())
7862 userDidInterfereWithAutoplay();
7863 }
7864}
7865
7866bool HTMLMediaElement::effectiveMuted() const
7867{
7868 return muted() || (document().page() && document().page()->isAudioMuted());
7869}
7870
7871bool HTMLMediaElement::doesHaveAttribute(const AtomicString& attribute, AtomicString* value) const
7872{
7873 QualifiedName attributeName(nullAtom(), attribute, nullAtom());
7874
7875 auto& elementValue = attributeWithoutSynchronization(attributeName);
7876 if (elementValue.isNull())
7877 return false;
7878
7879 if (attributeName == HTMLNames::x_itunes_inherit_uri_query_componentAttr && !document().settings().enableInheritURIQueryComponent())
7880 return false;
7881
7882 if (value)
7883 *value = elementValue;
7884
7885 return true;
7886}
7887
7888void HTMLMediaElement::setBufferingPolicy(BufferingPolicy policy)
7889{
7890 if (policy == m_bufferingPolicy)
7891 return;
7892
7893 INFO_LOG(LOGIDENTIFIER, policy);
7894
7895 m_bufferingPolicy = policy;
7896 if (m_player)
7897 m_player->setBufferingPolicy(policy);
7898}
7899
7900void HTMLMediaElement::purgeBufferedDataIfPossible()
7901{
7902 INFO_LOG(LOGIDENTIFIER);
7903
7904 if (!MemoryPressureHandler::singleton().isUnderMemoryPressure() && m_mediaSession->preferredBufferingPolicy() == BufferingPolicy::Default)
7905 return;
7906
7907 if (isPlayingToExternalTarget()) {
7908 INFO_LOG(LOGIDENTIFIER, "early return because playing to wireless target");
7909 return;
7910 }
7911
7912 setBufferingPolicy(BufferingPolicy::PurgeResources);
7913}
7914
7915bool HTMLMediaElement::canSaveMediaData() const
7916{
7917 if (m_player)
7918 return m_player->canSaveMediaData();
7919
7920 return false;
7921}
7922
7923#if ENABLE(MEDIA_SESSION)
7924double HTMLMediaElement::playerVolume() const
7925{
7926 return m_player ? m_player->volume() : 0;
7927}
7928
7929MediaSession* HTMLMediaElement::session() const
7930{
7931 RefPtr<MediaSession> session = m_session.get();
7932 if (session && session == &document().defaultMediaSession())
7933 return nullptr;
7934
7935 return session.get();
7936}
7937
7938void HTMLMediaElement::setSession(MediaSession* session)
7939{
7940 // 6.1. Extensions to the HTMLMediaElement interface
7941 // 1. Let m be the media element in question.
7942 // 2. Let old media session be m’s current media session, if it has one, and null otherwise.
7943 // 3. Let m’s current media session be the new value or the top-level browsing context’s media session if the new value is null.
7944 // 4. Let new media session be m’s current media session.
7945
7946 // 5. Update media sessions: If old media session and new media session are the same (whether both null or both the same media session), then terminate these steps.
7947 if (m_session.get() == session)
7948 return;
7949
7950 if (m_session) {
7951 // 6. If m is an audio-producing participant of old media session, then pause m and remove m from old media session’s list of audio-producing participants.
7952 if (m_session->isMediaElementActive(*this))
7953 pause();
7954
7955 m_session->removeMediaElement(*this);
7956
7957 // 7. If old media session is not null and no longer has one or more audio-producing participants, then run the media session deactivation algorithm for old media session.
7958 if (!m_session->hasActiveMediaElements())
7959 m_session->deactivate();
7960 }
7961
7962 if (session)
7963 setSessionInternal(*session);
7964 else
7965 setSessionInternal(document().defaultMediaSession());
7966}
7967
7968void HTMLMediaElement::setSessionInternal(MediaSession& session)
7969{
7970 m_session = &session;
7971 session.addMediaElement(*this);
7972 m_kind = session.kind();
7973}
7974
7975void HTMLMediaElement::setShouldDuck(bool duck)
7976{
7977 if (m_shouldDuck == duck)
7978 return;
7979
7980 m_shouldDuck = duck;
7981 updateVolume();
7982}
7983
7984#endif
7985
7986void HTMLMediaElement::allowsMediaDocumentInlinePlaybackChanged()
7987{
7988 if (potentiallyPlaying() && m_mediaSession->requiresFullscreenForVideoPlayback() && !isFullscreen())
7989 enterFullscreen();
7990}
7991
7992bool HTMLMediaElement::isVideoTooSmallForInlinePlayback()
7993{
7994 auto* renderer = this->renderer();
7995
7996 if (!renderer || !is<RenderVideo>(*renderer))
7997 return true;
7998
7999 IntRect videoBox = downcast<RenderVideo>(*renderer).videoBox();
8000 return (videoBox.width() <= 1 || videoBox.height() <= 1);
8001}
8002
8003void HTMLMediaElement::isVisibleInViewportChanged()
8004{
8005 m_visibilityChangeTaskQueue.enqueueTask([this] {
8006 m_mediaSession->isVisibleInViewportChanged();
8007 updateShouldAutoplay();
8008 schedulePlaybackControlsManagerUpdate();
8009 });
8010}
8011
8012void HTMLMediaElement::updateShouldAutoplay()
8013{
8014 if (!autoplay())
8015 return;
8016
8017 if (!m_mediaSession->hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted))
8018 return;
8019
8020 bool canAutoplay = mediaSession().autoplayPermitted();
8021 if (canAutoplay
8022 && m_mediaSession->state() == PlatformMediaSession::Interrupted
8023 && m_mediaSession->interruptionType() == PlatformMediaSession::InvisibleAutoplay)
8024 m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying);
8025 else if (!canAutoplay
8026 && m_mediaSession->state() != PlatformMediaSession::Interrupted)
8027 m_mediaSession->beginInterruption(PlatformMediaSession::InvisibleAutoplay);
8028}
8029
8030void HTMLMediaElement::updateShouldPlay()
8031{
8032 if (!paused() && !m_mediaSession->playbackPermitted()) {
8033 pauseInternal();
8034 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
8035 } else if (canTransitionFromAutoplayToPlay())
8036 play();
8037}
8038
8039void HTMLMediaElement::resetPlaybackSessionState()
8040{
8041 if (m_mediaSession)
8042 m_mediaSession->resetPlaybackSessionState();
8043}
8044
8045bool HTMLMediaElement::isVisibleInViewport() const
8046{
8047 auto renderer = this->renderer();
8048 return renderer && renderer->visibleInViewportState() == VisibleInViewportState::Yes;
8049}
8050
8051void HTMLMediaElement::schedulePlaybackControlsManagerUpdate()
8052{
8053 Page* page = document().page();
8054 if (!page)
8055 return;
8056 page->schedulePlaybackControlsManagerUpdate();
8057}
8058
8059void HTMLMediaElement::playbackControlsManagerBehaviorRestrictionsTimerFired()
8060{
8061 if (m_playbackControlsManagerBehaviorRestrictionsQueue.hasPendingTask())
8062 return;
8063
8064 if (!m_mediaSession->hasBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager))
8065 return;
8066
8067 RefPtr<HTMLMediaElement> protectedThis(this);
8068 m_playbackControlsManagerBehaviorRestrictionsQueue.scheduleTask([protectedThis] () {
8069 MediaElementSession* mediaElementSession = protectedThis->m_mediaSession.get();
8070 if (protectedThis->isPlaying() || mediaElementSession->state() == PlatformMediaSession::Autoplaying || mediaElementSession->state() == PlatformMediaSession::Playing)
8071 return;
8072
8073 mediaElementSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
8074 protectedThis->schedulePlaybackControlsManagerUpdate();
8075 });
8076}
8077
8078bool HTMLMediaElement::shouldOverrideBackgroundLoadingRestriction() const
8079{
8080 if (isPlayingToExternalTarget())
8081 return true;
8082
8083 return m_videoFullscreenMode == VideoFullscreenModePictureInPicture;
8084}
8085
8086void HTMLMediaElement::fullscreenModeChanged(VideoFullscreenMode mode)
8087{
8088 if (m_videoFullscreenMode == mode)
8089 return;
8090
8091 m_videoFullscreenMode = mode;
8092 visibilityStateChanged();
8093 schedulePlaybackControlsManagerUpdate();
8094}
8095
8096#if !RELEASE_LOG_DISABLED
8097WTFLogChannel& HTMLMediaElement::logChannel() const
8098{
8099 return LogMedia;
8100}
8101#endif
8102
8103bool HTMLMediaElement::willLog(WTFLogLevel level) const
8104{
8105#if !RELEASE_LOG_DISABLED
8106 return m_logger->willLog(logChannel(), level);
8107#else
8108 UNUSED_PARAM(level);
8109 return false;
8110#endif
8111}
8112
8113void HTMLMediaElement::applicationWillResignActive()
8114{
8115 if (m_player)
8116 m_player->applicationWillResignActive();
8117}
8118
8119void HTMLMediaElement::applicationDidBecomeActive()
8120{
8121 if (m_player)
8122 m_player->applicationDidBecomeActive();
8123}
8124
8125void HTMLMediaElement::setInActiveDocument(bool inActiveDocument)
8126{
8127 if (inActiveDocument == m_inActiveDocument)
8128 return;
8129
8130 m_inActiveDocument = inActiveDocument;
8131 m_mediaSession->inActiveDocumentChanged();
8132}
8133
8134HTMLMediaElementEnums::BufferingPolicy HTMLMediaElement::bufferingPolicy() const
8135{
8136 return m_bufferingPolicy;
8137}
8138
8139}
8140
8141#endif
8142