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