Unreviewed, rolling out r244627.
[WebKit-https.git] / Source / WebCore / platform / audio / ios / MediaSessionManagerIOS.mm
1 /*
2  * Copyright (C) 2014-2017 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_FAMILY)
30
31 #import "Logging.h"
32 #import "MediaPlayer.h"
33 #import "PlatformMediaSession.h"
34 #import "RuntimeApplicationChecks.h"
35 #import "SystemMemory.h"
36 #import "WebCoreThreadRun.h"
37 #import <AVFoundation/AVAudioSession.h>
38 #import <AVFoundation/AVRouteDetector.h>
39 #import <objc/runtime.h>
40 #import <pal/ios/UIKitSoftLink.h>
41 #import <pal/spi/ios/CelestialSPI.h>
42 #import <pal/spi/ios/UIKitSPI.h>
43 #import <wtf/BlockObjCExceptions.h>
44 #import <wtf/MainThread.h>
45 #import <wtf/RAMSize.h>
46 #import <wtf/RetainPtr.h>
47
48 SOFT_LINK_FRAMEWORK(AVFoundation)
49 SOFT_LINK_CLASS(AVFoundation, AVAudioSession)
50 SOFT_LINK_CONSTANT(AVFoundation, AVAudioSessionInterruptionNotification, NSString *)
51 SOFT_LINK_CONSTANT(AVFoundation, AVAudioSessionInterruptionTypeKey, NSString *)
52 SOFT_LINK_CONSTANT(AVFoundation, AVAudioSessionInterruptionOptionKey, NSString *)
53 SOFT_LINK_CONSTANT(AVFoundation, AVRouteDetectorMultipleRoutesDetectedDidChangeNotification, NSString *)
54
55 #if HAVE(CELESTIAL)
56 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(Celestial)
57 SOFT_LINK_CLASS_OPTIONAL(Celestial, AVSystemController)
58 SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_PIDToInheritApplicationStateFrom, NSString *)
59 #endif
60
61 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
62 SOFT_LINK_CLASS(AVFoundation, AVRouteDetector)
63 #endif
64
65 #define AVAudioSession getAVAudioSessionClass()
66 #define AVAudioSessionInterruptionNotification getAVAudioSessionInterruptionNotification()
67 #define AVAudioSessionInterruptionTypeKey getAVAudioSessionInterruptionTypeKey()
68 #define AVAudioSessionInterruptionOptionKey getAVAudioSessionInterruptionOptionKey()
69
70 WEBCORE_EXPORT NSString* WebUIApplicationWillResignActiveNotification = @"WebUIApplicationWillResignActiveNotification";
71 WEBCORE_EXPORT NSString* WebUIApplicationWillEnterForegroundNotification = @"WebUIApplicationWillEnterForegroundNotification";
72 WEBCORE_EXPORT NSString* WebUIApplicationDidBecomeActiveNotification = @"WebUIApplicationDidBecomeActiveNotification";
73 WEBCORE_EXPORT NSString* WebUIApplicationDidEnterBackgroundNotification = @"WebUIApplicationDidEnterBackgroundNotification";
74
75 using namespace WebCore;
76
77 @interface WebMediaSessionHelper : NSObject {
78     MediaSessionManageriOS* _callback;
79
80 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
81     RetainPtr<AVRouteDetector> _routeDetector;
82 #endif
83     bool _monitoringAirPlayRoutes;
84     bool _startMonitoringAirPlayRoutesPending;
85 }
86
87 - (id)initWithCallback:(MediaSessionManageriOS*)callback;
88
89 - (void)clearCallback;
90 - (void)interruption:(NSNotification *)notification;
91 - (void)applicationWillEnterForeground:(NSNotification *)notification;
92 - (void)applicationWillResignActive:(NSNotification *)notification;
93 - (void)applicationDidEnterBackground:(NSNotification *)notification;
94 - (BOOL)hasWirelessTargetsAvailable;
95
96 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
97 - (void)startMonitoringAirPlayRoutes;
98 - (void)stopMonitoringAirPlayRoutes;
99 #endif
100
101 @end
102
103 namespace WebCore {
104
105 static MediaSessionManageriOS* platformMediaSessionManager = nullptr;
106
107 PlatformMediaSessionManager& PlatformMediaSessionManager::sharedManager()
108 {
109     if (!platformMediaSessionManager)
110         platformMediaSessionManager = new MediaSessionManageriOS;
111     return *platformMediaSessionManager;
112 }
113
114 PlatformMediaSessionManager* PlatformMediaSessionManager::sharedManagerIfExists()
115 {
116     return platformMediaSessionManager;
117 }
118
119 MediaSessionManageriOS::MediaSessionManageriOS()
120     : MediaSessionManagerCocoa()
121 {
122     BEGIN_BLOCK_OBJC_EXCEPTIONS
123     m_objcObserver = adoptNS([[WebMediaSessionHelper alloc] initWithCallback:this]);
124     END_BLOCK_OBJC_EXCEPTIONS
125     resetRestrictions();
126 }
127
128 MediaSessionManageriOS::~MediaSessionManageriOS()
129 {
130     BEGIN_BLOCK_OBJC_EXCEPTIONS
131     [m_objcObserver clearCallback];
132     m_objcObserver = nil;
133     END_BLOCK_OBJC_EXCEPTIONS
134 }
135
136 void MediaSessionManageriOS::resetRestrictions()
137 {
138     static const size_t systemMemoryRequiredForVideoInBackgroundTabs = 1024 * 1024 * 1024;
139
140     ALWAYS_LOG(LOGIDENTIFIER);
141
142     PlatformMediaSessionManager::resetRestrictions();
143
144     if (ramSize() < systemMemoryRequiredForVideoInBackgroundTabs) {
145         ALWAYS_LOG(LOGIDENTIFIER, "restricting video in background tabs because system memory = ", ramSize());
146         addRestriction(PlatformMediaSession::Video, BackgroundTabPlaybackRestricted);
147     }
148
149     addRestriction(PlatformMediaSession::Video, BackgroundProcessPlaybackRestricted);
150     addRestriction(PlatformMediaSession::VideoAudio, ConcurrentPlaybackNotPermitted | BackgroundProcessPlaybackRestricted | SuspendedUnderLockPlaybackRestricted);
151 }
152
153 bool MediaSessionManageriOS::hasWirelessTargetsAvailable()
154 {
155     BEGIN_BLOCK_OBJC_EXCEPTIONS
156     return [m_objcObserver hasWirelessTargetsAvailable];
157     END_BLOCK_OBJC_EXCEPTIONS
158 }
159
160 void MediaSessionManageriOS::configureWireLessTargetMonitoring()
161 {
162 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
163     bool requiresMonitoring = anyOfSessions([] (PlatformMediaSession& session, size_t) {
164         return session.requiresPlaybackTargetRouteMonitoring();
165     });
166
167     ALWAYS_LOG(LOGIDENTIFIER, "requiresMonitoring = ", requiresMonitoring);
168
169     BEGIN_BLOCK_OBJC_EXCEPTIONS
170
171     if (requiresMonitoring)
172         [m_objcObserver startMonitoringAirPlayRoutes];
173     else
174         [m_objcObserver stopMonitoringAirPlayRoutes];
175
176     END_BLOCK_OBJC_EXCEPTIONS
177 #endif
178 }
179
180 void MediaSessionManageriOS::providePresentingApplicationPIDIfNecessary()
181 {
182 #if HAVE(CELESTIAL)
183     if (m_havePresentedApplicationPID)
184         return;
185     m_havePresentedApplicationPID = true;
186
187     if (!canLoadAVSystemController_PIDToInheritApplicationStateFrom())
188         return;
189
190     NSError *error = nil;
191     [[getAVSystemControllerClass() sharedAVSystemController] setAttribute:@(presentingApplicationPID()) forKey:getAVSystemController_PIDToInheritApplicationStateFrom() error:&error];
192     if (error)
193         WTFLogAlways("Failed to set up PID proxying: %s", error.localizedDescription.UTF8String);
194 #endif
195 }
196
197 void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
198 {
199     BEGIN_BLOCK_OBJC_EXCEPTIONS
200     forEachSession([haveTargets = [m_objcObserver hasWirelessTargetsAvailable]] (PlatformMediaSession& session, size_t) {
201         session.externalOutputDeviceAvailableDidChange(haveTargets);
202     });
203     END_BLOCK_OBJC_EXCEPTIONS
204 }
205
206 } // namespace WebCore
207
208 @implementation WebMediaSessionHelper
209
210 - (id)initWithCallback:(MediaSessionManageriOS*)callback
211 {
212     LOG(Media, "-[WebMediaSessionHelper initWithCallback]");
213
214     if (!(self = [super init]))
215         return nil;
216     
217     _callback = callback;
218
219     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
220     [center addObserver:self selector:@selector(interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
221
222     [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:PAL::get_UIKit_UIApplicationWillEnterForegroundNotification() object:nil];
223     [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:WebUIApplicationWillEnterForegroundNotification object:nil];
224     [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:PAL::get_UIKit_UIApplicationDidBecomeActiveNotification() object:nil];
225     [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:WebUIApplicationDidBecomeActiveNotification object:nil];
226     [center addObserver:self selector:@selector(applicationWillResignActive:) name:PAL::get_UIKit_UIApplicationWillResignActiveNotification() object:nil];
227     [center addObserver:self selector:@selector(applicationWillResignActive:) name:WebUIApplicationWillResignActiveNotification object:nil];
228     [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:PAL::get_UIKit_UIApplicationDidEnterBackgroundNotification() object:nil];
229     [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:WebUIApplicationDidEnterBackgroundNotification object:nil];
230
231     // Now playing won't work unless we turn on the delivery of remote control events.
232     dispatch_async(dispatch_get_main_queue(), ^ {
233         BEGIN_BLOCK_OBJC_EXCEPTIONS
234         [[PAL::getUIApplicationClass() sharedApplication] beginReceivingRemoteControlEvents];
235         END_BLOCK_OBJC_EXCEPTIONS
236     });
237
238     return self;
239 }
240
241 - (void)dealloc
242 {
243     LOG(Media, "-[WebMediaSessionHelper dealloc]");
244
245 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
246     if (!pthread_main_np()) {
247         dispatch_async(dispatch_get_main_queue(), [routeDetector = WTFMove(_routeDetector)] () mutable {
248             LOG(Media, "safelyTearDown - dipatched to UI thread.");
249             BEGIN_BLOCK_OBJC_EXCEPTIONS
250             routeDetector.get().routeDetectionEnabled = NO;
251             routeDetector.clear();
252             END_BLOCK_OBJC_EXCEPTIONS
253         });
254     } else
255         _routeDetector.get().routeDetectionEnabled = NO;
256 #endif
257
258     [[NSNotificationCenter defaultCenter] removeObserver:self];
259     [super dealloc];
260 }
261
262 - (void)clearCallback
263 {
264     LOG(Media, "-[WebMediaSessionHelper clearCallback]");
265     _callback = nil;
266 }
267
268 - (BOOL)hasWirelessTargetsAvailable
269 {
270     LOG(Media, "-[WebMediaSessionHelper hasWirelessTargetsAvailable]");
271 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
272     return _routeDetector.get().multipleRoutesDetected;
273 #else
274     return NO;
275 #endif
276 }
277
278 #if HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
279 - (void)startMonitoringAirPlayRoutes
280 {
281     if (_monitoringAirPlayRoutes)
282         return;
283
284     _monitoringAirPlayRoutes = true;
285
286     if (_startMonitoringAirPlayRoutesPending)
287         return;
288
289     if (_routeDetector) {
290         _routeDetector.get().routeDetectionEnabled = YES;
291         return;
292     }
293
294     _startMonitoringAirPlayRoutesPending = true;
295
296     LOG(Media, "-[WebMediaSessionHelper startMonitoringAirPlayRoutes]");
297
298     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
299         ASSERT(!protectedSelf->_routeDetector);
300
301         if (protectedSelf->_callback) {
302             BEGIN_BLOCK_OBJC_EXCEPTIONS
303             protectedSelf->_routeDetector = adoptNS([allocAVRouteDetectorInstance() init]);
304             protectedSelf->_routeDetector.get().routeDetectionEnabled = protectedSelf->_monitoringAirPlayRoutes;
305             [[NSNotificationCenter defaultCenter] addObserver:protectedSelf selector:@selector(wirelessRoutesAvailableDidChange:) name:getAVRouteDetectorMultipleRoutesDetectedDidChangeNotification() object:protectedSelf->_routeDetector.get()];
306             END_BLOCK_OBJC_EXCEPTIONS
307         }
308
309         protectedSelf->_startMonitoringAirPlayRoutesPending = false;
310     });
311 }
312
313 - (void)stopMonitoringAirPlayRoutes
314 {
315     if (!_monitoringAirPlayRoutes)
316         return;
317
318     LOG(Media, "-[WebMediaSessionHelper stopMonitoringAirPlayRoutes]");
319
320     _monitoringAirPlayRoutes = false;
321     _routeDetector.get().routeDetectionEnabled = NO;
322 }
323 #endif // HAVE(MEDIA_PLAYER) && !PLATFORM(WATCHOS)
324
325 - (void)interruption:(NSNotification *)notification
326 {
327     if (!_callback || _callback->willIgnoreSystemInterruptions())
328         return;
329
330     NSUInteger type = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
331     PlatformMediaSession::EndInterruptionFlags flags = PlatformMediaSession::NoFlags;
332
333     LOG(Media, "-[WebMediaSessionHelper interruption] - type = %i", (int)type);
334
335     if (type == AVAudioSessionInterruptionTypeEnded && [[[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == AVAudioSessionInterruptionOptionShouldResume)
336         flags = PlatformMediaSession::MayResumePlaying;
337
338     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self), type, flags]() mutable {
339         auto* callback = protectedSelf->_callback;
340         if (!callback)
341             return;
342
343         if (type == AVAudioSessionInterruptionTypeBegan)
344             callback->beginInterruption(PlatformMediaSession::SystemInterruption);
345         else
346             callback->endInterruption(flags);
347
348     });
349 }
350
351 - (void)applicationWillEnterForeground:(NSNotification *)notification
352 {
353     UNUSED_PARAM(notification);
354
355     if (!_callback || _callback->willIgnoreSystemInterruptions())
356         return;
357
358     LOG(Media, "-[WebMediaSessionHelper applicationWillEnterForeground]");
359
360     BOOL isSuspendedUnderLock = [[[notification userInfo] objectForKey:@"isSuspendedUnderLock"] boolValue];
361     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self), isSuspendedUnderLock]() mutable {
362         if (auto* callback = protectedSelf->_callback)
363             callback->applicationWillEnterForeground(isSuspendedUnderLock);
364     });
365 }
366
367 - (void)applicationDidBecomeActive:(NSNotification *)notification
368 {
369     UNUSED_PARAM(notification);
370
371     if (!_callback || _callback->willIgnoreSystemInterruptions())
372         return;
373
374     LOG(Media, "-[WebMediaSessionHelper applicationDidBecomeActive]");
375
376     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
377         if (auto* callback = protectedSelf->_callback)
378             callback->applicationDidBecomeActive();
379     });
380 }
381
382 - (void)applicationWillResignActive:(NSNotification *)notification
383 {
384     UNUSED_PARAM(notification);
385
386     if (!_callback || _callback->willIgnoreSystemInterruptions())
387         return;
388
389     LOG(Media, "-[WebMediaSessionHelper applicationWillResignActive]");
390
391     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
392         if (auto* callback = protectedSelf->_callback)
393             callback->applicationWillBecomeInactive();
394     });
395 }
396
397 - (void)wirelessRoutesAvailableDidChange:(NSNotification *)notification
398 {
399     UNUSED_PARAM(notification);
400
401     if (!_callback || !_monitoringAirPlayRoutes)
402         return;
403
404     LOG(Media, "-[WebMediaSessionHelper wirelessRoutesAvailableDidChange]");
405
406     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
407         if (auto* callback = protectedSelf->_callback)
408             callback->externalOutputDeviceAvailableDidChange();
409     });
410 }
411
412 - (void)applicationDidEnterBackground:(NSNotification *)notification
413 {
414     if (!_callback || _callback->willIgnoreSystemInterruptions())
415         return;
416
417     LOG(Media, "-[WebMediaSessionHelper applicationDidEnterBackground]");
418
419     BOOL isSuspendedUnderLock = [[[notification userInfo] objectForKey:@"isSuspendedUnderLock"] boolValue];
420     callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self), isSuspendedUnderLock]() mutable {
421         if (auto* callback = protectedSelf->_callback)
422             callback->applicationDidEnterBackground(isSuspendedUnderLock);
423     });
424 }
425 @end
426
427 #endif // PLATFORM(IOS_FAMILY)