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#import "config.h" 27#import "MediaSessionManagerIOS.h" 28 29#if PLATFORM(IOS) 30 31#import "Logging.h" 32#import "MediaPlayer.h" 33#import "MediaSession.h" 34#import "SoftLinking.h" 35#import "WebCoreSystemInterface.h" 36#import "WebCoreThreadRun.h" 37#import <AVFoundation/AVAudioSession.h> 38#import <MediaPlayer/MPAVRoutingController.h> 39#import <MediaPlayer/MPMediaItem.h> 40#import <MediaPlayer/MPNowPlayingInfoCenter.h> 41#import <MediaPlayer/MPVolumeView.h> 42#import <UIKit/UIApplication.h> 43#import <objc/runtime.h> 44#import <wtf/RetainPtr.h> 45 46SOFT_LINK_FRAMEWORK(AVFoundation) 47SOFT_LINK_CLASS(AVFoundation, AVAudioSession) 48SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionNotification, NSString *) 49SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionTypeKey, NSString *) 50SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionOptionKey, NSString *) 51 52#define AVAudioSession getAVAudioSessionClass() 53#define AVAudioSessionInterruptionNotification getAVAudioSessionInterruptionNotification() 54#define AVAudioSessionInterruptionTypeKey getAVAudioSessionInterruptionTypeKey() 55#define AVAudioSessionInterruptionOptionKey getAVAudioSessionInterruptionOptionKey() 56 57SOFT_LINK_FRAMEWORK(UIKit) 58SOFT_LINK_CLASS(UIKit, UIApplication) 59SOFT_LINK_POINTER(UIKit, UIApplicationWillResignActiveNotification, NSString *) 60SOFT_LINK_POINTER(UIKit, UIApplicationWillEnterForegroundNotification, NSString *) 61SOFT_LINK_POINTER(UIKit, UIApplicationDidBecomeActiveNotification, NSString *) 62 63#define UIApplication getUIApplicationClass() 64#define UIApplicationWillResignActiveNotification getUIApplicationWillResignActiveNotification() 65#define UIApplicationWillEnterForegroundNotification getUIApplicationWillEnterForegroundNotification() 66#define UIApplicationDidBecomeActiveNotification getUIApplicationDidBecomeActiveNotification() 67 68SOFT_LINK_FRAMEWORK(MediaPlayer) 69SOFT_LINK_CLASS(MediaPlayer, MPAVRoutingController) 70SOFT_LINK_CLASS(MediaPlayer, MPNowPlayingInfoCenter) 71SOFT_LINK_CLASS(MediaPlayer, MPVolumeView) 72SOFT_LINK_POINTER(MediaPlayer, MPMediaItemPropertyTitle, NSString *) 73SOFT_LINK_POINTER(MediaPlayer, MPMediaItemPropertyPlaybackDuration, NSString *) 74SOFT_LINK_POINTER(MediaPlayer, MPNowPlayingInfoPropertyElapsedPlaybackTime, NSString *) 75SOFT_LINK_POINTER(MediaPlayer, MPNowPlayingInfoPropertyPlaybackRate, NSString *) 76SOFT_LINK_POINTER(MediaPlayer, MPVolumeViewWirelessRoutesAvailableDidChangeNotification, NSString *) 77 78 79#define MPMediaItemPropertyTitle getMPMediaItemPropertyTitle() 80#define MPMediaItemPropertyPlaybackDuration getMPMediaItemPropertyPlaybackDuration() 81#define MPNowPlayingInfoPropertyElapsedPlaybackTime getMPNowPlayingInfoPropertyElapsedPlaybackTime() 82#define MPNowPlayingInfoPropertyPlaybackRate getMPNowPlayingInfoPropertyPlaybackRate() 83#define MPVolumeViewWirelessRoutesAvailableDidChangeNotification getMPVolumeViewWirelessRoutesAvailableDidChangeNotification() 84 85NSString* WebUIApplicationWillResignActiveNotification = @"WebUIApplicationWillResignActiveNotification"; 86NSString* WebUIApplicationWillEnterForegroundNotification = @"WebUIApplicationWillEnterForegroundNotification"; 87NSString* WebUIApplicationDidBecomeActiveNotification = @"WebUIApplicationDidBecomeActiveNotification"; 88 89using namespace WebCore; 90 91@interface WebMediaSessionHelper : NSObject { 92 MediaSessionManageriOS* _callback; 93 RetainPtr<MPVolumeView> _volumeView; 94 RetainPtr<MPAVRoutingController> _airPlayPresenceRoutingController; 95} 96 97- (id)initWithCallback:(MediaSessionManageriOS*)callback; 98- (void)clearCallback; 99- (void)interruption:(NSNotification *)notification; 100- (void)applicationWillEnterForeground:(NSNotification *)notification; 101- (void)applicationWillResignActive:(NSNotification *)notification; 102- (BOOL)hasWirelessTargetsAvailable; 103- (void)startMonitoringAirPlayRoutes; 104- (void)stopMonitoringAirPlayRoutes; 105@end 106 107namespace WebCore { 108 109MediaSessionManager& MediaSessionManager::sharedManager() 110{ 111 DEPRECATED_DEFINE_STATIC_LOCAL(MediaSessionManageriOS, manager, ()); 112 return manager; 113} 114 115MediaSessionManageriOS::MediaSessionManageriOS() 116 :MediaSessionManager() 117 , m_objcObserver(adoptNS([[WebMediaSessionHelper alloc] initWithCallback:this])) 118{ 119 resetRestrictions(); 120} 121 122MediaSessionManageriOS::~MediaSessionManageriOS() 123{ 124 [m_objcObserver clearCallback]; 125} 126 127void MediaSessionManageriOS::resetRestrictions() 128{ 129 MediaSessionManager::resetRestrictions(); 130 131 static wkDeviceClass deviceClass = iosDeviceClass(); 132 if (deviceClass == wkDeviceClassiPhone || deviceClass == wkDeviceClassiPod) 133 addRestriction(MediaSession::Video, InlineVideoPlaybackRestricted); 134 135 addRestriction(MediaSession::Video, ConcurrentPlaybackNotPermitted); 136 addRestriction(MediaSession::Video, BackgroundPlaybackNotPermitted); 137 138 removeRestriction(MediaSession::Audio, ConcurrentPlaybackNotPermitted); 139 removeRestriction(MediaSession::Audio, BackgroundPlaybackNotPermitted); 140 141 removeRestriction(MediaSession::WebAudio, ConcurrentPlaybackNotPermitted); 142 removeRestriction(MediaSession::WebAudio, BackgroundPlaybackNotPermitted); 143 144 removeRestriction(MediaSession::Audio, MetadataPreloadingNotPermitted); 145 removeRestriction(MediaSession::Video, MetadataPreloadingNotPermitted); 146 147 addRestriction(MediaSession::Audio, AutoPreloadingNotPermitted); 148 addRestriction(MediaSession::Video, AutoPreloadingNotPermitted); 149} 150 151#if ENABLE(IOS_AIRPLAY) 152 153bool MediaSessionManageriOS::hasWirelessTargetsAvailable() 154{ 155 return [m_objcObserver hasWirelessTargetsAvailable]; 156} 157 158void MediaSessionManageriOS::startMonitoringAirPlayRoutes() 159{ 160 [m_objcObserver startMonitoringAirPlayRoutes]; 161} 162 163void MediaSessionManageriOS::stopMonitoringAirPlayRoutes() 164{ 165 [m_objcObserver stopMonitoringAirPlayRoutes]; 166} 167#endif 168 169void MediaSessionManageriOS::sessionWillBeginPlayback(MediaSession& session) 170{ 171 MediaSessionManager::sessionWillBeginPlayback(session); 172 updateNowPlayingInfo(); 173} 174 175void MediaSessionManageriOS::sessionWillEndPlayback(MediaSession& session) 176{ 177 MediaSessionManager::sessionWillEndPlayback(session); 178 updateNowPlayingInfo(); 179} 180 181void MediaSessionManageriOS::updateNowPlayingInfo() 182{ 183 MPNowPlayingInfoCenter *nowPlaying = (MPNowPlayingInfoCenter *)[getMPNowPlayingInfoCenterClass() defaultCenter]; 184 const MediaSession* currentSession = this->currentSession(); 185 186 if (!currentSession) { 187 [nowPlaying setNowPlayingInfo:nil]; 188 return; 189 } 190 191 RetainPtr<NSMutableDictionary> info = adoptNS([[NSMutableDictionary alloc] init]); 192 193 String title = currentSession->title(); 194 if (!title.isEmpty()) 195 [info setValue:static_cast<NSString *>(title) forKey:MPMediaItemPropertyTitle]; 196 197 double duration = currentSession->duration(); 198 if (std::isfinite(duration) && duration != MediaPlayer::invalidTime()) 199 [info setValue:@(duration) forKey:MPMediaItemPropertyPlaybackDuration]; 200 201 double currentTime = currentSession->currentTime(); 202 if (std::isfinite(currentTime) && currentTime != MediaPlayer::invalidTime()) 203 [info setValue:@(currentTime) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; 204 205 [info setValue:(currentSession->state() == MediaSession::Playing ? @YES : @NO) forKey:MPNowPlayingInfoPropertyPlaybackRate]; 206 [nowPlaying setNowPlayingInfo:info.get()]; 207} 208 209} // namespace WebCore 210 211@implementation WebMediaSessionHelper 212 213- (id)initWithCallback:(MediaSessionManageriOS*)callback 214{ 215 if (!(self = [super init])) 216 return nil; 217 218 _callback = callback; 219 _volumeView = adoptNS([[getMPVolumeViewClass() alloc] init]); 220 221 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 222 [center addObserver:self selector:@selector(interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; 223 224 [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; 225 [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:WebUIApplicationWillEnterForegroundNotification object:nil]; 226 [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; 227 [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:WebUIApplicationDidBecomeActiveNotification object:nil]; 228 [center addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; 229 [center addObserver:self selector:@selector(applicationWillResignActive:) name:WebUIApplicationWillResignActiveNotification object:nil]; 230 [center addObserver:self selector:@selector(wirelessRoutesAvailableDidChange:) name:MPVolumeViewWirelessRoutesAvailableDidChangeNotification object:_volumeView.get()]; 231 232 // Now playing won't work unless we turn on the delivery of remote control events. 233 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; 234 235 return self; 236} 237 238- (void)dealloc 239{ 240 [[NSNotificationCenter defaultCenter] removeObserver:self]; 241 [super dealloc]; 242} 243 244- (void)clearCallback 245{ 246 _callback = nil; 247} 248 249- (BOOL)hasWirelessTargetsAvailable 250{ 251 return [_volumeView areWirelessRoutesAvailable]; 252} 253 254- (void)startMonitoringAirPlayRoutes 255{ 256 if (_airPlayPresenceRoutingController) 257 return; 258 259 _airPlayPresenceRoutingController = adoptNS([[getMPAVRoutingControllerClass() alloc] initWithName:@"WebCore - HTML media element checking for AirPlay route presence"]); 260 [_airPlayPresenceRoutingController setDiscoveryMode:MPRouteDiscoveryModePresence]; 261} 262 263- (void)stopMonitoringAirPlayRoutes 264{ 265 _airPlayPresenceRoutingController = nil; 266} 267 268- (void)interruption:(NSNotification *)notification 269{ 270 if (!_callback) 271 return; 272 273 NSUInteger type = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue]; 274 MediaSession::EndInterruptionFlags flags = MediaSession::NoFlags; 275 276 if (type == AVAudioSessionInterruptionTypeEnded && [[[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == AVAudioSessionInterruptionOptionShouldResume) 277 flags = MediaSession::MayResumePlaying; 278 279 WebThreadRun(^{ 280 if (!_callback) 281 return; 282 283 if (type == AVAudioSessionInterruptionTypeBegan) 284 _callback->beginInterruption(MediaSession::SystemInterruption); 285 else 286 _callback->endInterruption(flags); 287 288 }); 289} 290 291- (void)applicationWillEnterForeground:(NSNotification *)notification 292{ 293 UNUSED_PARAM(notification); 294 295 if (!_callback) 296 return; 297 298 WebThreadRun(^{ 299 if (!_callback) 300 return; 301 302 _callback->applicationWillEnterForeground(); 303 }); 304} 305 306- (void)applicationDidBecomeActive:(NSNotification *)notification 307{ 308 UNUSED_PARAM(notification); 309 310 if (!_callback) 311 return; 312 313 WebThreadRun(^{ 314 if (!_callback) 315 return; 316 317 _callback->applicationWillEnterForeground(); 318 }); 319} 320 321- (void)applicationWillResignActive:(NSNotification *)notification 322{ 323 UNUSED_PARAM(notification); 324 325 if (!_callback) 326 return; 327 328 WebThreadRun(^{ 329 if (!_callback) 330 return; 331 332 _callback->applicationWillEnterBackground(); 333 }); 334} 335 336- (void)wirelessRoutesAvailableDidChange:(NSNotification *)notification 337{ 338 UNUSED_PARAM(notification); 339 340 if (!_callback) 341 return; 342 343 WebThreadRun(^{ 344 if (!_callback) 345 return; 346 347 _callback->wirelessRoutesAvailableChanged(); 348 }); 349} 350@end 351 352#endif // PLATFORM(IOS) 353