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