Adopt the new version of AVOutputDeviceMenuController's showMenuForRect method.
[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 "HTMLMediaElement.h"
38 #include "HTMLMediaElementEnums.h"
39 #include "HTMLNames.h"
40 #include "HTMLVideoElement.h"
41 #include "Logging.h"
42 #include "Page.h"
43 #include "PlatformMediaSessionManager.h"
44 #include "ScriptController.h"
45 #include "SourceBuffer.h"
46
47 #if PLATFORM(IOS)
48 #include "AudioSession.h"
49 #include "RuntimeApplicationChecksIOS.h"
50 #endif
51
52 namespace WebCore {
53
54 #if !LOG_DISABLED
55 static String restrictionName(MediaElementSession::BehaviorRestrictions restriction)
56 {
57     StringBuilder restrictionBuilder;
58 #define CASE(restrictionType) \
59     if (restriction & MediaElementSession::restrictionType) { \
60         if (!restrictionBuilder.isEmpty()) \
61             restrictionBuilder.append(", "); \
62         restrictionBuilder.append(#restrictionType); \
63     } \
64
65     CASE(NoRestrictions);
66     CASE(RequireUserGestureForLoad);
67     CASE(RequireUserGestureForRateChange);
68     CASE(RequireUserGestureForAudioRateChange);
69     CASE(RequireUserGestureForFullscreen);
70     CASE(RequirePageConsentToLoadMedia);
71     CASE(RequirePageConsentToResumeMedia);
72 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
73     CASE(RequireUserGestureToShowPlaybackTargetPicker);
74     CASE(WirelessVideoPlaybackDisabled);
75 #endif
76     CASE(RequireUserGestureForAudioRateChange);
77     CASE(InvisibleAutoplayNotPermitted);
78
79     return restrictionBuilder.toString();
80 }
81 #endif
82
83 static bool pageExplicitlyAllowsElementToAutoplayInline(const HTMLMediaElement& element)
84 {
85     Document& document = element.document();
86     Page* page = document.page();
87     return document.isMediaDocument() && !document.ownerElement() && page && page->allowsMediaDocumentInlinePlayback();
88 }
89
90 MediaElementSession::MediaElementSession(PlatformMediaSessionClient& client)
91     : PlatformMediaSession(client)
92     , m_restrictions(NoRestrictions)
93 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
94     , m_targetAvailabilityChangedTimer(*this, &MediaElementSession::targetAvailabilityChangedTimerFired)
95 #endif
96 {
97 }
98
99 void MediaElementSession::registerWithDocument(Document& document)
100 {
101 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
102     document.addPlaybackTargetPickerClient(*this);
103 #else
104     UNUSED_PARAM(document);
105 #endif
106 }
107
108 void MediaElementSession::unregisterWithDocument(Document& document)
109 {
110 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
111     document.removePlaybackTargetPickerClient(*this);
112 #else
113     UNUSED_PARAM(document);
114 #endif
115 }
116
117 void MediaElementSession::addBehaviorRestriction(BehaviorRestrictions restriction)
118 {
119     LOG(Media, "MediaElementSession::addBehaviorRestriction - adding %s", restrictionName(restriction).utf8().data());
120     m_restrictions |= restriction;
121 }
122
123 void MediaElementSession::removeBehaviorRestriction(BehaviorRestrictions restriction)
124 {
125     LOG(Media, "MediaElementSession::removeBehaviorRestriction - removing %s", restrictionName(restriction).utf8().data());
126     m_restrictions &= ~restriction;
127 }
128
129 bool MediaElementSession::playbackPermitted(const HTMLMediaElement& element) const
130 {
131     if (pageExplicitlyAllowsElementToAutoplayInline(element))
132         return true;
133
134     if (m_restrictions & RequireUserGestureForRateChange && !ScriptController::processingUserGestureForMedia()) {
135         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE");
136         return false;
137     }
138
139     if (m_restrictions & RequireUserGestureForAudioRateChange && element.hasAudio() && !ScriptController::processingUserGestureForMedia()) {
140         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE");
141         return false;
142     }
143
144     return true;
145 }
146
147 bool MediaElementSession::dataLoadingPermitted(const HTMLMediaElement&) const
148 {
149     if (m_restrictions & RequireUserGestureForLoad && !ScriptController::processingUserGestureForMedia()) {
150         LOG(Media, "MediaElementSession::dataLoadingPermitted - returning FALSE");
151         return false;
152     }
153
154     return true;
155 }
156
157 bool MediaElementSession::fullscreenPermitted(const HTMLMediaElement&) const
158 {
159     if (m_restrictions & RequireUserGestureForFullscreen && !ScriptController::processingUserGestureForMedia()) {
160         LOG(Media, "MediaElementSession::fullscreenPermitted - returning FALSE");
161         return false;
162     }
163
164     return true;
165 }
166
167 bool MediaElementSession::pageAllowsDataLoading(const HTMLMediaElement& element) const
168 {
169     Page* page = element.document().page();
170     if (m_restrictions & RequirePageConsentToLoadMedia && page && !page->canStartMedia()) {
171         LOG(Media, "MediaElementSession::pageAllowsDataLoading - returning FALSE");
172         return false;
173     }
174
175     return true;
176 }
177
178 bool MediaElementSession::pageAllowsPlaybackAfterResuming(const HTMLMediaElement& element) const
179 {
180     Page* page = element.document().page();
181     if (m_restrictions & RequirePageConsentToResumeMedia && page && !page->canStartMedia()) {
182         LOG(Media, "MediaElementSession::pageAllowsPlaybackAfterResuming - returning FALSE");
183         return false;
184     }
185
186     return true;
187 }
188
189 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
190 void MediaElementSession::showPlaybackTargetPicker(const HTMLMediaElement& element)
191 {
192     LOG(Media, "MediaElementSession::showPlaybackTargetPicker");
193
194     if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !ScriptController::processingUserGestureForMedia()) {
195         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because of permissions");
196         return;
197     }
198
199     if (!element.document().page()) {
200         LOG(Media, "MediaElementSession::showingPlaybackTargetPickerPermitted - returning early because page is NULL");
201         return;
202     }
203
204 #if !PLATFORM(IOS)
205     if (element.readyState() < HTMLMediaElementEnums::HAVE_METADATA) {
206         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because element is not playable");
207         return;
208     }
209 #endif
210
211     String customMenuItemTitle = element.playbackTargetPickerCustomActionName();
212     element.document().showPlaybackTargetPicker(*this, is<HTMLVideoElement>(element), customMenuItemTitle);
213 }
214
215 bool MediaElementSession::hasWirelessPlaybackTargets(const HTMLMediaElement&) const
216 {
217 #if PLATFORM(IOS)
218     // FIXME: consolidate Mac and iOS implementations
219     m_hasPlaybackTargets = PlatformMediaSessionManager::sharedManager().hasWirelessTargetsAvailable();
220 #endif
221
222     LOG(Media, "MediaElementSession::hasWirelessPlaybackTargets - returning %s", m_hasPlaybackTargets ? "TRUE" : "FALSE");
223
224     return m_hasPlaybackTargets;
225 }
226
227 bool MediaElementSession::wirelessVideoPlaybackDisabled(const HTMLMediaElement& element) const
228 {
229     Settings* settings = element.document().settings();
230     if (!settings || !settings->allowsAirPlayForMediaPlayback()) {
231         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of settings");
232         return true;
233     }
234
235     if (element.fastHasAttribute(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)) {
236         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of attribute");
237         return true;
238     }
239
240 #if PLATFORM(IOS)
241     String legacyAirplayAttributeValue = element.fastGetAttribute(HTMLNames::webkitairplayAttr);
242     if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "deny")) {
243         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of legacy attribute");
244         return true;
245     }
246     if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "allow")) {
247         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning FALSE because of legacy attribute");
248         return false;
249     }
250 #endif
251
252     MediaPlayer* player = element.player();
253     if (!player)
254         return true;
255
256     bool disabled = player->wirelessVideoPlaybackDisabled();
257     LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning %s because media engine says so", disabled ? "TRUE" : "FALSE");
258     
259     return disabled;
260 }
261
262 void MediaElementSession::setWirelessVideoPlaybackDisabled(const HTMLMediaElement& element, bool disabled)
263 {
264     if (disabled)
265         addBehaviorRestriction(WirelessVideoPlaybackDisabled);
266     else
267         removeBehaviorRestriction(WirelessVideoPlaybackDisabled);
268
269     MediaPlayer* player = element.player();
270     if (!player)
271         return;
272
273     LOG(Media, "MediaElementSession::setWirelessVideoPlaybackDisabled - disabled %s", disabled ? "TRUE" : "FALSE");
274     player->setWirelessVideoPlaybackDisabled(disabled);
275 }
276
277 void MediaElementSession::setHasPlaybackTargetAvailabilityListeners(const HTMLMediaElement& element, bool hasListeners)
278 {
279     LOG(Media, "MediaElementSession::setHasPlaybackTargetAvailabilityListeners - hasListeners %s", hasListeners ? "TRUE" : "FALSE");
280
281 #if PLATFORM(IOS)
282     UNUSED_PARAM(element);
283     m_hasPlaybackTargetAvailabilityListeners = hasListeners;
284     PlatformMediaSessionManager::sharedManager().configureWireLessTargetMonitoring();
285 #else
286     UNUSED_PARAM(hasListeners);
287     element.document().playbackTargetPickerClientStateDidChange(*this, element.mediaState());
288 #endif
289 }
290
291 void MediaElementSession::setPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
292 {
293     m_playbackTarget = WTFMove(device);
294     client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
295 }
296
297 void MediaElementSession::targetAvailabilityChangedTimerFired()
298 {
299     client().wirelessRoutesAvailableDidChange();
300 }
301
302 void MediaElementSession::externalOutputDeviceAvailableDidChange(bool hasTargets)
303 {
304     if (m_hasPlaybackTargets == hasTargets)
305         return;
306
307     LOG(Media, "MediaElementSession::externalOutputDeviceAvailableDidChange(%p) - hasTargets %s", this, hasTargets ? "TRUE" : "FALSE");
308
309     m_hasPlaybackTargets = hasTargets;
310     m_targetAvailabilityChangedTimer.startOneShot(0);
311 }
312
313 bool MediaElementSession::canPlayToWirelessPlaybackTarget() const
314 {
315     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
316         return false;
317
318     return client().canPlayToWirelessPlaybackTarget();
319 }
320
321 bool MediaElementSession::isPlayingToWirelessPlaybackTarget() const
322 {
323     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
324         return false;
325
326     return client().isPlayingToWirelessPlaybackTarget();
327 }
328
329 void MediaElementSession::setShouldPlayToPlaybackTarget(bool shouldPlay)
330 {
331     LOG(Media, "MediaElementSession::setShouldPlayToPlaybackTarget - shouldPlay %s", shouldPlay ? "TRUE" : "FALSE");
332     m_shouldPlayToPlaybackTarget = shouldPlay;
333     client().setShouldPlayToPlaybackTarget(shouldPlay);
334 }
335
336 void MediaElementSession::customPlaybackActionSelected()
337 {
338     client().customPlaybackActionSelected();
339 }
340
341 void MediaElementSession::mediaStateDidChange(const HTMLMediaElement& element, MediaProducer::MediaStateFlags state)
342 {
343     element.document().playbackTargetPickerClientStateDidChange(*this, state);
344 }
345 #endif
346
347 MediaPlayer::Preload MediaElementSession::effectivePreloadForElement(const HTMLMediaElement& element) const
348 {
349     MediaPlayer::Preload preload = element.preloadValue();
350
351     if (pageExplicitlyAllowsElementToAutoplayInline(element))
352         return preload;
353
354     if (m_restrictions & MetadataPreloadingNotPermitted)
355         return MediaPlayer::None;
356
357     if (m_restrictions & AutoPreloadingNotPermitted) {
358         if (preload > MediaPlayer::MetaData)
359             return MediaPlayer::MetaData;
360     }
361
362     return preload;
363 }
364
365 bool MediaElementSession::requiresFullscreenForVideoPlayback(const HTMLMediaElement& element) const
366 {
367     if (pageExplicitlyAllowsElementToAutoplayInline(element))
368         return false;
369
370     Settings* settings = element.document().settings();
371     if (!settings || !settings->allowsInlineMediaPlayback())
372         return true;
373
374     return settings->inlineMediaPlaybackRequiresPlaysInlineAttribute() && !element.fastHasAttribute(HTMLNames::webkit_playsinlineAttr);
375 }
376
377 bool MediaElementSession::allowsAutomaticMediaDataLoading(const HTMLMediaElement& element) const
378 {
379     if (pageExplicitlyAllowsElementToAutoplayInline(element))
380         return true;
381
382     Settings* settings = element.document().settings();
383     if (settings && settings->mediaDataLoadsAutomatically())
384         return true;
385
386     return false;
387 }
388
389 void MediaElementSession::mediaEngineUpdated(const HTMLMediaElement& element)
390 {
391     LOG(Media, "MediaElementSession::mediaEngineUpdated");
392
393 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
394     if (m_restrictions & WirelessVideoPlaybackDisabled)
395         setWirelessVideoPlaybackDisabled(element, true);
396     if (m_playbackTarget)
397         client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
398     if (m_shouldPlayToPlaybackTarget)
399         client().setShouldPlayToPlaybackTarget(true);
400 #else
401     UNUSED_PARAM(element);
402 #endif
403     
404 }
405
406 bool MediaElementSession::allowsPictureInPicture(const HTMLMediaElement& element) const
407 {
408     Settings* settings = element.document().settings();
409     return settings && settings->allowsPictureInPictureMediaPlayback() && !element.webkitCurrentPlaybackTargetIsWireless();
410 }
411
412 #if PLATFORM(IOS)
413 bool MediaElementSession::requiresPlaybackTargetRouteMonitoring() const
414 {
415     return m_hasPlaybackTargetAvailabilityListeners && !client().elementIsHidden();
416 }
417 #endif
418
419 #if ENABLE(MEDIA_SOURCE)
420 const unsigned fiveMinutesOf1080PVideo = 290 * 1024 * 1024; // 290 MB is approximately 5 minutes of 8Mbps (1080p) content.
421 const unsigned fiveMinutesStereoAudio = 14 * 1024 * 1024; // 14 MB is approximately 5 minutes of 384kbps content.
422
423 size_t MediaElementSession::maximumMediaSourceBufferSize(const SourceBuffer& buffer) const
424 {
425     // A good quality 1080p video uses 8,000 kbps and stereo audio uses 384 kbps, so assume 95% for video and 5% for audio.
426     const float bufferBudgetPercentageForVideo = .95;
427     const float bufferBudgetPercentageForAudio = .05;
428
429     size_t maximum;
430     Settings* settings = buffer.document().settings();
431     if (settings)
432         maximum = settings->maximumSourceBufferSize();
433     else
434         maximum = fiveMinutesOf1080PVideo + fiveMinutesStereoAudio;
435
436     // Allow a SourceBuffer to buffer as though it is audio-only even if it doesn't have any active tracks (yet).
437     size_t bufferSize = static_cast<size_t>(maximum * bufferBudgetPercentageForAudio);
438     if (buffer.hasVideo())
439         bufferSize += static_cast<size_t>(maximum * bufferBudgetPercentageForVideo);
440
441     // FIXME: we might want to modify this algorithm to:
442     // - decrease the maximum size for background tabs
443     // - decrease the maximum size allowed for inactive elements when a process has more than one
444     //   element, eg. so a page with many elements which are played one at a time doesn't keep
445     //   everything buffered after an element has finished playing.
446
447     return bufferSize;
448 }
449 #endif
450
451 }
452
453 #endif // ENABLE(VIDEO)