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 "HTMLMediaSession.h" 31 32#include "Chrome.h" 33#include "ChromeClient.h" 34#include "Frame.h" 35#include "HTMLMediaElement.h" 36#include "HTMLNames.h" 37#include "Logging.h" 38#include "MediaSessionManager.h" 39#include "Page.h" 40#include "ScriptController.h" 41#include "SourceBuffer.h" 42 43#if PLATFORM(IOS) 44#include "AudioSession.h" 45#include "RuntimeApplicationChecksIOS.h" 46#endif 47 48namespace WebCore { 49 50#if !LOG_DISABLED 51static const char* restrictionName(HTMLMediaSession::BehaviorRestrictions restriction) 52{ 53#define CASE(restriction) case HTMLMediaSession::restriction: return #restriction 54 switch (restriction) { 55 CASE(NoRestrictions); 56 CASE(RequireUserGestureForLoad); 57 CASE(RequireUserGestureForRateChange); 58 CASE(RequireUserGestureForFullscreen); 59 CASE(RequirePageConsentToLoadMedia); 60 CASE(RequirePageConsentToResumeMedia); 61#if ENABLE(IOS_AIRPLAY) 62 CASE(RequireUserGestureToShowPlaybackTargetPicker); 63 CASE(WirelessVideoPlaybackDisabled); 64#endif 65 } 66 67 ASSERT_NOT_REACHED(); 68 return ""; 69} 70#endif 71 72std::unique_ptr<HTMLMediaSession> HTMLMediaSession::create(MediaSessionClient& client) 73{ 74 return std::make_unique<HTMLMediaSession>(client); 75} 76 77HTMLMediaSession::HTMLMediaSession(MediaSessionClient& client) 78 : MediaSession(client) 79 , m_restrictions(NoRestrictions) 80{ 81} 82 83void HTMLMediaSession::addBehaviorRestriction(BehaviorRestrictions restriction) 84{ 85 LOG(Media, "HTMLMediaSession::addBehaviorRestriction - adding %s", restrictionName(restriction)); 86 m_restrictions |= restriction; 87} 88 89void HTMLMediaSession::removeBehaviorRestriction(BehaviorRestrictions restriction) 90{ 91 LOG(Media, "HTMLMediaSession::removeBehaviorRestriction - removing %s", restrictionName(restriction)); 92 m_restrictions &= ~restriction; 93} 94 95bool HTMLMediaSession::playbackPermitted(const HTMLMediaElement&) const 96{ 97 if (m_restrictions & RequireUserGestureForRateChange && !ScriptController::processingUserGesture()) { 98 LOG(Media, "HTMLMediaSession::playbackPermitted - returning FALSE"); 99 return false; 100 } 101 102 return true; 103} 104 105bool HTMLMediaSession::dataLoadingPermitted(const HTMLMediaElement&) const 106{ 107 if (m_restrictions & RequireUserGestureForLoad && !ScriptController::processingUserGesture()) { 108 LOG(Media, "HTMLMediaSession::dataLoadingPermitted - returning FALSE"); 109 return false; 110 } 111 112 return true; 113} 114 115bool HTMLMediaSession::fullscreenPermitted(const HTMLMediaElement&) const 116{ 117 if (m_restrictions & RequireUserGestureForFullscreen && !ScriptController::processingUserGesture()) { 118 LOG(Media, "HTMLMediaSession::fullscreenPermitted - returning FALSE"); 119 return false; 120 } 121 122 return true; 123} 124 125bool HTMLMediaSession::pageAllowsDataLoading(const HTMLMediaElement& element) const 126{ 127 Page* page = element.document().page(); 128 if (m_restrictions & RequirePageConsentToLoadMedia && page && !page->canStartMedia()) { 129 LOG(Media, "HTMLMediaSession::pageAllowsDataLoading - returning FALSE"); 130 return false; 131 } 132 133 return true; 134} 135 136bool HTMLMediaSession::pageAllowsPlaybackAfterResuming(const HTMLMediaElement& element) const 137{ 138 Page* page = element.document().page(); 139 if (m_restrictions & RequirePageConsentToResumeMedia && page && !page->canStartMedia()) { 140 LOG(Media, "HTMLMediaSession::pageAllowsPlaybackAfterResuming - returning FALSE"); 141 return false; 142 } 143 144 return true; 145} 146 147#if ENABLE(IOS_AIRPLAY) 148bool HTMLMediaSession::showingPlaybackTargetPickerPermitted(const HTMLMediaElement& element) const 149{ 150 if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !ScriptController::processingUserGesture()) { 151 LOG(Media, "HTMLMediaSession::showingPlaybackTargetPickerPermitted - returning FALSE because of permissions"); 152 return false; 153 } 154 155 if (!element.document().page()) { 156 LOG(Media, "HTMLMediaSession::showingPlaybackTargetPickerPermitted - returning FALSE because page is NULL"); 157 return false; 158 } 159 160 return !wirelessVideoPlaybackDisabled(element); 161} 162 163bool HTMLMediaSession::currentPlaybackTargetIsWireless(const HTMLMediaElement& element) const 164{ 165 MediaPlayer* player = element.player(); 166 if (!player) { 167 LOG(Media, "HTMLMediaSession::currentPlaybackTargetIsWireless - returning FALSE because player is NULL"); 168 return false; 169 } 170 171 bool isWireless = player->isCurrentPlaybackTargetWireless(); 172 LOG(Media, "HTMLMediaSession::currentPlaybackTargetIsWireless - returning %s", isWireless ? "TRUE" : "FALSE"); 173 174 return isWireless; 175} 176 177void HTMLMediaSession::showPlaybackTargetPicker(const HTMLMediaElement& element) 178{ 179 LOG(Media, "HTMLMediaSession::showPlaybackTargetPicker"); 180 181 if (!showingPlaybackTargetPickerPermitted(element)) 182 return; 183 184#if PLATFORM(IOS) 185 element.document().frame()->page()->chrome().client().showPlaybackTargetPicker(element.hasVideo()); 186#endif 187} 188 189bool HTMLMediaSession::hasWirelessPlaybackTargets(const HTMLMediaElement& element) const 190{ 191 UNUSED_PARAM(element); 192 193 bool hasTargets = MediaSessionManager::sharedManager().hasWirelessTargetsAvailable(); 194 LOG(Media, "HTMLMediaSession::hasWirelessPlaybackTargets - returning %s", hasTargets ? "TRUE" : "FALSE"); 195 196 return hasTargets; 197} 198 199bool HTMLMediaSession::wirelessVideoPlaybackDisabled(const HTMLMediaElement& element) const 200{ 201 Settings* settings = element.document().settings(); 202 if (!settings || !settings->mediaPlaybackAllowsAirPlay()) { 203 LOG(Media, "HTMLMediaSession::wirelessVideoPlaybackDisabled - returning TRUE because of settings"); 204 return true; 205 } 206 207 if (element.fastHasAttribute(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)) { 208 LOG(Media, "HTMLMediaSession::wirelessVideoPlaybackDisabled - returning TRUE because of attribute"); 209 return true; 210 } 211 212#if PLATFORM(IOS) 213 String legacyAirplayAttributeValue = element.fastGetAttribute(HTMLNames::webkitairplayAttr); 214 if (equalIgnoringCase(legacyAirplayAttributeValue, "deny")) { 215 LOG(Media, "HTMLMediaSession::wirelessVideoPlaybackDisabled - returning TRUE because of legacy attribute"); 216 return true; 217 } 218#endif 219 220 MediaPlayer* player = element.player(); 221 if (!player) 222 return false; 223 224 bool disabled = player->wirelessVideoPlaybackDisabled(); 225 LOG(Media, "HTMLMediaSession::wirelessVideoPlaybackDisabled - returning %s because media engine says so", disabled ? "TRUE" : "FALSE"); 226 227 return disabled; 228} 229 230void HTMLMediaSession::setWirelessVideoPlaybackDisabled(const HTMLMediaElement& element, bool disabled) 231{ 232 if (disabled) 233 addBehaviorRestriction(WirelessVideoPlaybackDisabled); 234 else 235 removeBehaviorRestriction(WirelessVideoPlaybackDisabled); 236 237 MediaPlayer* player = element.player(); 238 if (!player) 239 return; 240 241 LOG(Media, "HTMLMediaSession::setWirelessVideoPlaybackDisabled - disabled %s", disabled ? "TRUE" : "FALSE"); 242 player->setWirelessVideoPlaybackDisabled(disabled); 243} 244 245void HTMLMediaSession::setHasPlaybackTargetAvailabilityListeners(const HTMLMediaElement& element, bool hasListeners) 246{ 247 LOG(Media, "HTMLMediaSession::setHasPlaybackTargetAvailabilityListeners - hasListeners %s", hasListeners ? "TRUE" : "FALSE"); 248 UNUSED_PARAM(element); 249 250 if (hasListeners) 251 MediaSessionManager::sharedManager().startMonitoringAirPlayRoutes(); 252 else 253 MediaSessionManager::sharedManager().stopMonitoringAirPlayRoutes(); 254} 255#endif 256 257MediaPlayer::Preload HTMLMediaSession::effectivePreloadForElement(const HTMLMediaElement& element) const 258{ 259 MediaSessionManager::SessionRestrictions restrictions = MediaSessionManager::sharedManager().restrictions(mediaType()); 260 MediaPlayer::Preload preload = element.preloadValue(); 261 262 if ((restrictions & MediaSessionManager::MetadataPreloadingNotPermitted) == MediaSessionManager::MetadataPreloadingNotPermitted) 263 return MediaPlayer::None; 264 265 if ((restrictions & MediaSessionManager::AutoPreloadingNotPermitted) == MediaSessionManager::AutoPreloadingNotPermitted) { 266 if (preload > MediaPlayer::MetaData) 267 return MediaPlayer::MetaData; 268 } 269 270 return preload; 271} 272 273bool HTMLMediaSession::requiresFullscreenForVideoPlayback(const HTMLMediaElement& element) const 274{ 275 if (!MediaSessionManager::sharedManager().sessionRestrictsInlineVideoPlayback(*this)) 276 return false; 277 278 Settings* settings = element.document().settings(); 279 if (!settings || !settings->mediaPlaybackAllowsInline()) 280 return true; 281 282 if (element.fastHasAttribute(HTMLNames::webkit_playsinlineAttr)) 283 return false; 284 285#if PLATFORM(IOS) 286 if (applicationIsDumpRenderTree()) 287 return false; 288#endif 289 290 return true; 291} 292 293void HTMLMediaSession::applyMediaPlayerRestrictions(const HTMLMediaElement& element) 294{ 295 LOG(Media, "HTMLMediaSession::applyMediaPlayerRestrictions"); 296 297#if ENABLE(IOS_AIRPLAY) 298 setWirelessVideoPlaybackDisabled(element, m_restrictions & WirelessVideoPlaybackDisabled); 299#else 300 UNUSED_PARAM(element); 301#endif 302 303} 304 305#if ENABLE(MEDIA_SOURCE) 306const unsigned fiveMinutesOf1080PVideo = 290 * 1024 * 1024; // 290 MB is approximately 5 minutes of 8Mbps (1080p) content. 307const unsigned fiveMinutesStereoAudio = 14 * 1024 * 1024; // 14 MB is approximately 5 minutes of 384kbps content. 308 309size_t HTMLMediaSession::maximumMediaSourceBufferSize(const SourceBuffer& buffer) const 310{ 311 // A good quality 1080p video uses 8,000 kbps and stereo audio uses 384 kbps, so assume 95% for video and 5% for audio. 312 const float bufferBudgetPercentageForVideo = .95; 313 const float bufferBudgetPercentageForAudio = .05; 314 315 size_t maximum; 316 Settings* settings = buffer.document().settings(); 317 if (settings) 318 maximum = settings->maximumSourceBufferSize(); 319 else 320 maximum = fiveMinutesOf1080PVideo + fiveMinutesStereoAudio; 321 322 // Allow a SourceBuffer to buffer as though it is audio-only even if it doesn't have any active tracks (yet). 323 size_t bufferSize = static_cast<size_t>(maximum * bufferBudgetPercentageForAudio); 324 if (buffer.hasVideo()) 325 bufferSize += static_cast<size_t>(maximum * bufferBudgetPercentageForVideo); 326 327 // FIXME: we might want to modify this algorithm to: 328 // - decrease the maximum size for background tabs 329 // - decrease the maximum size allowed for inactive elements when a process has more than one 330 // element, eg. so a page with many elements which are played one at a time doesn't keep 331 // everything buffered after an element has finished playing. 332 333 return bufferSize; 334} 335#endif 336 337} 338 339#endif // ENABLE(VIDEO) 340