af5295010c39aa1e49f793bb13990b857939044c
[WebKit-https.git] / Source / WebCore / platform / audio / ios / MediaSessionManagerIOS.mm
1 /*
2  * Copyright (C) 2014-2015 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 <MediaPlayer/MPMediaItem.h>
38 #import <MediaPlayer/MPNowPlayingInfoCenter.h>
39 #import <MediaPlayer/MPVolumeView.h>
40 #import <UIKit/UIApplication.h>
41 #import <objc/runtime.h>
42 #import <pal/spi/ios/MediaPlayerSPI.h>
43 #import <wtf/BlockObjCExceptions.h>
44 #import <wtf/MainThread.h>
45 #import <wtf/RAMSize.h>
46 #import <wtf/RetainPtr.h>
47 #import <wtf/SoftLinking.h>
48
49 SOFT_LINK_FRAMEWORK(AVFoundation)
50 SOFT_LINK_CLASS(AVFoundation, AVAudioSession)
51 SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionNotification, NSString *)
52 SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionTypeKey, NSString *)
53 SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionOptionKey, NSString *)
54
55 #define AVAudioSession getAVAudioSessionClass()
56 #define AVAudioSessionInterruptionNotification getAVAudioSessionInterruptionNotification()
57 #define AVAudioSessionInterruptionTypeKey getAVAudioSessionInterruptionTypeKey()
58 #define AVAudioSessionInterruptionOptionKey getAVAudioSessionInterruptionOptionKey()
59
60 SOFT_LINK_FRAMEWORK(UIKit)
61 SOFT_LINK_CLASS(UIKit, UIApplication)
62 SOFT_LINK_POINTER(UIKit, UIApplicationWillResignActiveNotification, NSString *)
63 SOFT_LINK_POINTER(UIKit, UIApplicationWillEnterForegroundNotification, NSString *)
64 SOFT_LINK_POINTER(UIKit, UIApplicationDidBecomeActiveNotification, NSString *)
65 SOFT_LINK_POINTER(UIKit, UIApplicationDidEnterBackgroundNotification, NSString *)
66
67 #define UIApplication getUIApplicationClass()
68 #define UIApplicationWillResignActiveNotification getUIApplicationWillResignActiveNotification()
69 #define UIApplicationWillEnterForegroundNotification getUIApplicationWillEnterForegroundNotification()
70 #define UIApplicationDidBecomeActiveNotification getUIApplicationDidBecomeActiveNotification()
71 #define UIApplicationDidEnterBackgroundNotification getUIApplicationDidEnterBackgroundNotification()
72
73 SOFT_LINK_FRAMEWORK(MediaPlayer)
74 SOFT_LINK_CLASS(MediaPlayer, MPAVRoutingController)
75 SOFT_LINK_CLASS(MediaPlayer, MPNowPlayingInfoCenter)
76 SOFT_LINK_CLASS(MediaPlayer, MPVolumeView)
77 SOFT_LINK_POINTER(MediaPlayer, MPMediaItemPropertyTitle, NSString *)
78 SOFT_LINK_POINTER(MediaPlayer, MPMediaItemPropertyPlaybackDuration, NSString *)
79 SOFT_LINK_POINTER(MediaPlayer, MPNowPlayingInfoPropertyElapsedPlaybackTime, NSString *)
80 SOFT_LINK_POINTER(MediaPlayer, MPNowPlayingInfoPropertyPlaybackRate, NSString *)
81 SOFT_LINK_POINTER(MediaPlayer, MPVolumeViewWirelessRoutesAvailableDidChangeNotification, NSString *)
82
83
84 #define MPMediaItemPropertyTitle getMPMediaItemPropertyTitle()
85 #define MPMediaItemPropertyPlaybackDuration getMPMediaItemPropertyPlaybackDuration()
86 #define MPNowPlayingInfoPropertyElapsedPlaybackTime getMPNowPlayingInfoPropertyElapsedPlaybackTime()
87 #define MPNowPlayingInfoPropertyPlaybackRate getMPNowPlayingInfoPropertyPlaybackRate()
88 #define MPVolumeViewWirelessRoutesAvailableDidChangeNotification getMPVolumeViewWirelessRoutesAvailableDidChangeNotification()
89
90 WEBCORE_EXPORT NSString* WebUIApplicationWillResignActiveNotification = @"WebUIApplicationWillResignActiveNotification";
91 WEBCORE_EXPORT NSString* WebUIApplicationWillEnterForegroundNotification = @"WebUIApplicationWillEnterForegroundNotification";
92 WEBCORE_EXPORT NSString* WebUIApplicationDidBecomeActiveNotification = @"WebUIApplicationDidBecomeActiveNotification";
93 WEBCORE_EXPORT NSString* WebUIApplicationDidEnterBackgroundNotification = @"WebUIApplicationDidEnterBackgroundNotification";
94
95 using namespace WebCore;
96
97 @interface WebMediaSessionHelper : NSObject {
98     MediaSessionManageriOS* _callback;
99     RetainPtr<MPVolumeView> _volumeView;
100     RetainPtr<MPAVRoutingController> _airPlayPresenceRoutingController;
101 }
102
103 - (id)initWithCallback:(MediaSessionManageriOS*)callback;
104 - (void)allocateVolumeView;
105 - (void)setVolumeView:(RetainPtr<MPVolumeView>)volumeView;
106 - (void)clearCallback;
107 - (void)interruption:(NSNotification *)notification;
108 - (void)applicationWillEnterForeground:(NSNotification *)notification;
109 - (void)applicationWillResignActive:(NSNotification *)notification;
110 - (void)applicationDidEnterBackground:(NSNotification *)notification;
111 - (BOOL)hasWirelessTargetsAvailable;
112 - (void)startMonitoringAirPlayRoutes;
113 - (void)stopMonitoringAirPlayRoutes;
114 @end
115
116 namespace WebCore {
117
118 static MediaSessionManageriOS* platformMediaSessionManager = nullptr;
119
120 PlatformMediaSessionManager& PlatformMediaSessionManager::sharedManager()
121 {
122     if (!platformMediaSessionManager)
123         platformMediaSessionManager = new MediaSessionManageriOS;
124     return *platformMediaSessionManager;
125 }
126
127 PlatformMediaSessionManager* PlatformMediaSessionManager::sharedManagerIfExists()
128 {
129     return platformMediaSessionManager;
130 }
131
132 MediaSessionManageriOS::MediaSessionManageriOS()
133     : PlatformMediaSessionManager()
134 {
135     BEGIN_BLOCK_OBJC_EXCEPTIONS
136     m_objcObserver = adoptNS([[WebMediaSessionHelper alloc] initWithCallback:this]);
137     END_BLOCK_OBJC_EXCEPTIONS
138     resetRestrictions();
139 }
140
141 MediaSessionManageriOS::~MediaSessionManageriOS()
142 {
143     BEGIN_BLOCK_OBJC_EXCEPTIONS
144     [m_objcObserver clearCallback];
145     m_objcObserver = nil;
146     END_BLOCK_OBJC_EXCEPTIONS
147 }
148
149 void MediaSessionManageriOS::resetRestrictions()
150 {
151     static const size_t systemMemoryRequiredForVideoInBackgroundTabs = 1024 * 1024 * 1024;
152
153     LOG(Media, "MediaSessionManageriOS::resetRestrictions");
154
155     PlatformMediaSessionManager::resetRestrictions();
156
157     if (ramSize() < systemMemoryRequiredForVideoInBackgroundTabs) {
158         LOG(Media, "MediaSessionManageriOS::resetRestrictions - restricting video in background tabs because system memory = %zul", ramSize());
159         addRestriction(PlatformMediaSession::Video, BackgroundTabPlaybackRestricted);
160     }
161
162     addRestriction(PlatformMediaSession::Video, BackgroundProcessPlaybackRestricted);
163     addRestriction(PlatformMediaSession::VideoAudio, ConcurrentPlaybackNotPermitted | BackgroundProcessPlaybackRestricted | SuspendedUnderLockPlaybackRestricted);
164 }
165
166 bool MediaSessionManageriOS::hasWirelessTargetsAvailable()
167 {
168     BEGIN_BLOCK_OBJC_EXCEPTIONS
169     return [m_objcObserver hasWirelessTargetsAvailable];
170     END_BLOCK_OBJC_EXCEPTIONS
171 }
172
173 void MediaSessionManageriOS::configureWireLessTargetMonitoring()
174 {
175     bool requiresMonitoring = anyOfSessions([] (PlatformMediaSession& session, size_t) {
176         return session.requiresPlaybackTargetRouteMonitoring();
177     });
178
179     LOG(Media, "MediaSessionManageriOS::configureWireLessTargetMonitoring - requiresMonitoring = %s", requiresMonitoring ? "true" : "false");
180
181     BEGIN_BLOCK_OBJC_EXCEPTIONS
182
183     if (requiresMonitoring)
184         [m_objcObserver startMonitoringAirPlayRoutes];
185     else
186         [m_objcObserver stopMonitoringAirPlayRoutes];
187
188     END_BLOCK_OBJC_EXCEPTIONS
189 }
190
191 bool MediaSessionManageriOS::sessionWillBeginPlayback(PlatformMediaSession& session)
192 {
193     if (!PlatformMediaSessionManager::sessionWillBeginPlayback(session))
194         return false;
195
196     LOG(Media, "MediaSessionManageriOS::sessionWillBeginPlayback");
197     updateNowPlayingInfo();
198     return true;
199 }
200
201 void MediaSessionManageriOS::removeSession(PlatformMediaSession& session)
202 {
203     PlatformMediaSessionManager::removeSession(session);
204     LOG(Media, "MediaSessionManageriOS::removeSession");
205     updateNowPlayingInfo();
206 }
207
208 void MediaSessionManageriOS::sessionWillEndPlayback(PlatformMediaSession& session)
209 {
210     PlatformMediaSessionManager::sessionWillEndPlayback(session);
211     LOG(Media, "MediaSessionManageriOS::sessionWillEndPlayback");
212     updateNowPlayingInfo();
213 }
214
215 void MediaSessionManageriOS::clientCharacteristicsChanged(PlatformMediaSession&)
216 {
217     LOG(Media, "MediaSessionManageriOS::clientCharacteristicsChanged");
218     updateNowPlayingInfo();
219 }
220
221 PlatformMediaSession* MediaSessionManageriOS::nowPlayingEligibleSession()
222 {
223     return findSession([] (PlatformMediaSession& session, size_t) {
224         PlatformMediaSession::MediaType type = session.mediaType();
225         if (type != PlatformMediaSession::VideoAudio && type != PlatformMediaSession::Audio)
226             return false;
227
228         if (session.characteristics() & PlatformMediaSession::HasAudio)
229             return true;
230
231         return false;
232     });
233 }
234
235 void MediaSessionManageriOS::updateNowPlayingInfo()
236 {
237     BEGIN_BLOCK_OBJC_EXCEPTIONS
238     MPNowPlayingInfoCenter *nowPlaying = (MPNowPlayingInfoCenter *)[getMPNowPlayingInfoCenterClass() defaultCenter];
239     const PlatformMediaSession* currentSession = this->nowPlayingEligibleSession();
240
241     LOG(Media, "MediaSessionManageriOS::updateNowPlayingInfo - currentSession = %p", currentSession);
242
243     if (!currentSession) {
244         if (m_nowPlayingActive) {
245             LOG(Media, "MediaSessionManageriOS::updateNowPlayingInfo - clearing now playing info");
246             [nowPlaying setNowPlayingInfo:nil];
247             m_nowPlayingActive = false;
248         }
249
250         return;
251     }
252
253     String title = currentSession->title();
254     double duration = currentSession->duration();
255     double rate = currentSession->state() == PlatformMediaSession::Playing ? 1 : 0;
256     double currentTime = currentSession->currentTime();
257     if (m_reportedTitle == title && m_reportedRate == rate && m_reportedDuration == duration) {
258         LOG(Media, "MediaSessionManageriOS::updateNowPlayingInfo - nothing new to show");
259         return;
260     }
261
262     m_reportedRate = rate;
263     m_reportedDuration = duration;
264     m_reportedTitle = title;
265     m_reportedCurrentTime = currentTime;
266
267     auto info = adoptNS([[NSMutableDictionary alloc] init]);
268     if (!title.isEmpty())
269         info.get()[MPMediaItemPropertyTitle] = static_cast<NSString *>(title);
270     if (std::isfinite(duration) && duration != MediaPlayer::invalidTime())
271         info.get()[MPMediaItemPropertyPlaybackDuration] = @(duration);
272     info.get()[MPNowPlayingInfoPropertyPlaybackRate] = @(rate);
273
274     if (std::isfinite(currentTime) && currentTime != MediaPlayer::invalidTime())
275         info.get()[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(currentTime);
276
277     LOG(Media, "MediaSessionManageriOS::updateNowPlayingInfo - title = \"%s\", rate = %f, duration = %f, now = %f",
278         title.utf8().data(), rate, duration, currentTime);
279
280     m_nowPlayingActive = true;
281     [nowPlaying setNowPlayingInfo:info.get()];
282     END_BLOCK_OBJC_EXCEPTIONS
283 }
284
285 bool MediaSessionManageriOS::sessionCanLoadMedia(const PlatformMediaSession& session) const
286 {
287     if (session.displayType() == PlatformMediaSession::Optimized)
288         return true;
289
290     return PlatformMediaSessionManager::sessionCanLoadMedia(session);
291 }
292
293 void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
294 {
295     BEGIN_BLOCK_OBJC_EXCEPTIONS
296     forEachSession([haveTargets = [m_objcObserver hasWirelessTargetsAvailable]] (PlatformMediaSession& session, size_t) {
297         session.externalOutputDeviceAvailableDidChange(haveTargets);
298     });
299     END_BLOCK_OBJC_EXCEPTIONS
300 }
301
302 } // namespace WebCore
303
304 @implementation WebMediaSessionHelper
305
306 - (void)allocateVolumeView
307 {
308     if (pthread_main_np()) {
309         [self setVolumeView:adoptNS([allocMPVolumeViewInstance() init])];
310         return;
311     }
312
313     RetainPtr<WebMediaSessionHelper> strongSelf = self;
314     dispatch_async(dispatch_get_main_queue(), [strongSelf]() {
315         BEGIN_BLOCK_OBJC_EXCEPTIONS
316         RetainPtr<MPVolumeView> volumeView = adoptNS([allocMPVolumeViewInstance() init]);
317         callOnWebThreadOrDispatchAsyncOnMainThread([strongSelf, volumeView]() {
318             BEGIN_BLOCK_OBJC_EXCEPTIONS
319             [strongSelf setVolumeView:volumeView];
320             END_BLOCK_OBJC_EXCEPTIONS
321         });
322         END_BLOCK_OBJC_EXCEPTIONS
323     });
324 }
325
326 - (void)setVolumeView:(RetainPtr<MPVolumeView>)volumeView
327 {
328     if (_volumeView)
329         [[NSNotificationCenter defaultCenter] removeObserver:self name:MPVolumeViewWirelessRoutesAvailableDidChangeNotification object:_volumeView.get()];
330
331     _volumeView = volumeView;
332
333     if (_volumeView)
334         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wirelessRoutesAvailableDidChange:) name:MPVolumeViewWirelessRoutesAvailableDidChangeNotification object:_volumeView.get()];
335 }
336
337 - (id)initWithCallback:(MediaSessionManageriOS*)callback
338 {
339     LOG(Media, "-[WebMediaSessionHelper initWithCallback]");
340
341     if (!(self = [super init]))
342         return nil;
343     
344     _callback = callback;
345
346     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
347     [center addObserver:self selector:@selector(interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
348
349     [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
350     [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:WebUIApplicationWillEnterForegroundNotification object:nil];
351     [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
352     [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:WebUIApplicationDidBecomeActiveNotification object:nil];
353     [center addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
354     [center addObserver:self selector:@selector(applicationWillResignActive:) name:WebUIApplicationWillResignActiveNotification object:nil];
355     [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
356     [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:WebUIApplicationDidEnterBackgroundNotification object:nil];
357
358     [self allocateVolumeView];
359
360     // Now playing won't work unless we turn on the delivery of remote control events.
361     dispatch_async(dispatch_get_main_queue(), ^ {
362         BEGIN_BLOCK_OBJC_EXCEPTIONS
363         [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
364         END_BLOCK_OBJC_EXCEPTIONS
365     });
366
367     return self;
368 }
369
370 - (void)dealloc
371 {
372     LOG(Media, "-[WebMediaSessionHelper dealloc]");
373
374     if (!isMainThread()) {
375         callOnMainThread([volumeView = WTFMove(_volumeView), routingController = WTFMove(_airPlayPresenceRoutingController)] () mutable {
376             LOG(Media, "-[WebMediaSessionHelper dealloc] - dipatched to MainThread");
377
378             volumeView.clear();
379
380             if (!routingController)
381                 return;
382
383             [routingController setDiscoveryMode:MPRouteDiscoveryModeDisabled];
384             routingController.clear();
385         });
386     }
387
388     [[NSNotificationCenter defaultCenter] removeObserver:self];
389     [super dealloc];
390 }
391
392 - (void)clearCallback
393 {
394     LOG(Media, "-[WebMediaSessionHelper clearCallback]");
395     _callback = nil;
396 }
397
398 - (BOOL)hasWirelessTargetsAvailable
399 {
400     LOG(Media, "-[WebMediaSessionHelper hasWirelessTargetsAvailable]");
401     return _volumeView ? [_volumeView areWirelessRoutesAvailable] : NO;
402 }
403
404 - (void)startMonitoringAirPlayRoutes
405 {
406     if (_airPlayPresenceRoutingController)
407         return;
408
409     LOG(Media, "-[WebMediaSessionHelper startMonitoringAirPlayRoutes]");
410
411     callOnMainThread([protectedSelf = RetainPtr<WebMediaSessionHelper>(self)] () {
412         LOG(Media, "-[WebMediaSessionHelper startMonitoringAirPlayRoutes] - dipatched to MainThread");
413
414         if (protectedSelf->_airPlayPresenceRoutingController)
415             return;
416
417         protectedSelf->_airPlayPresenceRoutingController = adoptNS([allocMPAVRoutingControllerInstance() initWithName:@"WebCore - HTML media element checking for AirPlay route presence"]);
418         [protectedSelf->_airPlayPresenceRoutingController setDiscoveryMode:MPRouteDiscoveryModePresence];
419     });
420 }
421
422 - (void)stopMonitoringAirPlayRoutes
423 {
424     if (!_airPlayPresenceRoutingController)
425         return;
426
427     LOG(Media, "-[WebMediaSessionHelper stopMonitoringAirPlayRoutes]");
428
429     callOnMainThread([protectedSelf = RetainPtr<WebMediaSessionHelper>(self)] () {
430         LOG(Media, "-[WebMediaSessionHelper stopMonitoringAirPlayRoutes] - dipatched to MainThread");
431
432         if (!protectedSelf->_airPlayPresenceRoutingController)
433             return;
434
435         [protectedSelf->_airPlayPresenceRoutingController setDiscoveryMode:MPRouteDiscoveryModeDisabled];
436         protectedSelf->_airPlayPresenceRoutingController = nil;
437     });
438 }
439
440 - (void)interruption:(NSNotification *)notification
441 {
442     if (!_callback || _callback->willIgnoreSystemInterruptions())
443         return;
444
445     NSUInteger type = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
446     PlatformMediaSession::EndInterruptionFlags flags = PlatformMediaSession::NoFlags;
447
448     LOG(Media, "-[WebMediaSessionHelper interruption] - type = %i", (int)type);
449
450     if (type == AVAudioSessionInterruptionTypeEnded && [[[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == AVAudioSessionInterruptionOptionShouldResume)
451         flags = PlatformMediaSession::MayResumePlaying;
452
453     WebThreadRun(^{
454         if (!_callback)
455             return;
456
457         if (type == AVAudioSessionInterruptionTypeBegan)
458             _callback->beginInterruption(PlatformMediaSession::SystemInterruption);
459         else
460             _callback->endInterruption(flags);
461
462     });
463 }
464
465 - (void)applicationWillEnterForeground:(NSNotification *)notification
466 {
467     UNUSED_PARAM(notification);
468
469     if (!_callback || _callback->willIgnoreSystemInterruptions())
470         return;
471
472     LOG(Media, "-[WebMediaSessionHelper applicationWillEnterForeground]");
473
474     BOOL isSuspendedUnderLock = [[[notification userInfo] objectForKey:@"isSuspendedUnderLock"] boolValue];
475
476     WebThreadRun(^{
477         if (!_callback)
478             return;
479
480         _callback->applicationWillEnterForeground(isSuspendedUnderLock);
481     });
482 }
483
484 - (void)applicationDidBecomeActive:(NSNotification *)notification
485 {
486     UNUSED_PARAM(notification);
487
488     if (!_callback || _callback->willIgnoreSystemInterruptions())
489         return;
490
491     LOG(Media, "-[WebMediaSessionHelper applicationDidBecomeActive]");
492
493     WebThreadRun(^{
494         if (!_callback)
495             return;
496
497         _callback->applicationDidBecomeActive();
498     });
499 }
500
501 - (void)applicationWillResignActive:(NSNotification *)notification
502 {
503     UNUSED_PARAM(notification);
504
505     if (!_callback || _callback->willIgnoreSystemInterruptions())
506         return;
507
508     LOG(Media, "-[WebMediaSessionHelper applicationWillResignActive]");
509
510     WebThreadRun(^{
511         if (!_callback)
512             return;
513
514         _callback->applicationWillBecomeInactive();
515     });
516 }
517
518 - (void)wirelessRoutesAvailableDidChange:(NSNotification *)notification
519 {
520     UNUSED_PARAM(notification);
521
522     if (!_callback)
523         return;
524
525     LOG(Media, "-[WebMediaSessionHelper wirelessRoutesAvailableDidChange]");
526
527     WebThreadRun(^{
528         if (!_callback)
529             return;
530
531         _callback->externalOutputDeviceAvailableDidChange();
532     });
533 }
534
535 - (void)applicationDidEnterBackground:(NSNotification *)notification
536 {
537     if (!_callback || _callback->willIgnoreSystemInterruptions())
538         return;
539
540     LOG(Media, "-[WebMediaSessionHelper applicationDidEnterBackground]");
541
542     BOOL isSuspendedUnderLock = [[[notification userInfo] objectForKey:@"isSuspendedUnderLock"] boolValue];
543
544     WebThreadRun(^{
545         if (!_callback)
546             return;
547
548         _callback->applicationDidEnterBackground(isSuspendedUnderLock);
549     });
550 }
551 @end
552
553 #endif // PLATFORM(IOS)