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