Enable USE_MEDIAREMOTE on iOS
[WebKit-https.git] / Source / WebCore / platform / audio / cocoa / MediaSessionManagerCocoa.mm
1 /*
2  * Copyright (C) 2013-2014 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 "MediaSessionManagerCocoa.h"
28
29 #if USE(AUDIO_SESSION) && PLATFORM(COCOA)
30
31 #include "AudioSession.h"
32 #include "DeprecatedGlobalSettings.h"
33 #include "HTMLMediaElement.h"
34 #include "Logging.h"
35 #include "MediaPlayer.h"
36 #include "PlatformMediaSession.h"
37 #include <wtf/BlockObjCExceptions.h>
38 #include <wtf/Function.h>
39
40 #include "MediaRemoteSoftLink.h"
41
42 using namespace WebCore;
43
44 static const size_t kWebAudioBufferSize = 128;
45 static const size_t kLowPowerVideoBufferSize = 4096;
46
47 #if PLATFORM(MAC)
48 static MediaSessionManagerCocoa* platformMediaSessionManager = nullptr;
49
50 PlatformMediaSessionManager& PlatformMediaSessionManager::sharedManager()
51 {
52     if (!platformMediaSessionManager)
53         platformMediaSessionManager = new MediaSessionManagerCocoa;
54     return *platformMediaSessionManager;
55 }
56
57 PlatformMediaSessionManager* PlatformMediaSessionManager::sharedManagerIfExists()
58 {
59     return platformMediaSessionManager;
60 }
61 #endif
62
63 void MediaSessionManagerCocoa::updateSessionState()
64 {
65     LOG(Media, "PlatformMediaSessionManager::scheduleUpdateSessionState() - types: Video(%d), Audio(%d), WebAudio(%d)", count(PlatformMediaSession::Video), count(PlatformMediaSession::Audio), count(PlatformMediaSession::WebAudio));
66
67     if (has(PlatformMediaSession::WebAudio))
68         AudioSession::sharedSession().setPreferredBufferSize(kWebAudioBufferSize);
69     // In case of audio capture, we want to grab 20 ms chunks to limit the latency so that it is not noticeable by users
70     // while having a large enough buffer so that the audio rendering remains stable, hence a computation based on sample rate.
71     else if (has(PlatformMediaSession::MediaStreamCapturingAudio))
72         AudioSession::sharedSession().setPreferredBufferSize(AudioSession::sharedSession().sampleRate() / 50);
73     else if ((has(PlatformMediaSession::VideoAudio) || has(PlatformMediaSession::Audio)) && DeprecatedGlobalSettings::lowPowerVideoAudioBufferSizeEnabled()) {
74         // FIXME: <http://webkit.org/b/116725> Figure out why enabling the code below
75         // causes media LayoutTests to fail on 10.8.
76
77         size_t bufferSize;
78         if (audioHardwareListener() && audioHardwareListener()->outputDeviceSupportsLowPowerMode())
79             bufferSize = kLowPowerVideoBufferSize;
80         else
81             bufferSize = kWebAudioBufferSize;
82
83         AudioSession::sharedSession().setPreferredBufferSize(bufferSize);
84     }
85
86     if (!DeprecatedGlobalSettings::shouldManageAudioSessionCategory())
87         return;
88
89     bool hasWebAudioType = false;
90     bool hasAudibleAudioOrVideoMediaType = false;
91     bool hasAudioCapture = anyOfSessions([&hasWebAudioType, &hasAudibleAudioOrVideoMediaType] (PlatformMediaSession& session, size_t) mutable {
92         auto type = session.mediaType();
93         if (type == PlatformMediaSession::WebAudio)
94             hasWebAudioType = true;
95         if ((type == PlatformMediaSession::VideoAudio || type == PlatformMediaSession::Audio) && session.canProduceAudio() && session.hasPlayedSinceLastInterruption())
96             hasAudibleAudioOrVideoMediaType = true;
97         if (session.isPlayingToWirelessPlaybackTarget())
98             hasAudibleAudioOrVideoMediaType = true;
99         return (type == PlatformMediaSession::MediaStreamCapturingAudio);
100     });
101
102     if (hasAudioCapture)
103         AudioSession::sharedSession().setCategory(AudioSession::PlayAndRecord);
104     else if (hasAudibleAudioOrVideoMediaType)
105         AudioSession::sharedSession().setCategory(AudioSession::MediaPlayback);
106     else if (hasWebAudioType)
107         AudioSession::sharedSession().setCategory(AudioSession::AmbientSound);
108     else
109         AudioSession::sharedSession().setCategory(AudioSession::None);
110 }
111
112 void MediaSessionManagerCocoa::beginInterruption(PlatformMediaSession::InterruptionType type)
113 {
114     if (type == PlatformMediaSession::InterruptionType::SystemInterruption) {
115         forEachSession([] (PlatformMediaSession& session, size_t) {
116             session.clearHasPlayedSinceLastInterruption();
117         });
118     }
119
120     PlatformMediaSessionManager::beginInterruption(type);
121 }
122
123 void MediaSessionManagerCocoa::scheduleUpdateNowPlayingInfo()
124 {
125     if (!m_nowPlayingUpdateTaskQueue.hasPendingTasks())
126         m_nowPlayingUpdateTaskQueue.enqueueTask(std::bind(&MediaSessionManagerCocoa::updateNowPlayingInfo, this));
127 }
128
129 bool MediaSessionManagerCocoa::sessionWillBeginPlayback(PlatformMediaSession& session)
130 {
131     if (!PlatformMediaSessionManager::sessionWillBeginPlayback(session))
132         return false;
133     
134     LOG(Media, "MediaSessionManagerCocoa::sessionWillBeginPlayback");
135     scheduleUpdateNowPlayingInfo();
136     return true;
137 }
138
139 void MediaSessionManagerCocoa::sessionDidEndRemoteScrubbing(const PlatformMediaSession&)
140 {
141     scheduleUpdateNowPlayingInfo();
142 }
143
144 void MediaSessionManagerCocoa::removeSession(PlatformMediaSession& session)
145 {
146     PlatformMediaSessionManager::removeSession(session);
147     LOG(Media, "MediaSessionManagerCocoa::removeSession");
148     scheduleUpdateNowPlayingInfo();
149 }
150
151 void MediaSessionManagerCocoa::sessionWillEndPlayback(PlatformMediaSession& session)
152 {
153     PlatformMediaSessionManager::sessionWillEndPlayback(session);
154     LOG(Media, "MediaSessionManagerCocoa::sessionWillEndPlayback");
155     updateNowPlayingInfo();
156 }
157
158 void MediaSessionManagerCocoa::clientCharacteristicsChanged(PlatformMediaSession&)
159 {
160     LOG(Media, "MediaSessionManagerCocoa::clientCharacteristicsChanged");
161     scheduleUpdateNowPlayingInfo();
162 }
163
164 void MediaSessionManagerCocoa::sessionCanProduceAudioChanged(PlatformMediaSession& session)
165 {
166     PlatformMediaSessionManager::sessionCanProduceAudioChanged(session);
167     scheduleUpdateNowPlayingInfo();
168 }
169
170 PlatformMediaSession* MediaSessionManagerCocoa::nowPlayingEligibleSession()
171 {
172     if (auto element = HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose::NowPlaying))
173         return &element->mediaSession();
174
175     return nullptr;
176 }
177
178 void MediaSessionManagerCocoa::updateNowPlayingInfo()
179 {
180 #if USE(MEDIAREMOTE)
181     if (!isMediaRemoteFrameworkAvailable())
182         return;
183
184     BEGIN_BLOCK_OBJC_EXCEPTIONS
185
186     const PlatformMediaSession* currentSession = this->nowPlayingEligibleSession();
187
188     LOG(Media, "MediaSessionManagerCocoa::updateNowPlayingInfo - currentSession = %p", currentSession);
189
190     if (!currentSession) {
191         if (canLoad_MediaRemote_MRMediaRemoteSetNowPlayingVisibility())
192             MRMediaRemoteSetNowPlayingVisibility(MRMediaRemoteGetLocalOrigin(), MRNowPlayingClientVisibilityNeverVisible);
193
194         LOG(Media, "MediaSessionManagerCocoa::updateNowPlayingInfo - clearing now playing info");
195
196         MRMediaRemoteSetCanBeNowPlayingApplication(false);
197         m_registeredAsNowPlayingApplication = false;
198
199         MRMediaRemoteSetNowPlayingInfo(nullptr);
200         m_nowPlayingActive = false;
201         m_lastUpdatedNowPlayingTitle = emptyString();
202         m_lastUpdatedNowPlayingDuration = NAN;
203         m_lastUpdatedNowPlayingElapsedTime = NAN;
204         m_lastUpdatedNowPlayingInfoUniqueIdentifier = 0;
205         MRMediaRemoteSetNowPlayingApplicationPlaybackStateForOrigin(MRMediaRemoteGetLocalOrigin(), kMRPlaybackStateStopped, dispatch_get_main_queue(), ^(MRMediaRemoteError error) {
206 #if LOG_DISABLED
207             UNUSED_PARAM(error);
208 #else
209             if (error)
210                 LOG(Media, "MediaSessionManagerCocoa::updateNowPlayingInfo - MRMediaRemoteSetNowPlayingApplicationPlaybackStateForOrigin(stopped) failed with error %ud", error);
211 #endif
212         });
213
214         return;
215     }
216
217     if (!m_registeredAsNowPlayingApplication) {
218         m_registeredAsNowPlayingApplication = true;
219         MRMediaRemoteSetCanBeNowPlayingApplication(true);
220     }
221
222     String title = currentSession->title();
223     double duration = currentSession->supportsSeeking() ? currentSession->duration() : MediaPlayer::invalidTime();
224     double rate = currentSession->state() == PlatformMediaSession::Playing ? 1 : 0;
225     auto info = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
226
227     if (!title.isEmpty()) {
228         CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoTitle, title.createCFString().get());
229         m_lastUpdatedNowPlayingTitle = title;
230     }
231
232     if (std::isfinite(duration) && duration != MediaPlayer::invalidTime()) {
233         auto cfDuration = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &duration));
234         CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoDuration, cfDuration.get());
235         m_lastUpdatedNowPlayingDuration = duration;
236     }
237
238     auto cfRate = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &rate));
239     CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoPlaybackRate, cfRate.get());
240
241     m_lastUpdatedNowPlayingInfoUniqueIdentifier = currentSession->uniqueIdentifier();
242     auto cfIdentifier = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &m_lastUpdatedNowPlayingInfoUniqueIdentifier));
243     CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoUniqueIdentifier, cfIdentifier.get());
244
245     double currentTime = currentSession->currentTime();
246     if (std::isfinite(currentTime) && currentTime != MediaPlayer::invalidTime() && currentSession->supportsSeeking()) {
247         auto cfCurrentTime = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &currentTime));
248         CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoElapsedTime, cfCurrentTime.get());
249         m_lastUpdatedNowPlayingElapsedTime = currentTime;
250     }
251
252     LOG(Media, "MediaSessionManagerCocoa::updateNowPlayingInfo - title = \"%s\", rate = %f, duration = %f, now = %f",
253         title.utf8().data(), rate, duration, currentTime);
254
255     String parentApplication = currentSession->sourceApplicationIdentifier();
256     if (canLoad_MediaRemote_MRMediaRemoteSetParentApplication() && !parentApplication.isEmpty())
257         MRMediaRemoteSetParentApplication(MRMediaRemoteGetLocalOrigin(), parentApplication.createCFString().get());
258
259     m_nowPlayingActive = currentSession->allowsNowPlayingControlsVisibility();
260     MRPlaybackState playbackState = (currentSession->state() == PlatformMediaSession::Playing) ? kMRPlaybackStatePlaying : kMRPlaybackStatePaused;
261     MRMediaRemoteSetNowPlayingApplicationPlaybackStateForOrigin(MRMediaRemoteGetLocalOrigin(), playbackState, dispatch_get_main_queue(), ^(MRMediaRemoteError error) {
262 #if LOG_DISABLED
263         UNUSED_PARAM(error);
264 #else
265         LOG(Media, "MediaSessionManagerCocoa::updateNowPlayingInfo - MRMediaRemoteSetNowPlayingApplicationPlaybackStateForOrigin(playing) failed with error %ud", error);
266 #endif
267     });
268     MRMediaRemoteSetNowPlayingInfo(info.get());
269
270     if (canLoad_MediaRemote_MRMediaRemoteSetNowPlayingVisibility()) {
271         MRNowPlayingClientVisibility visibility = currentSession->allowsNowPlayingControlsVisibility() ? MRNowPlayingClientVisibilityAlwaysVisible : MRNowPlayingClientVisibilityNeverVisible;
272         MRMediaRemoteSetNowPlayingVisibility(MRMediaRemoteGetLocalOrigin(), visibility);
273     }
274     END_BLOCK_OBJC_EXCEPTIONS
275 #endif // USE(MEDIAREMOTE)
276 }
277
278 #endif // USE(AUDIO_SESSION)