b97434c4e6245d73ccf2d625a0a1b03e1290cbd3
[WebKit-https.git] / Source / WebCore / Modules / mediasession / WebMediaSessionManager.cpp
1 /*
2  * Copyright (C) 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 #include "config.h"
27 #include "WebMediaSessionManager.h"
28
29 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)
30
31 #include "FloatRect.h"
32 #include "Logging.h"
33 #include "MediaPlaybackTargetPickerMock.h"
34 #include "WebMediaSessionManagerClient.h"
35 #include <wtf/text/StringBuilder.h>
36
37 namespace WebCore {
38
39 static const Seconds taskDelayInterval { 100_ms };
40
41 struct ClientState {
42     WTF_MAKE_STRUCT_FAST_ALLOCATED;
43
44     explicit ClientState(WebMediaSessionManagerClient& client, uint64_t contextId)
45         : client(client)
46         , contextId(contextId)
47     {
48     }
49
50     bool operator == (ClientState const& other) const
51     {
52         return contextId == other.contextId && &client == &other.client;
53     }
54
55     WebMediaSessionManagerClient& client;
56     uint64_t contextId { 0 };
57     WebCore::MediaProducer::MediaStateFlags flags { WebCore::MediaProducer::IsNotPlaying };
58     bool requestedPicker { false };
59     bool previouslyRequestedPicker { false };
60     bool configurationRequired { true };
61     bool playedToEnd { false };
62 };
63
64 static bool flagsAreSet(MediaProducer::MediaStateFlags value, unsigned flags)
65 {
66     return value & flags;
67 }
68
69 #if !LOG_DISABLED
70 static String mediaProducerStateString(MediaProducer::MediaStateFlags flags)
71 {
72     StringBuilder string;
73     if (flags & MediaProducer::IsPlayingAudio)
74         string.append("IsPlayingAudio + ");
75     if (flags & MediaProducer::IsPlayingVideo)
76         string.append("IsPlayingVideo + ");
77     if (flags & MediaProducer::IsPlayingToExternalDevice)
78         string.append("IsPlayingToExternalDevice + ");
79     if (flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
80         string.append("HasPlaybackTargetAvailabilityListener + ");
81     if (flags & MediaProducer::RequiresPlaybackTargetMonitoring)
82         string.append("RequiresPlaybackTargetMonitoring + ");
83     if (flags & MediaProducer::ExternalDeviceAutoPlayCandidate)
84         string.append("ExternalDeviceAutoPlayCandidate + ");
85     if (flags & MediaProducer::DidPlayToEnd)
86         string.append("DidPlayToEnd + ");
87     if (flags & MediaProducer::HasAudioOrVideo)
88         string.append("HasAudioOrVideo + ");
89     if (string.isEmpty())
90         string.append("IsNotPlaying");
91     else
92         string.resize(string.length() - 2);
93
94     return string.toString();
95 }
96 #endif
97
98 void WebMediaSessionManager::setMockMediaPlaybackTargetPickerEnabled(bool enabled)
99 {
100     LOG(Media, "WebMediaSessionManager::setMockMediaPlaybackTargetPickerEnabled - enabled = %i", (int)enabled);
101
102     if (m_mockPickerEnabled == enabled)
103         return;
104
105     m_mockPickerEnabled = enabled;
106 }
107
108 void WebMediaSessionManager::setMockMediaPlaybackTargetPickerState(const String& name, MediaPlaybackTargetContext::State state)
109 {
110     LOG(Media, "WebMediaSessionManager::setMockMediaPlaybackTargetPickerState - name = %s, state = %i", name.utf8().data(), (int)state);
111
112     mockPicker().setState(name, state);
113 }
114
115 MediaPlaybackTargetPickerMock& WebMediaSessionManager::mockPicker()
116 {
117     if (!m_pickerOverride)
118         m_pickerOverride = makeUnique<MediaPlaybackTargetPickerMock>(*this);
119
120     return *m_pickerOverride.get();
121 }
122
123 WebCore::MediaPlaybackTargetPicker& WebMediaSessionManager::targetPicker()
124 {
125     if (m_mockPickerEnabled)
126         return mockPicker();
127
128     return platformPicker();
129 }
130
131 WebMediaSessionManager::WebMediaSessionManager()
132     : m_taskTimer(RunLoop::current(), this, &WebMediaSessionManager::taskTimerFired)
133     , m_watchdogTimer(RunLoop::current(), this, &WebMediaSessionManager::watchdogTimerFired)
134 {
135 }
136
137 WebMediaSessionManager::~WebMediaSessionManager() = default;
138
139 uint64_t WebMediaSessionManager::addPlaybackTargetPickerClient(WebMediaSessionManagerClient& client, uint64_t contextId)
140 {
141     size_t index = find(&client, contextId);
142     ASSERT(index == notFound);
143     if (index != notFound)
144         return 0;
145
146     LOG(Media, "WebMediaSessionManager::addPlaybackTargetPickerClient(%p + %llu)", &client, contextId);
147
148     m_clientState.append(makeUnique<ClientState>(client, contextId));
149
150     if (m_externalOutputDeviceAvailable || m_playbackTarget)
151         scheduleDelayedTask(InitialConfigurationTask | TargetClientsConfigurationTask);
152
153     return contextId;
154 }
155
156 void WebMediaSessionManager::removePlaybackTargetPickerClient(WebMediaSessionManagerClient& client, uint64_t contextId)
157 {
158     size_t index = find(&client, contextId);
159     ASSERT(index != notFound);
160     if (index == notFound)
161         return;
162
163     LOG(Media, "WebMediaSessionManager::removePlaybackTargetPickerClient(%p + %llu)", &client, contextId);
164
165     m_clientState.remove(index);
166     scheduleDelayedTask(TargetMonitoringConfigurationTask | TargetClientsConfigurationTask);
167 }
168
169 void WebMediaSessionManager::removeAllPlaybackTargetPickerClients(WebMediaSessionManagerClient& client)
170 {
171     if (m_clientState.isEmpty())
172         return;
173
174     LOG(Media, "WebMediaSessionManager::removeAllPlaybackTargetPickerClients(%p)", &client);
175
176     for (size_t i = m_clientState.size(); i > 0; --i) {
177         if (&m_clientState[i - 1]->client == &client)
178             m_clientState.remove(i - 1);
179     }
180     scheduleDelayedTask(TargetMonitoringConfigurationTask | TargetClientsConfigurationTask);
181 }
182
183 void WebMediaSessionManager::showPlaybackTargetPicker(WebMediaSessionManagerClient& client, uint64_t contextId, const IntRect& rect, bool, bool useDarkAppearance)
184 {
185     size_t index = find(&client, contextId);
186     ASSERT(index != notFound);
187     if (index == notFound)
188         return;
189
190     auto& clientRequestingPicker = m_clientState[index];
191     for (auto& state : m_clientState) {
192         state->requestedPicker = state == clientRequestingPicker;
193         state->previouslyRequestedPicker = state == clientRequestingPicker;
194     }
195
196     bool hasActiveRoute = flagsAreSet(m_clientState[index]->flags, MediaProducer::IsPlayingToExternalDevice);
197     LOG(Media, "WebMediaSessionManager::showPlaybackTargetPicker(%p + %llu) - hasActiveRoute = %i", &client, contextId, (int)hasActiveRoute);
198     targetPicker().showPlaybackTargetPicker(FloatRect(rect), hasActiveRoute, useDarkAppearance);
199 }
200
201 void WebMediaSessionManager::clientStateDidChange(WebMediaSessionManagerClient& client, uint64_t contextId, MediaProducer::MediaStateFlags newFlags)
202 {
203     size_t index = find(&client, contextId);
204     ASSERT(index != notFound);
205     if (index == notFound)
206         return;
207
208     auto& changedClientState = m_clientState[index];
209     MediaProducer::MediaStateFlags oldFlags = changedClientState->flags;
210     if (newFlags == oldFlags)
211         return;
212
213     LOG(Media, "WebMediaSessionManager::clientStateDidChange(%p + %llu) - new flags = %s, old flags = %s", &client, contextId, mediaProducerStateString(newFlags).utf8().data(), mediaProducerStateString(oldFlags).utf8().data());
214
215     changedClientState->flags = newFlags;
216
217     MediaProducer::MediaStateFlags updateConfigurationFlags = MediaProducer::RequiresPlaybackTargetMonitoring | MediaProducer::HasPlaybackTargetAvailabilityListener | MediaProducer::HasAudioOrVideo;
218     if ((oldFlags & updateConfigurationFlags) != (newFlags & updateConfigurationFlags))
219         scheduleDelayedTask(TargetMonitoringConfigurationTask);
220
221     MediaProducer::MediaStateFlags playingToTargetFlags = MediaProducer::IsPlayingToExternalDevice | MediaProducer::IsPlayingVideo;
222     if ((oldFlags & playingToTargetFlags) != (newFlags & playingToTargetFlags)) {
223         if (flagsAreSet(oldFlags, MediaProducer::IsPlayingVideo) && !flagsAreSet(newFlags, MediaProducer::IsPlayingVideo) && flagsAreSet(newFlags, MediaProducer::DidPlayToEnd))
224             changedClientState->playedToEnd = true;
225         scheduleDelayedTask(WatchdogTimerConfigurationTask);
226     }
227
228     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute() || !flagsAreSet(newFlags, MediaProducer::ExternalDeviceAutoPlayCandidate))
229         return;
230
231     // Do not interrupt another element already playing to a device.
232     for (auto& state : m_clientState) {
233         if (state == changedClientState)
234             continue;
235
236         if (flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice) && flagsAreSet(state->flags, MediaProducer::IsPlayingVideo))
237             return;
238     }
239
240     // Do not begin playing to the device unless playback has just started.
241     if (!flagsAreSet(newFlags, MediaProducer::IsPlayingVideo) || flagsAreSet(oldFlags, MediaProducer::IsPlayingVideo))
242         return;
243
244     for (auto& state : m_clientState) {
245         if (state == changedClientState)
246             continue;
247         state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
248     }
249
250     changedClientState->client.setShouldPlayToPlaybackTarget(changedClientState->contextId, true);
251
252     if (index && m_clientState.size() > 1)
253         std::swap(m_clientState.at(index), m_clientState.at(0));
254 }
255
256 void WebMediaSessionManager::setPlaybackTarget(Ref<MediaPlaybackTarget>&& target)
257 {
258     m_playbackTarget = WTFMove(target);
259     m_targetChanged = true;
260     scheduleDelayedTask(TargetClientsConfigurationTask);
261 }
262
263 void WebMediaSessionManager::externalOutputDeviceAvailableDidChange(bool available)
264 {
265     LOG(Media, "WebMediaSessionManager::externalOutputDeviceAvailableDidChange - clients = %zu, available = %i", m_clientState.size(), (int)available);
266
267     m_externalOutputDeviceAvailable = available;
268     for (auto& state : m_clientState)
269         state->client.externalOutputDeviceAvailableDidChange(state->contextId, available);
270 }
271
272 void WebMediaSessionManager::configureNewClients()
273 {
274     for (auto& state : m_clientState) {
275         if (!state->configurationRequired)
276             continue;
277
278         state->configurationRequired = false;
279         if (m_externalOutputDeviceAvailable)
280             state->client.externalOutputDeviceAvailableDidChange(state->contextId, true);
281
282         if (m_playbackTarget)
283             state->client.setPlaybackTarget(state->contextId, *m_playbackTarget.copyRef());
284     }
285 }
286
287 void WebMediaSessionManager::configurePlaybackTargetClients()
288 {
289     if (m_clientState.isEmpty())
290         return;
291
292     size_t indexOfClientThatRequestedPicker = notFound;
293     size_t indexOfLastClientToRequestPicker = notFound;
294     size_t indexOfClientWillPlayToTarget = notFound;
295     bool haveActiveRoute = m_playbackTarget && m_playbackTarget->hasActiveRoute();
296
297     for (size_t i = 0; i < m_clientState.size(); ++i) {
298         auto& state = m_clientState[i];
299
300         LOG(Media, "WebMediaSessionManager::configurePlaybackTargetClients %zu - client (%p + %llu) requestedPicker = %i, flags = %s", i, &state->client, state->contextId, state->requestedPicker, mediaProducerStateString(state->flags).utf8().data());
301
302         if (m_targetChanged && state->requestedPicker)
303             indexOfClientThatRequestedPicker = i;
304
305         if (indexOfClientWillPlayToTarget == notFound && flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice))
306             indexOfClientWillPlayToTarget = i;
307
308         if (indexOfClientWillPlayToTarget == notFound && haveActiveRoute && state->previouslyRequestedPicker)
309             indexOfLastClientToRequestPicker = i;
310     }
311
312     if (indexOfClientThatRequestedPicker != notFound)
313         indexOfClientWillPlayToTarget = indexOfClientThatRequestedPicker;
314     if (indexOfClientWillPlayToTarget == notFound && indexOfLastClientToRequestPicker != notFound)
315         indexOfClientWillPlayToTarget = indexOfLastClientToRequestPicker;
316     if (indexOfClientWillPlayToTarget == notFound && haveActiveRoute && flagsAreSet(m_clientState[0]->flags, MediaProducer::ExternalDeviceAutoPlayCandidate) && !flagsAreSet(m_clientState[0]->flags, MediaProducer::IsPlayingVideo))
317         indexOfClientWillPlayToTarget = 0;
318
319     LOG(Media, "WebMediaSessionManager::configurePlaybackTargetClients - indexOfClientWillPlayToTarget = %zu", indexOfClientWillPlayToTarget);
320
321     for (size_t i = 0; i < m_clientState.size(); ++i) {
322         auto& state = m_clientState[i];
323
324         if (m_playbackTarget)
325             state->client.setPlaybackTarget(state->contextId, *m_playbackTarget.copyRef());
326
327         if (i != indexOfClientWillPlayToTarget || !haveActiveRoute)
328             state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
329
330         state->configurationRequired = false;
331         if (m_targetChanged)
332             state->requestedPicker = false;
333     }
334
335     if (haveActiveRoute && indexOfClientWillPlayToTarget != notFound) {
336         auto& state = m_clientState[indexOfClientWillPlayToTarget];
337         if (!flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice))
338             state->client.setShouldPlayToPlaybackTarget(state->contextId, true);
339     }
340
341     m_targetChanged = false;
342     configureWatchdogTimer();
343 }
344
345 void WebMediaSessionManager::configurePlaybackTargetMonitoring()
346 {
347     bool monitoringRequired = false;
348     bool hasAvailabilityListener = false;
349     bool haveClientWithMedia = false;
350     for (auto& state : m_clientState) {
351         if (state->flags & MediaProducer::RequiresPlaybackTargetMonitoring) {
352             monitoringRequired = true;
353             break;
354         }
355         if (state->flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
356             hasAvailabilityListener = true;
357         if (state->flags & MediaProducer::HasAudioOrVideo)
358             haveClientWithMedia = true;
359     }
360
361     LOG(Media, "WebMediaSessionManager::configurePlaybackTargetMonitoring - monitoringRequired = %i", static_cast<int>(monitoringRequired || (hasAvailabilityListener && haveClientWithMedia)));
362
363     if (monitoringRequired || (hasAvailabilityListener && haveClientWithMedia))
364         targetPicker().startingMonitoringPlaybackTargets();
365     else
366         targetPicker().stopMonitoringPlaybackTargets();
367 }
368
369 #if !LOG_DISABLED
370 String WebMediaSessionManager::toString(ConfigurationTasks tasks)
371 {
372     StringBuilder string;
373     if (tasks & InitialConfigurationTask)
374         string.append("InitialConfigurationTask + ");
375     if (tasks & TargetClientsConfigurationTask)
376         string.append("TargetClientsConfigurationTask + ");
377     if (tasks & TargetMonitoringConfigurationTask)
378         string.append("TargetMonitoringConfigurationTask + ");
379     if (tasks & WatchdogTimerConfigurationTask)
380         string.append("WatchdogTimerConfigurationTask + ");
381     if (string.isEmpty())
382         string.append("NoTask");
383     else
384         string.resize(string.length() - 2);
385     
386     return string.toString();
387 }
388 #endif
389
390 void WebMediaSessionManager::scheduleDelayedTask(ConfigurationTasks tasks)
391 {
392     LOG(Media, "WebMediaSessionManager::scheduleDelayedTask - %s", toString(tasks).utf8().data());
393
394     m_taskFlags |= tasks;
395     m_taskTimer.startOneShot(taskDelayInterval);
396 }
397
398 void WebMediaSessionManager::taskTimerFired()
399 {
400     LOG(Media, "WebMediaSessionManager::taskTimerFired - tasks = %s", toString(m_taskFlags).utf8().data());
401
402     if (m_taskFlags & InitialConfigurationTask)
403         configureNewClients();
404     if (m_taskFlags & TargetClientsConfigurationTask)
405         configurePlaybackTargetClients();
406     if (m_taskFlags & TargetMonitoringConfigurationTask)
407         configurePlaybackTargetMonitoring();
408     if (m_taskFlags & WatchdogTimerConfigurationTask)
409         configureWatchdogTimer();
410
411     m_taskFlags = NoTask;
412 }
413
414 size_t WebMediaSessionManager::find(WebMediaSessionManagerClient* client, uint64_t contextId)
415 {
416     for (size_t i = 0; i < m_clientState.size(); ++i) {
417         if (m_clientState[i]->contextId == contextId && &m_clientState[i]->client == client)
418             return i;
419     }
420
421     return notFound;
422 }
423
424 void WebMediaSessionManager::configureWatchdogTimer()
425 {
426     static const Seconds watchdogTimerIntervalAfterPausing { 1_h };
427     static const Seconds watchdogTimerIntervalAfterPlayingToEnd { 8_min };
428
429     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute()) {
430         m_watchdogTimer.stop();
431         return;
432     }
433
434     bool stopTimer = false;
435     bool didPlayToEnd = false;
436     for (auto& state : m_clientState) {
437         if (flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice) && flagsAreSet(state->flags, MediaProducer::IsPlayingVideo))
438             stopTimer = true;
439         if (state->playedToEnd)
440             didPlayToEnd = true;
441         state->playedToEnd = false;
442     }
443
444     if (stopTimer) {
445         m_currentWatchdogInterval = { };
446         m_watchdogTimer.stop();
447         LOG(Media, "WebMediaSessionManager::configureWatchdogTimer - timer stopped");
448     } else {
449         Seconds interval = didPlayToEnd ? watchdogTimerIntervalAfterPlayingToEnd : watchdogTimerIntervalAfterPausing;
450         if (interval != m_currentWatchdogInterval || !m_watchdogTimer.isActive()) {
451             m_watchdogTimer.startOneShot(interval);
452             LOG(Media, "WebMediaSessionManager::configureWatchdogTimer - timer scheduled for %.0f seconds", interval.value());
453         }
454         m_currentWatchdogInterval = interval;
455     }
456 }
457
458 void WebMediaSessionManager::watchdogTimerFired()
459 {
460     LOG(Media, "WebMediaSessionManager::watchdogTimerFired");
461     if (!m_playbackTarget)
462         return;
463
464     targetPicker().invalidatePlaybackTargets();
465 }
466
467 } // namespace WebCore
468
469 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)