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