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