9671b687ac6081ffed2d7c60beb650b8f5742684
[WebKit-https.git] / Source / WebCore / html / MediaElementSession.cpp
1 /*
2  * Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO)
29
30 #include "MediaElementSession.h"
31
32 #include "Chrome.h"
33 #include "ChromeClient.h"
34 #include "Document.h"
35 #include "Frame.h"
36 #include "FrameView.h"
37 #include "HTMLAudioElement.h"
38 #include "HTMLMediaElement.h"
39 #include "HTMLNames.h"
40 #include "HTMLVideoElement.h"
41 #include "HitTestResult.h"
42 #include "Logging.h"
43 #include "MainFrame.h"
44 #include "Page.h"
45 #include "PlatformMediaSessionManager.h"
46 #include "RenderMedia.h"
47 #include "RenderView.h"
48 #include "ScriptController.h"
49 #include "SourceBuffer.h"
50 #include <wtf/CurrentTime.h>
51
52 #if PLATFORM(IOS)
53 #include "AudioSession.h"
54 #include "RuntimeApplicationChecks.h"
55 #include <wtf/spi/darwin/dyldSPI.h>
56 #endif
57
58 namespace WebCore {
59
60 static const double elementMainContentCheckInterval = .250;
61
62 static bool isElementRectMostlyInMainFrame(const HTMLMediaElement&);
63 static bool isElementLargeEnoughForMainContent(const HTMLMediaElement&, MediaSessionMainContentPurpose);
64
65 #if !LOG_DISABLED
66 static String restrictionName(MediaElementSession::BehaviorRestrictions restriction)
67 {
68     StringBuilder restrictionBuilder;
69 #define CASE(restrictionType) \
70     if (restriction & MediaElementSession::restrictionType) { \
71         if (!restrictionBuilder.isEmpty()) \
72             restrictionBuilder.appendLiteral(", "); \
73         restrictionBuilder.append(#restrictionType); \
74     } \
75
76     CASE(NoRestrictions);
77     CASE(RequireUserGestureForLoad);
78     CASE(RequireUserGestureForVideoRateChange);
79     CASE(RequireUserGestureForAudioRateChange);
80     CASE(RequireUserGestureForFullscreen);
81     CASE(RequirePageConsentToLoadMedia);
82     CASE(RequirePageConsentToResumeMedia);
83 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
84     CASE(RequireUserGestureToShowPlaybackTargetPicker);
85     CASE(WirelessVideoPlaybackDisabled);
86 #endif
87     CASE(InvisibleAutoplayNotPermitted);
88     CASE(OverrideUserGestureRequirementForMainContent);
89
90     return restrictionBuilder.toString();
91 }
92 #endif
93
94 static bool pageExplicitlyAllowsElementToAutoplayInline(const HTMLMediaElement& element)
95 {
96     Document& document = element.document();
97     Page* page = document.page();
98     return document.isMediaDocument() && !document.ownerElement() && page && page->allowsMediaDocumentInlinePlayback();
99 }
100
101 MediaElementSession::MediaElementSession(HTMLMediaElement& element)
102     : PlatformMediaSession(element)
103     , m_element(element)
104     , m_restrictions(NoRestrictions)
105 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
106     , m_targetAvailabilityChangedTimer(*this, &MediaElementSession::targetAvailabilityChangedTimerFired)
107 #endif
108     , m_mainContentCheckTimer(*this, &MediaElementSession::mainContentCheckTimerFired)
109 {
110 }
111
112 void MediaElementSession::registerWithDocument(Document& document)
113 {
114 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
115     document.addPlaybackTargetPickerClient(*this);
116 #else
117     UNUSED_PARAM(document);
118 #endif
119 }
120
121 void MediaElementSession::unregisterWithDocument(Document& document)
122 {
123 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
124     document.removePlaybackTargetPickerClient(*this);
125 #else
126     UNUSED_PARAM(document);
127 #endif
128 }
129
130 void MediaElementSession::addBehaviorRestriction(BehaviorRestrictions restriction)
131 {
132     LOG(Media, "MediaElementSession::addBehaviorRestriction - adding %s", restrictionName(restriction).utf8().data());
133     m_restrictions |= restriction;
134
135     if (restriction & OverrideUserGestureRequirementForMainContent)
136         m_mainContentCheckTimer.startRepeating(elementMainContentCheckInterval);
137 }
138
139 void MediaElementSession::removeBehaviorRestriction(BehaviorRestrictions restriction)
140 {
141     if (restriction & RequireUserGestureToControlControlsManager) {
142         m_mostRecentUserInteractionTime = monotonicallyIncreasingTime();
143         if (auto page = m_element.document().page())
144             page->setAllowsPlaybackControlsForAutoplayingAudio(true);
145     }
146
147     LOG(Media, "MediaElementSession::removeBehaviorRestriction - removing %s", restrictionName(restriction).utf8().data());
148     m_restrictions &= ~restriction;
149 }
150
151 bool MediaElementSession::playbackPermitted(const HTMLMediaElement& element) const
152 {
153     if (pageExplicitlyAllowsElementToAutoplayInline(element))
154         return true;
155
156     if (requiresFullscreenForVideoPlayback(element) && !fullscreenPermitted(element)) {
157         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE because of fullscreen restriction");
158         return false;
159     }
160
161     if (m_restrictions & OverrideUserGestureRequirementForMainContent && updateIsMainContent())
162         return true;
163
164     if (m_restrictions & RequireUserGestureForVideoRateChange && element.isVideo() && !ScriptController::processingUserGestureForMedia()) {
165         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE because of video rate change restriction");
166         return false;
167     }
168
169     if (m_restrictions & RequireUserGestureForAudioRateChange && (!element.isVideo() || element.hasAudio()) && !element.muted() && !ScriptController::processingUserGestureForMedia()) {
170         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE because of audio rate change restriction");
171         return false;
172     }
173
174     return true;
175 }
176
177 bool MediaElementSession::autoplayPermitted() const
178 {
179     const Document& document = m_element.document();
180     if (document.pageCacheState() != Document::NotInPageCache)
181         return false;
182     if (document.activeDOMObjectsAreSuspended())
183         return false;
184
185     if (!hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted))
186         return true;
187
188     auto* renderer = m_element.renderer();
189     if (!renderer)
190         return false;
191     if (renderer->style().visibility() != VISIBLE)
192         return false;
193     if (renderer->view().frameView().isOffscreen())
194         return false;
195     if (renderer->visibleInViewportState() != RenderElement::VisibleInViewport)
196         return false;
197     return true;
198 }
199
200 bool MediaElementSession::dataLoadingPermitted(const HTMLMediaElement&) const
201 {
202     if (m_restrictions & OverrideUserGestureRequirementForMainContent && updateIsMainContent())
203         return true;
204
205     if (m_restrictions & RequireUserGestureForLoad && !ScriptController::processingUserGestureForMedia()) {
206         LOG(Media, "MediaElementSession::dataLoadingPermitted - returning FALSE");
207         return false;
208     }
209
210     return true;
211 }
212
213 bool MediaElementSession::fullscreenPermitted(const HTMLMediaElement&) const
214 {
215     if (m_restrictions & RequireUserGestureForFullscreen && !ScriptController::processingUserGestureForMedia()) {
216         LOG(Media, "MediaElementSession::fullscreenPermitted - returning FALSE");
217         return false;
218     }
219
220     return true;
221 }
222
223 bool MediaElementSession::pageAllowsDataLoading(const HTMLMediaElement& element) const
224 {
225     Page* page = element.document().page();
226     if (m_restrictions & RequirePageConsentToLoadMedia && page && !page->canStartMedia()) {
227         LOG(Media, "MediaElementSession::pageAllowsDataLoading - returning FALSE");
228         return false;
229     }
230
231     return true;
232 }
233
234 bool MediaElementSession::pageAllowsPlaybackAfterResuming(const HTMLMediaElement& element) const
235 {
236     Page* page = element.document().page();
237     if (m_restrictions & RequirePageConsentToResumeMedia && page && !page->canStartMedia()) {
238         LOG(Media, "MediaElementSession::pageAllowsPlaybackAfterResuming - returning FALSE");
239         return false;
240     }
241
242     return true;
243 }
244
245 bool MediaElementSession::canShowControlsManager(PlaybackControlsPurpose purpose) const
246 {
247     if (m_element.isFullscreen()) {
248         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is fullscreen");
249         return true;
250     }
251
252     if (m_element.muted()) {
253         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Muted");
254         return false;
255     }
256
257     if (m_element.document().isMediaDocument()) {
258         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is media document");
259         return true;
260     }
261
262     if (client().presentationType() == Audio) {
263         if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || ScriptController::processingUserGestureForMedia()) {
264             LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Audio element with user gesture");
265             return true;
266         }
267
268         if (m_element.isPlaying() && allowsPlaybackControlsForAutoplayingAudio()) {
269             LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: User has played media before");
270             return true;
271         }
272
273         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Audio element is not suitable");
274         return false;
275     }
276
277     if (purpose == PlaybackControlsPurpose::ControlsManager && !isElementRectMostlyInMainFrame(m_element)) {
278         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Not in main frame");
279         return false;
280     }
281
282     if (!m_element.hasAudio() && !m_element.hasEverHadAudio()) {
283         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No audio");
284         return false;
285     }
286
287     if (m_element.document().activeDOMObjectsAreSuspended()) {
288         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: activeDOMObjectsAreSuspended()");
289         return false;
290     }
291
292     if (!playbackPermitted(m_element)) {
293         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Playback not permitted");
294         return false;
295     }
296
297     if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || ScriptController::processingUserGestureForMedia()) {
298         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: No user gesture required");
299         return true;
300     }
301
302     if (purpose == PlaybackControlsPurpose::ControlsManager && hasBehaviorRestriction(RequirePlaybackToControlControlsManager) && !m_element.isPlaying()) {
303         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Needs to be playing");
304         return false;
305     }
306
307     if (!m_element.hasEverNotifiedAboutPlaying()) {
308         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Hasn't fired playing notification");
309         return false;
310     }
311
312     // Only allow the main content heuristic to forbid videos from showing up if our purpose is the controls manager.
313     if (purpose == PlaybackControlsPurpose::ControlsManager && m_element.isVideo()) {
314         if (!m_element.renderer()) {
315             LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No renderer");
316             return false;
317         }
318
319         if (!m_element.hasVideo() && !m_element.hasEverHadVideo()) {
320             LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No video");
321             return false;
322         }
323
324         if (isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls)) {
325             LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is main content");
326             return true;
327         }
328     }
329
330     if (purpose == PlaybackControlsPurpose::NowPlaying) {
331         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Potentially plays audio");
332         return true;
333     }
334
335     LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No user gesture");
336     return false;
337 }
338
339 bool MediaElementSession::isLargeEnoughForMainContent(MediaSessionMainContentPurpose purpose) const
340 {
341     return isElementLargeEnoughForMainContent(m_element, purpose);
342 }
343
344 double MediaElementSession::mostRecentUserInteractionTime() const
345 {
346     return m_mostRecentUserInteractionTime;
347 }
348
349 bool MediaElementSession::wantsToObserveViewportVisibilityForMediaControls() const
350 {
351     return isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls);
352 }
353
354 bool MediaElementSession::wantsToObserveViewportVisibilityForAutoplay() const
355 {
356     return hasBehaviorRestriction(InvisibleAutoplayNotPermitted) || hasBehaviorRestriction(OverrideUserGestureRequirementForMainContent);
357 }
358
359 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
360 void MediaElementSession::showPlaybackTargetPicker(const HTMLMediaElement& element)
361 {
362     LOG(Media, "MediaElementSession::showPlaybackTargetPicker");
363
364     if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !ScriptController::processingUserGestureForMedia()) {
365         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because of permissions");
366         return;
367     }
368
369     if (!element.document().page()) {
370         LOG(Media, "MediaElementSession::showingPlaybackTargetPickerPermitted - returning early because page is NULL");
371         return;
372     }
373
374 #if !PLATFORM(IOS)
375     if (element.readyState() < HTMLMediaElementEnums::HAVE_METADATA) {
376         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because element is not playable");
377         return;
378     }
379 #endif
380
381     element.document().showPlaybackTargetPicker(*this, is<HTMLVideoElement>(element));
382 }
383
384 bool MediaElementSession::hasWirelessPlaybackTargets(const HTMLMediaElement&) const
385 {
386 #if PLATFORM(IOS)
387     // FIXME: consolidate Mac and iOS implementations
388     m_hasPlaybackTargets = PlatformMediaSessionManager::sharedManager().hasWirelessTargetsAvailable();
389 #endif
390
391     LOG(Media, "MediaElementSession::hasWirelessPlaybackTargets - returning %s", m_hasPlaybackTargets ? "TRUE" : "FALSE");
392
393     return m_hasPlaybackTargets;
394 }
395
396 bool MediaElementSession::wirelessVideoPlaybackDisabled(const HTMLMediaElement& element) const
397 {
398     Settings* settings = element.document().settings();
399     if (!settings || !settings->allowsAirPlayForMediaPlayback()) {
400         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of settings");
401         return true;
402     }
403
404     if (element.hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)) {
405         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of attribute");
406         return true;
407     }
408
409 #if PLATFORM(IOS)
410     String legacyAirplayAttributeValue = element.attributeWithoutSynchronization(HTMLNames::webkitairplayAttr);
411     if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "deny")) {
412         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of legacy attribute");
413         return true;
414     }
415     if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "allow")) {
416         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning FALSE because of legacy attribute");
417         return false;
418     }
419 #endif
420
421     MediaPlayer* player = element.player();
422     if (!player)
423         return true;
424
425     bool disabled = player->wirelessVideoPlaybackDisabled();
426     LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning %s because media engine says so", disabled ? "TRUE" : "FALSE");
427     
428     return disabled;
429 }
430
431 void MediaElementSession::setWirelessVideoPlaybackDisabled(const HTMLMediaElement& element, bool disabled)
432 {
433     if (disabled)
434         addBehaviorRestriction(WirelessVideoPlaybackDisabled);
435     else
436         removeBehaviorRestriction(WirelessVideoPlaybackDisabled);
437
438     MediaPlayer* player = element.player();
439     if (!player)
440         return;
441
442     LOG(Media, "MediaElementSession::setWirelessVideoPlaybackDisabled - disabled %s", disabled ? "TRUE" : "FALSE");
443     player->setWirelessVideoPlaybackDisabled(disabled);
444 }
445
446 void MediaElementSession::setHasPlaybackTargetAvailabilityListeners(const HTMLMediaElement& element, bool hasListeners)
447 {
448     LOG(Media, "MediaElementSession::setHasPlaybackTargetAvailabilityListeners - hasListeners %s", hasListeners ? "TRUE" : "FALSE");
449
450 #if PLATFORM(IOS)
451     UNUSED_PARAM(element);
452     m_hasPlaybackTargetAvailabilityListeners = hasListeners;
453     PlatformMediaSessionManager::sharedManager().configureWireLessTargetMonitoring();
454 #else
455     UNUSED_PARAM(hasListeners);
456     element.document().playbackTargetPickerClientStateDidChange(*this, element.mediaState());
457 #endif
458 }
459
460 void MediaElementSession::setPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
461 {
462     m_playbackTarget = WTFMove(device);
463     client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
464 }
465
466 void MediaElementSession::targetAvailabilityChangedTimerFired()
467 {
468     client().wirelessRoutesAvailableDidChange();
469 }
470
471 void MediaElementSession::externalOutputDeviceAvailableDidChange(bool hasTargets)
472 {
473     if (m_hasPlaybackTargets == hasTargets)
474         return;
475
476     LOG(Media, "MediaElementSession::externalOutputDeviceAvailableDidChange(%p) - hasTargets %s", this, hasTargets ? "TRUE" : "FALSE");
477
478     m_hasPlaybackTargets = hasTargets;
479     m_targetAvailabilityChangedTimer.startOneShot(0);
480 }
481
482 bool MediaElementSession::canPlayToWirelessPlaybackTarget() const
483 {
484 #if !PLATFORM(IOS)
485     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
486         return false;
487 #endif
488
489     return client().canPlayToWirelessPlaybackTarget();
490 }
491
492 bool MediaElementSession::isPlayingToWirelessPlaybackTarget() const
493 {
494 #if !PLATFORM(IOS)
495     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
496         return false;
497 #endif
498
499     return client().isPlayingToWirelessPlaybackTarget();
500 }
501
502 void MediaElementSession::setShouldPlayToPlaybackTarget(bool shouldPlay)
503 {
504     LOG(Media, "MediaElementSession::setShouldPlayToPlaybackTarget - shouldPlay %s", shouldPlay ? "TRUE" : "FALSE");
505     m_shouldPlayToPlaybackTarget = shouldPlay;
506     client().setShouldPlayToPlaybackTarget(shouldPlay);
507 }
508
509 void MediaElementSession::mediaStateDidChange(const HTMLMediaElement& element, MediaProducer::MediaStateFlags state)
510 {
511     element.document().playbackTargetPickerClientStateDidChange(*this, state);
512 }
513 #endif
514
515 MediaPlayer::Preload MediaElementSession::effectivePreloadForElement(const HTMLMediaElement& element) const
516 {
517     MediaPlayer::Preload preload = element.preloadValue();
518
519     if (pageExplicitlyAllowsElementToAutoplayInline(element))
520         return preload;
521
522     if (m_restrictions & MetadataPreloadingNotPermitted)
523         return MediaPlayer::None;
524
525     if (m_restrictions & AutoPreloadingNotPermitted) {
526         if (preload > MediaPlayer::MetaData)
527             return MediaPlayer::MetaData;
528     }
529
530     return preload;
531 }
532
533 bool MediaElementSession::requiresFullscreenForVideoPlayback(const HTMLMediaElement& element) const
534 {
535     if (pageExplicitlyAllowsElementToAutoplayInline(element))
536         return false;
537
538     if (is<HTMLAudioElement>(element))
539         return false;
540
541     Settings* settings = element.document().settings();
542     if (!settings || !settings->allowsInlineMediaPlayback())
543         return true;
544
545     if (!settings->inlineMediaPlaybackRequiresPlaysInlineAttribute())
546         return false;
547
548 #if PLATFORM(IOS)
549     if (IOSApplication::isIBooks())
550         return !element.hasAttributeWithoutSynchronization(HTMLNames::webkit_playsinlineAttr) && !element.hasAttributeWithoutSynchronization(HTMLNames::playsinlineAttr);
551     if (dyld_get_program_sdk_version() < DYLD_IOS_VERSION_10_0)
552         return !element.hasAttributeWithoutSynchronization(HTMLNames::webkit_playsinlineAttr);
553 #endif
554     return !element.hasAttributeWithoutSynchronization(HTMLNames::playsinlineAttr);
555 }
556
557 bool MediaElementSession::allowsAutomaticMediaDataLoading(const HTMLMediaElement& element) const
558 {
559     if (pageExplicitlyAllowsElementToAutoplayInline(element))
560         return true;
561
562     Settings* settings = element.document().settings();
563     if (settings && settings->mediaDataLoadsAutomatically())
564         return true;
565
566     return false;
567 }
568
569 void MediaElementSession::mediaEngineUpdated(const HTMLMediaElement& element)
570 {
571     LOG(Media, "MediaElementSession::mediaEngineUpdated");
572
573 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
574     if (m_restrictions & WirelessVideoPlaybackDisabled)
575         setWirelessVideoPlaybackDisabled(element, true);
576     if (m_playbackTarget)
577         client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
578     if (m_shouldPlayToPlaybackTarget)
579         client().setShouldPlayToPlaybackTarget(true);
580 #else
581     UNUSED_PARAM(element);
582 #endif
583     
584 }
585
586 void MediaElementSession::resetPlaybackSessionState()
587 {
588     m_mostRecentUserInteractionTime = 0;
589     addBehaviorRestriction(RequireUserGestureToControlControlsManager | RequirePlaybackToControlControlsManager);
590 }
591
592 bool MediaElementSession::allowsPictureInPicture(const HTMLMediaElement& element) const
593 {
594     Settings* settings = element.document().settings();
595     return settings && settings->allowsPictureInPictureMediaPlayback() && !element.webkitCurrentPlaybackTargetIsWireless();
596 }
597
598 #if PLATFORM(IOS)
599 bool MediaElementSession::requiresPlaybackTargetRouteMonitoring() const
600 {
601     return m_hasPlaybackTargetAvailabilityListeners && !client().elementIsHidden();
602 }
603 #endif
604
605 #if ENABLE(MEDIA_SOURCE)
606 const unsigned fiveMinutesOf1080PVideo = 290 * 1024 * 1024; // 290 MB is approximately 5 minutes of 8Mbps (1080p) content.
607 const unsigned fiveMinutesStereoAudio = 14 * 1024 * 1024; // 14 MB is approximately 5 minutes of 384kbps content.
608
609 size_t MediaElementSession::maximumMediaSourceBufferSize(const SourceBuffer& buffer) const
610 {
611     // A good quality 1080p video uses 8,000 kbps and stereo audio uses 384 kbps, so assume 95% for video and 5% for audio.
612     const float bufferBudgetPercentageForVideo = .95;
613     const float bufferBudgetPercentageForAudio = .05;
614
615     size_t maximum;
616     Settings* settings = buffer.document().settings();
617     if (settings)
618         maximum = settings->maximumSourceBufferSize();
619     else
620         maximum = fiveMinutesOf1080PVideo + fiveMinutesStereoAudio;
621
622     // Allow a SourceBuffer to buffer as though it is audio-only even if it doesn't have any active tracks (yet).
623     size_t bufferSize = static_cast<size_t>(maximum * bufferBudgetPercentageForAudio);
624     if (buffer.hasVideo())
625         bufferSize += static_cast<size_t>(maximum * bufferBudgetPercentageForVideo);
626
627     // FIXME: we might want to modify this algorithm to:
628     // - decrease the maximum size for background tabs
629     // - decrease the maximum size allowed for inactive elements when a process has more than one
630     //   element, eg. so a page with many elements which are played one at a time doesn't keep
631     //   everything buffered after an element has finished playing.
632
633     return bufferSize;
634 }
635 #endif
636
637 static bool isMainContentForPurposesOfAutoplay(const HTMLMediaElement& element)
638 {
639     if (!element.hasAudio() || !element.hasVideo())
640         return false;
641
642     // Elements which have not yet been laid out, or which are not yet in the DOM, cannot be main content.
643     auto* renderer = element.renderer();
644     if (!renderer)
645         return false;
646
647     if (!isElementLargeEnoughForMainContent(element, MediaSessionMainContentPurpose::Autoplay))
648         return false;
649
650     // Elements which are hidden by style, or have been scrolled out of view, cannot be main content.
651     // But elements which have audio & video and are already playing should not stop playing because
652     // they are scrolled off the page.
653     if (renderer->style().visibility() != VISIBLE)
654         return false;
655     if (renderer->visibleInViewportState() != RenderElement::VisibleInViewport && !element.isPlaying())
656         return false;
657
658     // Main content elements must be in the main frame.
659     Document& document = element.document();
660     if (!document.frame() || !document.frame()->isMainFrame())
661         return false;
662
663     MainFrame& mainFrame = document.frame()->mainFrame();
664     if (!mainFrame.view() || !mainFrame.view()->renderView())
665         return false;
666
667     RenderView& mainRenderView = *mainFrame.view()->renderView();
668
669     // Hit test the area of the main frame where the element appears, to determine if the element is being obscured.
670     IntRect rectRelativeToView = element.clientRect();
671     ScrollPosition scrollPosition = mainFrame.view()->documentScrollPositionRelativeToViewOrigin();
672     IntRect rectRelativeToTopDocument(rectRelativeToView.location() + scrollPosition, rectRelativeToView.size());
673     HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent | HitTestRequest::IgnoreClipping | HitTestRequest::DisallowUserAgentShadowContent);
674     HitTestResult result(rectRelativeToTopDocument.center());
675
676     // Elements which are obscured by other elements cannot be main content.
677     mainRenderView.hitTest(request, result);
678     result.setToNonUserAgentShadowAncestor();
679     Element* hitElement = result.innerElement();
680     if (hitElement != &element)
681         return false;
682
683     return true;
684 }
685
686 static bool isElementRectMostlyInMainFrame(const HTMLMediaElement& element)
687 {
688     if (!element.renderer())
689         return false;
690
691     auto* documentFrame = element.document().frame();
692     if (!documentFrame)
693         return false;
694
695     auto mainFrameView = documentFrame->mainFrame().view();
696     if (!mainFrameView)
697         return false;
698
699     IntRect mainFrameRectAdjustedForScrollPosition = IntRect(-mainFrameView->documentScrollPositionRelativeToViewOrigin(), mainFrameView->contentsSize());
700     IntRect elementRectInMainFrame = element.clientRect();
701     unsigned totalElementArea = elementRectInMainFrame.area().unsafeGet();
702     elementRectInMainFrame.intersect(mainFrameRectAdjustedForScrollPosition);
703
704     return elementRectInMainFrame.area().unsafeGet() > totalElementArea / 2;
705 }
706
707 static bool isElementLargeRelativeToMainFrame(const HTMLMediaElement& element)
708 {
709     static const double minimumPercentageOfMainFrameAreaForMainContent = 0.9;
710     auto* renderer = element.renderer();
711     if (!renderer)
712         return false;
713
714     auto* documentFrame = element.document().frame();
715     if (!documentFrame)
716         return false;
717
718     if (!documentFrame->mainFrame().view())
719         return false;
720
721     auto& mainFrameView = *documentFrame->mainFrame().view();
722     auto maxVisibleClientWidth = std::min(renderer->clientWidth().toInt(), mainFrameView.visibleWidth());
723     auto maxVisibleClientHeight = std::min(renderer->clientHeight().toInt(), mainFrameView.visibleHeight());
724
725     return maxVisibleClientWidth * maxVisibleClientHeight > minimumPercentageOfMainFrameAreaForMainContent * mainFrameView.visibleWidth() * mainFrameView.visibleHeight();
726 }
727
728 static bool isElementLargeEnoughForMainContent(const HTMLMediaElement& element, MediaSessionMainContentPurpose purpose)
729 {
730     static const double elementMainContentAreaMinimum = 400 * 300;
731     static const double maximumAspectRatio = purpose == MediaSessionMainContentPurpose::MediaControls ? 3 : 1.8;
732     static const double minimumAspectRatio = .5; // Slightly smaller than 9:16.
733
734     // Elements which have not yet been laid out, or which are not yet in the DOM, cannot be main content.
735     auto* renderer = element.renderer();
736     if (!renderer)
737         return false;
738
739     double width = renderer->clientWidth();
740     double height = renderer->clientHeight();
741     double area = width * height;
742     double aspectRatio = width / height;
743
744     if (area < elementMainContentAreaMinimum)
745         return false;
746
747     if (aspectRatio >= minimumAspectRatio && aspectRatio <= maximumAspectRatio)
748         return true;
749
750     return isElementLargeRelativeToMainFrame(element);
751 }
752
753 void MediaElementSession::mainContentCheckTimerFired()
754 {
755     if (!hasBehaviorRestriction(OverrideUserGestureRequirementForMainContent))
756         return;
757
758     updateIsMainContent();
759 }
760
761 bool MediaElementSession::updateIsMainContent() const
762 {
763     bool wasMainContent = m_isMainContent;
764     m_isMainContent = isMainContentForPurposesOfAutoplay(m_element);
765
766     if (m_isMainContent != wasMainContent)
767         m_element.updateShouldPlay();
768
769     return m_isMainContent;
770 }
771
772 bool MediaElementSession::allowsNowPlayingControlsVisibility() const
773 {
774     auto page = m_element.document().page();
775     return page && !page->isVisibleAndActive();
776 }
777
778 bool MediaElementSession::allowsPlaybackControlsForAutoplayingAudio() const
779 {
780     auto page = m_element.document().page();
781     return page && page->allowsPlaybackControlsForAutoplayingAudio();
782 }
783
784 }
785
786 #endif // ENABLE(VIDEO)