1/*
2 * Copyright (C) 2013-2019 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#include "config.h"
27#include "PlatformMediaSessionManager.h"
28
29#include "AudioSession.h"
30#include "Document.h"
31#include "Logging.h"
32#include "PlatformMediaSession.h"
33
34namespace WebCore {
35
36#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO)
37
38#if !PLATFORM(COCOA)
39static PlatformMediaSessionManager* platformMediaSessionManager = nullptr;
40
41PlatformMediaSessionManager& PlatformMediaSessionManager::sharedManager()
42{
43 if (!platformMediaSessionManager)
44 platformMediaSessionManager = new PlatformMediaSessionManager;
45 return *platformMediaSessionManager;
46}
47
48PlatformMediaSessionManager* PlatformMediaSessionManager::sharedManagerIfExists()
49{
50 return platformMediaSessionManager;
51}
52#endif // !PLATFORM(COCOA)
53
54void PlatformMediaSessionManager::updateNowPlayingInfoIfNecessary()
55{
56 if (auto existingManager = PlatformMediaSessionManager::sharedManagerIfExists())
57 existingManager->scheduleUpdateNowPlayingInfo();
58}
59
60PlatformMediaSessionManager::PlatformMediaSessionManager()
61 : m_systemSleepListener(PAL::SystemSleepListener::create(*this))
62#if !RELEASE_LOG_DISABLED
63 , m_logger(AggregateLogger::create(this))
64#endif
65{
66 resetRestrictions();
67}
68
69void PlatformMediaSessionManager::resetRestrictions()
70{
71 m_restrictions[PlatformMediaSession::Video] = NoRestrictions;
72 m_restrictions[PlatformMediaSession::Audio] = NoRestrictions;
73 m_restrictions[PlatformMediaSession::VideoAudio] = NoRestrictions;
74 m_restrictions[PlatformMediaSession::WebAudio] = NoRestrictions;
75 m_restrictions[PlatformMediaSession::MediaStreamCapturingAudio] = NoRestrictions;
76}
77
78bool PlatformMediaSessionManager::has(PlatformMediaSession::MediaType type) const
79{
80 ASSERT(type >= PlatformMediaSession::None && type <= PlatformMediaSession::MediaStreamCapturingAudio);
81
82 return anyOfSessions([type] (PlatformMediaSession& session, size_t) {
83 return session.mediaType() == type;
84 });
85}
86
87bool PlatformMediaSessionManager::activeAudioSessionRequired() const
88{
89 return anyOfSessions([] (PlatformMediaSession& session, size_t) {
90 return session.activeAudioSessionRequired();
91 });
92}
93
94bool PlatformMediaSessionManager::canProduceAudio() const
95{
96 return anyOfSessions([] (PlatformMediaSession& session, size_t) {
97 return session.canProduceAudio();
98 });
99}
100
101int PlatformMediaSessionManager::count(PlatformMediaSession::MediaType type) const
102{
103 ASSERT(type >= PlatformMediaSession::None && type <= PlatformMediaSession::MediaStreamCapturingAudio);
104
105 int count = 0;
106 for (auto* session : m_sessions) {
107 if (session->mediaType() == type)
108 ++count;
109 }
110
111 return count;
112}
113
114void PlatformMediaSessionManager::beginInterruption(PlatformMediaSession::InterruptionType type)
115{
116 ALWAYS_LOG(LOGIDENTIFIER);
117
118 m_interrupted = true;
119 forEachSession([type] (PlatformMediaSession& session, size_t) {
120 session.beginInterruption(type);
121 });
122 updateSessionState();
123}
124
125void PlatformMediaSessionManager::endInterruption(PlatformMediaSession::EndInterruptionFlags flags)
126{
127 ALWAYS_LOG(LOGIDENTIFIER);
128
129 m_interrupted = false;
130 forEachSession([flags] (PlatformMediaSession& session, size_t) {
131 session.endInterruption(flags);
132 });
133}
134
135void PlatformMediaSessionManager::addSession(PlatformMediaSession& session)
136{
137 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier());
138
139 m_sessions.append(&session);
140 if (m_interrupted)
141 session.setState(PlatformMediaSession::Interrupted);
142
143 if (!m_remoteCommandListener)
144 m_remoteCommandListener = RemoteCommandListener::create(*this);
145
146 if (!m_audioHardwareListener)
147 m_audioHardwareListener = AudioHardwareListener::create(*this);
148
149#if !RELEASE_LOG_DISABLED
150 m_logger->addLogger(session.logger());
151#endif
152
153 updateSessionState();
154}
155
156void PlatformMediaSessionManager::removeSession(PlatformMediaSession& session)
157{
158 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier());
159
160 size_t index = m_sessions.find(&session);
161 if (index == notFound)
162 return;
163
164 if (m_iteratingOverSessions)
165 m_sessions.at(index) = nullptr;
166 else
167 m_sessions.remove(index);
168
169 if (m_sessions.isEmpty() || std::all_of(m_sessions.begin(), m_sessions.end(), std::logical_not<void>())) {
170 m_remoteCommandListener = nullptr;
171 m_audioHardwareListener = nullptr;
172#if USE(AUDIO_SESSION)
173 if (m_becameActive && shouldDeactivateAudioSession()) {
174 AudioSession::sharedSession().tryToSetActive(false);
175 m_becameActive = false;
176 }
177#endif
178 }
179
180#if !RELEASE_LOG_DISABLED
181 m_logger->removeLogger(session.logger());
182#endif
183
184 updateSessionState();
185}
186
187void PlatformMediaSessionManager::addRestriction(PlatformMediaSession::MediaType type, SessionRestrictions restriction)
188{
189 ASSERT(type > PlatformMediaSession::None && type <= PlatformMediaSession::MediaStreamCapturingAudio);
190 m_restrictions[type] |= restriction;
191}
192
193void PlatformMediaSessionManager::removeRestriction(PlatformMediaSession::MediaType type, SessionRestrictions restriction)
194{
195 ASSERT(type > PlatformMediaSession::None && type <= PlatformMediaSession::MediaStreamCapturingAudio);
196 m_restrictions[type] &= ~restriction;
197}
198
199PlatformMediaSessionManager::SessionRestrictions PlatformMediaSessionManager::restrictions(PlatformMediaSession::MediaType type)
200{
201 ASSERT(type > PlatformMediaSession::None && type <= PlatformMediaSession::MediaStreamCapturingAudio);
202 return m_restrictions[type];
203}
204
205bool PlatformMediaSessionManager::sessionWillBeginPlayback(PlatformMediaSession& session)
206{
207 setCurrentSession(session);
208
209 PlatformMediaSession::MediaType sessionType = session.mediaType();
210 SessionRestrictions restrictions = m_restrictions[sessionType];
211 if (session.state() == PlatformMediaSession::Interrupted && restrictions & InterruptedPlaybackNotPermitted) {
212 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier(), " returning false because session.state() is Interrupted, and InterruptedPlaybackNotPermitted");
213 return false;
214 }
215
216 if (m_processIsSuspended) {
217 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier(), " returning false because process is suspended");
218 return false;
219 }
220
221#if USE(AUDIO_SESSION)
222 if (activeAudioSessionRequired()) {
223 if (!AudioSession::sharedSession().tryToSetActive(true)) {
224 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier(), " returning false failed to set active AudioSession");
225 return false;
226 }
227
228 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier(), " sucessfully activated AudioSession");
229 m_becameActive = true;
230 }
231#endif
232
233 if (m_interrupted)
234 endInterruption(PlatformMediaSession::NoFlags);
235
236 forEachSession([&] (PlatformMediaSession& oneSession, size_t) {
237 if (&oneSession == &session)
238 return;
239 if (oneSession.mediaType() == sessionType
240 && restrictions & ConcurrentPlaybackNotPermitted
241 && oneSession.state() == PlatformMediaSession::Playing)
242 oneSession.pauseSession();
243 });
244
245 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier(), " returning true");
246 return true;
247}
248
249void PlatformMediaSessionManager::sessionWillEndPlayback(PlatformMediaSession& session)
250{
251 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier());
252
253 if (m_sessions.size() < 2)
254 return;
255
256 size_t pausingSessionIndex = notFound;
257 size_t lastPlayingSessionIndex = notFound;
258 anyOfSessions([&] (PlatformMediaSession& oneSession, size_t i) {
259 if (&oneSession == &session) {
260 pausingSessionIndex = i;
261 return false;
262 }
263 if (oneSession.state() == PlatformMediaSession::Playing) {
264 lastPlayingSessionIndex = i;
265 return false;
266 }
267 return oneSession.state() != PlatformMediaSession::Playing;
268 });
269 if (lastPlayingSessionIndex == notFound || pausingSessionIndex == notFound)
270 return;
271
272 if (pausingSessionIndex > lastPlayingSessionIndex)
273 return;
274
275 m_sessions.remove(pausingSessionIndex);
276 m_sessions.insert(lastPlayingSessionIndex, &session);
277
278 ALWAYS_LOG(LOGIDENTIFIER, "session moved from index ", pausingSessionIndex, " to ", lastPlayingSessionIndex);
279}
280
281void PlatformMediaSessionManager::sessionStateChanged(PlatformMediaSession&)
282{
283 updateSessionState();
284}
285
286void PlatformMediaSessionManager::setCurrentSession(PlatformMediaSession& session)
287{
288 ALWAYS_LOG(LOGIDENTIFIER, session.logIdentifier());
289
290 if (m_sessions.size() < 2)
291 return;
292
293 size_t index = m_sessions.find(&session);
294 ASSERT(index != notFound);
295 if (!index || index == notFound)
296 return;
297
298 m_sessions.remove(index);
299 m_sessions.insert(0, &session);
300 if (m_remoteCommandListener)
301 m_remoteCommandListener->updateSupportedCommands();
302
303 ALWAYS_LOG(LOGIDENTIFIER, "session moved from index ", index, " to 0");
304}
305
306PlatformMediaSession* PlatformMediaSessionManager::currentSession() const
307{
308 if (!m_sessions.size())
309 return nullptr;
310
311 return m_sessions[0];
312}
313
314Vector<PlatformMediaSession*> PlatformMediaSessionManager::currentSessionsMatching(const WTF::Function<bool(const PlatformMediaSession&)>& filter)
315{
316 Vector<PlatformMediaSession*> matchingSessions;
317 forEachSession([&] (PlatformMediaSession& session, size_t) {
318 if (filter(session))
319 matchingSessions.append(&session);
320 });
321 return matchingSessions;
322}
323
324void PlatformMediaSessionManager::applicationWillBecomeInactive() const
325{
326 ALWAYS_LOG(LOGIDENTIFIER);
327
328 forEachSession([&] (PlatformMediaSession& session, size_t) {
329 if (m_restrictions[session.mediaType()] & InactiveProcessPlaybackRestricted)
330 session.beginInterruption(PlatformMediaSession::ProcessInactive);
331 });
332}
333
334void PlatformMediaSessionManager::applicationDidBecomeActive() const
335{
336 ALWAYS_LOG(LOGIDENTIFIER);
337
338 forEachSession([&] (PlatformMediaSession& session, size_t) {
339 if (m_restrictions[session.mediaType()] & InactiveProcessPlaybackRestricted)
340 session.endInterruption(PlatformMediaSession::MayResumePlaying);
341 });
342}
343
344void PlatformMediaSessionManager::applicationDidEnterBackground(bool suspendedUnderLock) const
345{
346 ALWAYS_LOG(LOGIDENTIFIER, "suspendedUnderLock: ", suspendedUnderLock);
347
348 if (m_isApplicationInBackground)
349 return;
350
351 m_isApplicationInBackground = true;
352
353 forEachSession([&] (PlatformMediaSession& session, size_t) {
354 if (suspendedUnderLock && m_restrictions[session.mediaType()] & SuspendedUnderLockPlaybackRestricted)
355 session.beginInterruption(PlatformMediaSession::SuspendedUnderLock);
356 else if (m_restrictions[session.mediaType()] & BackgroundProcessPlaybackRestricted)
357 session.beginInterruption(PlatformMediaSession::EnteringBackground);
358 });
359}
360
361void PlatformMediaSessionManager::applicationWillEnterForeground(bool suspendedUnderLock) const
362{
363 ALWAYS_LOG(LOGIDENTIFIER, "suspendedUnderLock: ", suspendedUnderLock);
364
365 if (!m_isApplicationInBackground)
366 return;
367
368 m_isApplicationInBackground = false;
369
370 forEachSession([&] (PlatformMediaSession& session, size_t) {
371 if ((suspendedUnderLock && m_restrictions[session.mediaType()] & SuspendedUnderLockPlaybackRestricted) || m_restrictions[session.mediaType()] & BackgroundProcessPlaybackRestricted)
372 session.endInterruption(PlatformMediaSession::MayResumePlaying);
373 });
374}
375
376void PlatformMediaSessionManager::processWillSuspend()
377{
378 if (m_processIsSuspended)
379 return;
380 m_processIsSuspended = true;
381
382#if USE(AUDIO_SESSION)
383 if (m_becameActive && shouldDeactivateAudioSession()) {
384 AudioSession::sharedSession().tryToSetActive(false);
385 ALWAYS_LOG(LOGIDENTIFIER, "tried to set inactive AudioSession");
386 m_becameActive = false;
387 }
388#endif
389}
390
391void PlatformMediaSessionManager::processDidResume()
392{
393 if (!m_processIsSuspended)
394 return;
395 m_processIsSuspended = false;
396
397#if USE(AUDIO_SESSION)
398 if (!m_becameActive && activeAudioSessionRequired()) {
399 m_becameActive = AudioSession::sharedSession().tryToSetActive(true);
400 ALWAYS_LOG(LOGIDENTIFIER, "tried to set active AudioSession, ", m_becameActive ? "succeeded" : "failed");
401 }
402#endif
403}
404
405
406void PlatformMediaSessionManager::sessionIsPlayingToWirelessPlaybackTargetChanged(PlatformMediaSession& session)
407{
408 if (!m_isApplicationInBackground || !(m_restrictions[session.mediaType()] & BackgroundProcessPlaybackRestricted))
409 return;
410
411 if (session.state() != PlatformMediaSession::Interrupted)
412 session.beginInterruption(PlatformMediaSession::EnteringBackground);
413}
414
415void PlatformMediaSessionManager::sessionCanProduceAudioChanged(PlatformMediaSession&)
416{
417 updateSessionState();
418}
419
420void PlatformMediaSessionManager::didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType command, const PlatformMediaSession::RemoteCommandArgument* argument)
421{
422 PlatformMediaSession* activeSession = currentSession();
423 if (!activeSession || !activeSession->canReceiveRemoteControlCommands())
424 return;
425 activeSession->didReceiveRemoteControlCommand(command, argument);
426}
427
428bool PlatformMediaSessionManager::supportsSeeking() const
429{
430 PlatformMediaSession* activeSession = currentSession();
431 if (!activeSession)
432 return false;
433 return activeSession->supportsSeeking();
434}
435
436void PlatformMediaSessionManager::systemWillSleep()
437{
438 if (m_interrupted)
439 return;
440
441 forEachSession([] (PlatformMediaSession& session, size_t) {
442 session.beginInterruption(PlatformMediaSession::SystemSleep);
443 });
444}
445
446void PlatformMediaSessionManager::systemDidWake()
447{
448 if (m_interrupted)
449 return;
450
451 forEachSession([] (PlatformMediaSession& session, size_t) {
452 session.endInterruption(PlatformMediaSession::MayResumePlaying);
453 });
454}
455
456void PlatformMediaSessionManager::audioOutputDeviceChanged()
457{
458 updateSessionState();
459}
460
461void PlatformMediaSessionManager::stopAllMediaPlaybackForDocument(const Document* document)
462{
463 forEachSession([document] (PlatformMediaSession& session, size_t) {
464 if (session.client().hostingDocument() == document)
465 session.pauseSession();
466 });
467}
468
469void PlatformMediaSessionManager::stopAllMediaPlaybackForProcess()
470{
471 forEachSession([] (PlatformMediaSession& session, size_t) {
472 session.stopSession();
473 });
474}
475
476void PlatformMediaSessionManager::suspendAllMediaPlaybackForDocument(const Document& document)
477{
478 forEachSession([&] (PlatformMediaSession& session, size_t) {
479 if (session.client().hostingDocument() == &document)
480 session.beginInterruption(PlatformMediaSession::PlaybackSuspended);
481 });
482}
483
484void PlatformMediaSessionManager::resumeAllMediaPlaybackForDocument(const Document& document)
485{
486 forEachSession([&] (PlatformMediaSession& session, size_t) {
487 if (session.client().hostingDocument() == &document)
488 session.endInterruption(PlatformMediaSession::MayResumePlaying);
489 });
490}
491
492void PlatformMediaSessionManager::suspendAllMediaBufferingForDocument(const Document& document)
493{
494 forEachSession([&] (PlatformMediaSession& session, size_t) {
495 if (session.client().hostingDocument() == &document)
496 session.suspendBuffering();
497 });
498}
499
500void PlatformMediaSessionManager::resumeAllMediaBufferingForDocument(const Document& document)
501{
502 forEachSession([&] (PlatformMediaSession& session, size_t) {
503 if (session.client().hostingDocument() == &document)
504 session.resumeBuffering();
505 });
506}
507
508void PlatformMediaSessionManager::forEachSession(const Function<void(PlatformMediaSession&, size_t)>& predicate) const
509{
510 ++m_iteratingOverSessions;
511
512 for (size_t i = 0, size = m_sessions.size(); i < size; ++i) {
513 auto session = m_sessions[i];
514 if (!session)
515 continue;
516 predicate(*session, i);
517 }
518
519 --m_iteratingOverSessions;
520 if (!m_iteratingOverSessions)
521 m_sessions.removeAll(nullptr);
522}
523
524PlatformMediaSession* PlatformMediaSessionManager::findSession(const Function<bool(PlatformMediaSession&, size_t)>& predicate) const
525{
526 ++m_iteratingOverSessions;
527
528 PlatformMediaSession* foundSession = nullptr;
529 for (size_t i = 0, size = m_sessions.size(); i < size; ++i) {
530 auto session = m_sessions[i];
531 if (!session)
532 continue;
533
534 if (!predicate(*session, i))
535 continue;
536
537 foundSession = session;
538 break;
539 }
540
541 --m_iteratingOverSessions;
542 if (!m_iteratingOverSessions)
543 m_sessions.removeAll(nullptr);
544
545 return foundSession;
546}
547
548static bool& deactivateAudioSession()
549{
550 static bool deactivate;
551 return deactivate;
552}
553
554bool PlatformMediaSessionManager::shouldDeactivateAudioSession()
555{
556 return deactivateAudioSession();
557}
558
559void PlatformMediaSessionManager::setShouldDeactivateAudioSession(bool deactivate)
560{
561 deactivateAudioSession() = deactivate;
562}
563
564#else // ENABLE(VIDEO) || ENABLE(WEB_AUDIO)
565
566void PlatformMediaSessionManager::updateNowPlayingInfoIfNecessary()
567{
568
569}
570
571#endif // ENABLE(VIDEO) || ENABLE(WEB_AUDIO)
572
573#if !RELEASE_LOG_DISABLED
574WTFLogChannel& PlatformMediaSessionManager::logChannel() const
575{
576 return LogMedia;
577}
578#endif
579
580} // namespace WebCore
581