Replace WTF::move with WTFMove
[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     CASE(InvisibleAutoplayNotPermitted);
77
78     return restrictionBuilder.toString();
79 }
80 #endif
81
82 static bool pageExplicitlyAllowsElementToAutoplayInline(const HTMLMediaElement& element)
83 {
84     Document& document = element.document();
85     Page* page = document.page();
86     return document.isMediaDocument() && !document.ownerElement() && page && page->allowsMediaDocumentInlinePlayback();
87 }
88
89 MediaElementSession::MediaElementSession(PlatformMediaSessionClient& client)
90     : PlatformMediaSession(client)
91     , m_restrictions(NoRestrictions)
92 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
93     , m_targetAvailabilityChangedTimer(*this, &MediaElementSession::targetAvailabilityChangedTimerFired)
94 #endif
95 {
96 }
97
98 void MediaElementSession::registerWithDocument(Document& document)
99 {
100 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
101     document.addPlaybackTargetPickerClient(*this);
102 #else
103     UNUSED_PARAM(document);
104 #endif
105 }
106
107 void MediaElementSession::unregisterWithDocument(Document& document)
108 {
109 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
110     document.removePlaybackTargetPickerClient(*this);
111 #else
112     UNUSED_PARAM(document);
113 #endif
114 }
115
116 void MediaElementSession::addBehaviorRestriction(BehaviorRestrictions restriction)
117 {
118     LOG(Media, "MediaElementSession::addBehaviorRestriction - adding %s", restrictionName(restriction).utf8().data());
119     m_restrictions |= restriction;
120 }
121
122 void MediaElementSession::removeBehaviorRestriction(BehaviorRestrictions restriction)
123 {
124     LOG(Media, "MediaElementSession::removeBehaviorRestriction - removing %s", restrictionName(restriction).utf8().data());
125     m_restrictions &= ~restriction;
126 }
127
128 bool MediaElementSession::playbackPermitted(const HTMLMediaElement& element) const
129 {
130     if (pageExplicitlyAllowsElementToAutoplayInline(element))
131         return true;
132
133     if (m_restrictions & RequireUserGestureForRateChange && !ScriptController::processingUserGestureForMedia()) {
134         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE");
135         return false;
136     }
137
138     if (m_restrictions & RequireUserGestureForAudioRateChange && element.hasAudio() && !ScriptController::processingUserGestureForMedia()) {
139         LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE");
140         return false;
141     }
142
143     return true;
144 }
145
146 bool MediaElementSession::dataLoadingPermitted(const HTMLMediaElement&) const
147 {
148     if (m_restrictions & RequireUserGestureForLoad && !ScriptController::processingUserGestureForMedia()) {
149         LOG(Media, "MediaElementSession::dataLoadingPermitted - returning FALSE");
150         return false;
151     }
152
153     return true;
154 }
155
156 bool MediaElementSession::fullscreenPermitted(const HTMLMediaElement&) const
157 {
158     if (m_restrictions & RequireUserGestureForFullscreen && !ScriptController::processingUserGestureForMedia()) {
159         LOG(Media, "MediaElementSession::fullscreenPermitted - returning FALSE");
160         return false;
161     }
162
163     return true;
164 }
165
166 bool MediaElementSession::pageAllowsDataLoading(const HTMLMediaElement& element) const
167 {
168     Page* page = element.document().page();
169     if (m_restrictions & RequirePageConsentToLoadMedia && page && !page->canStartMedia()) {
170         LOG(Media, "MediaElementSession::pageAllowsDataLoading - returning FALSE");
171         return false;
172     }
173
174     return true;
175 }
176
177 bool MediaElementSession::pageAllowsPlaybackAfterResuming(const HTMLMediaElement& element) const
178 {
179     Page* page = element.document().page();
180     if (m_restrictions & RequirePageConsentToResumeMedia && page && !page->canStartMedia()) {
181         LOG(Media, "MediaElementSession::pageAllowsPlaybackAfterResuming - returning FALSE");
182         return false;
183     }
184
185     return true;
186 }
187
188 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
189 void MediaElementSession::showPlaybackTargetPicker(const HTMLMediaElement& element)
190 {
191     LOG(Media, "MediaElementSession::showPlaybackTargetPicker");
192
193     if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !ScriptController::processingUserGestureForMedia()) {
194         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because of permissions");
195         return;
196     }
197
198     if (!element.document().page()) {
199         LOG(Media, "MediaElementSession::showingPlaybackTargetPickerPermitted - returning early because page is NULL");
200         return;
201     }
202
203 #if !PLATFORM(IOS)
204     if (element.readyState() < HTMLMediaElementEnums::HAVE_METADATA) {
205         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because element is not playable");
206         return;
207     }
208 #endif
209
210     element.document().showPlaybackTargetPicker(*this, is<HTMLVideoElement>(element));
211 }
212
213 bool MediaElementSession::hasWirelessPlaybackTargets(const HTMLMediaElement&) const
214 {
215 #if PLATFORM(IOS)
216     // FIXME: consolidate Mac and iOS implementations
217     m_hasPlaybackTargets = PlatformMediaSessionManager::sharedManager().hasWirelessTargetsAvailable();
218 #endif
219
220     LOG(Media, "MediaElementSession::hasWirelessPlaybackTargets - returning %s", m_hasPlaybackTargets ? "TRUE" : "FALSE");
221
222     return m_hasPlaybackTargets;
223 }
224
225 bool MediaElementSession::wirelessVideoPlaybackDisabled(const HTMLMediaElement& element) const
226 {
227     Settings* settings = element.document().settings();
228     if (!settings || !settings->allowsAirPlayForMediaPlayback()) {
229         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of settings");
230         return true;
231     }
232
233     if (element.fastHasAttribute(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)) {
234         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of attribute");
235         return true;
236     }
237
238 #if PLATFORM(IOS)
239     String legacyAirplayAttributeValue = element.fastGetAttribute(HTMLNames::webkitairplayAttr);
240     if (equalIgnoringCase(legacyAirplayAttributeValue, "deny")) {
241         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of legacy attribute");
242         return true;
243     }
244     if (equalIgnoringCase(legacyAirplayAttributeValue, "allow")) {
245         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning FALSE because of legacy attribute");
246         return false;
247     }
248 #endif
249
250     MediaPlayer* player = element.player();
251     if (!player)
252         return true;
253
254     bool disabled = player->wirelessVideoPlaybackDisabled();
255     LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning %s because media engine says so", disabled ? "TRUE" : "FALSE");
256     
257     return disabled;
258 }
259
260 void MediaElementSession::setWirelessVideoPlaybackDisabled(const HTMLMediaElement& element, bool disabled)
261 {
262     if (disabled)
263         addBehaviorRestriction(WirelessVideoPlaybackDisabled);
264     else
265         removeBehaviorRestriction(WirelessVideoPlaybackDisabled);
266
267     MediaPlayer* player = element.player();
268     if (!player)
269         return;
270
271     LOG(Media, "MediaElementSession::setWirelessVideoPlaybackDisabled - disabled %s", disabled ? "TRUE" : "FALSE");
272     player->setWirelessVideoPlaybackDisabled(disabled);
273 }
274
275 void MediaElementSession::setHasPlaybackTargetAvailabilityListeners(const HTMLMediaElement& element, bool hasListeners)
276 {
277     LOG(Media, "MediaElementSession::setHasPlaybackTargetAvailabilityListeners - hasListeners %s", hasListeners ? "TRUE" : "FALSE");
278
279 #if PLATFORM(IOS)
280     UNUSED_PARAM(element);
281     m_hasPlaybackTargetAvailabilityListeners = hasListeners;
282     PlatformMediaSessionManager::sharedManager().configureWireLessTargetMonitoring();
283 #else
284     UNUSED_PARAM(hasListeners);
285     element.document().playbackTargetPickerClientStateDidChange(*this, element.mediaState());
286 #endif
287 }
288
289 void MediaElementSession::setPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
290 {
291     m_playbackTarget = WTFMove(device);
292     client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
293 }
294
295 void MediaElementSession::targetAvailabilityChangedTimerFired()
296 {
297     client().wirelessRoutesAvailableDidChange();
298 }
299
300 void MediaElementSession::externalOutputDeviceAvailableDidChange(bool hasTargets)
301 {
302     if (m_hasPlaybackTargets == hasTargets)
303         return;
304
305     LOG(Media, "MediaElementSession::externalOutputDeviceAvailableDidChange(%p) - hasTargets %s", this, hasTargets ? "TRUE" : "FALSE");
306
307     m_hasPlaybackTargets = hasTargets;
308     m_targetAvailabilityChangedTimer.startOneShot(0);
309 }
310
311 bool MediaElementSession::canPlayToWirelessPlaybackTarget() const
312 {
313     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
314         return false;
315
316     return client().canPlayToWirelessPlaybackTarget();
317 }
318
319 bool MediaElementSession::isPlayingToWirelessPlaybackTarget() const
320 {
321     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
322         return false;
323
324     return client().isPlayingToWirelessPlaybackTarget();
325 }
326
327 void MediaElementSession::setShouldPlayToPlaybackTarget(bool shouldPlay)
328 {
329     LOG(Media, "MediaElementSession::setShouldPlayToPlaybackTarget - shouldPlay %s", shouldPlay ? "TRUE" : "FALSE");
330     m_shouldPlayToPlaybackTarget = shouldPlay;
331     client().setShouldPlayToPlaybackTarget(shouldPlay);
332 }
333
334 void MediaElementSession::mediaStateDidChange(const HTMLMediaElement& element, MediaProducer::MediaStateFlags state)
335 {
336     element.document().playbackTargetPickerClientStateDidChange(*this, state);
337 }
338 #endif
339
340 MediaPlayer::Preload MediaElementSession::effectivePreloadForElement(const HTMLMediaElement& element) const
341 {
342     MediaPlayer::Preload preload = element.preloadValue();
343
344     if (pageExplicitlyAllowsElementToAutoplayInline(element))
345         return preload;
346
347     if (m_restrictions & MetadataPreloadingNotPermitted)
348         return MediaPlayer::None;
349
350     if (m_restrictions & 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)