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