2 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "WebMediaSessionManager.h"
29 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
31 #include "FloatRect.h"
33 #include "MediaPlaybackTargetPickerMock.h"
34 #include "WebMediaSessionManagerClient.h"
35 #include <wtf/text/StringBuilder.h>
39 static const double taskDelayInterval = 1.0 / 10.0;
42 explicit ClientState(WebMediaSessionManagerClient& client, uint64_t contextId)
44 , contextId(contextId)
48 bool operator == (ClientState const& other) const
50 return contextId == other.contextId && &client == &other.client;
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 };
62 static bool flagsAreSet(MediaProducer::MediaStateFlags value, unsigned flags)
68 static String mediaProducerStateString(MediaProducer::MediaStateFlags flags)
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 + ");
88 string.append("IsNotPlaying");
90 string.resize(string.length() - 2);
92 return string.toString();
96 void WebMediaSessionManager::setMockMediaPlaybackTargetPickerEnabled(bool enabled)
98 LOG(Media, "WebMediaSessionManager::setMockMediaPlaybackTargetPickerEnabled - enabled = %i", (int)enabled);
100 if (m_mockPickerEnabled == enabled)
103 m_mockPickerEnabled = enabled;
106 void WebMediaSessionManager::setMockMediaPlaybackTargetPickerState(const String& name, MediaPlaybackTargetContext::State state)
108 LOG(Media, "WebMediaSessionManager::setMockMediaPlaybackTargetPickerState - name = %s, state = %i", name.utf8().data(), (int)state);
110 mockPicker().setState(name, state);
113 MediaPlaybackTargetPickerMock& WebMediaSessionManager::mockPicker()
115 if (!m_pickerOverride)
116 m_pickerOverride = std::make_unique<MediaPlaybackTargetPickerMock>(*this);
118 return *m_pickerOverride.get();
121 WebCore::MediaPlaybackTargetPicker& WebMediaSessionManager::targetPicker()
123 if (m_mockPickerEnabled)
126 return platformPicker();
129 WebMediaSessionManager::WebMediaSessionManager()
130 : m_taskTimer(RunLoop::current(), this, &WebMediaSessionManager::taskTimerFired)
131 , m_watchdogTimer(RunLoop::current(), this, &WebMediaSessionManager::watchdogTimerFired)
135 WebMediaSessionManager::~WebMediaSessionManager()
139 uint64_t WebMediaSessionManager::addPlaybackTargetPickerClient(WebMediaSessionManagerClient& client, uint64_t contextId)
141 size_t index = find(&client, contextId);
142 ASSERT(index == notFound);
143 if (index != notFound)
146 LOG(Media, "WebMediaSessionManager::addPlaybackTargetPickerClient(%p + %llu)", &client, contextId);
148 m_clientState.append(std::make_unique<ClientState>(client, contextId));
150 if (m_externalOutputDeviceAvailable || m_playbackTarget)
151 scheduleDelayedTask(InitialConfigurationTask | TargetClientsConfigurationTask);
156 void WebMediaSessionManager::removePlaybackTargetPickerClient(WebMediaSessionManagerClient& client, uint64_t contextId)
158 size_t index = find(&client, contextId);
159 ASSERT(index != notFound);
160 if (index == notFound)
163 LOG(Media, "WebMediaSessionManager::removePlaybackTargetPickerClient(%p + %llu)", &client, contextId);
165 m_clientState.remove(index);
166 scheduleDelayedTask(TargetMonitoringConfigurationTask | TargetClientsConfigurationTask);
169 void WebMediaSessionManager::removeAllPlaybackTargetPickerClients(WebMediaSessionManagerClient& client)
171 if (m_clientState.isEmpty())
174 LOG(Media, "WebMediaSessionManager::removeAllPlaybackTargetPickerClients(%p)", &client);
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);
180 scheduleDelayedTask(TargetMonitoringConfigurationTask | TargetClientsConfigurationTask);
183 void WebMediaSessionManager::showPlaybackTargetPicker(WebMediaSessionManagerClient& client, uint64_t contextId, const IntRect& rect, bool)
185 size_t index = find(&client, contextId);
186 ASSERT(index != notFound);
187 if (index == notFound)
190 auto& clientRequestingPicker = m_clientState[index];
191 for (auto& state : m_clientState) {
192 state->requestedPicker = state == clientRequestingPicker;
193 state->previouslyRequestedPicker = state == clientRequestingPicker;
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);
201 void WebMediaSessionManager::clientStateDidChange(WebMediaSessionManagerClient& client, uint64_t contextId, MediaProducer::MediaStateFlags newFlags)
203 size_t index = find(&client, contextId);
204 ASSERT(index != notFound);
205 if (index == notFound)
208 auto& changedClientState = m_clientState[index];
209 MediaProducer::MediaStateFlags oldFlags = changedClientState->flags;
210 if (newFlags == oldFlags)
213 LOG(Media, "WebMediaSessionManager::clientStateDidChange(%p + %llu) - new flags = %s, old flags = %s", &client, contextId, mediaProducerStateString(newFlags).utf8().data(), mediaProducerStateString(oldFlags).utf8().data());
215 changedClientState->flags = newFlags;
217 MediaProducer::MediaStateFlags updateConfigurationFlags = MediaProducer::RequiresPlaybackTargetMonitoring | MediaProducer::HasPlaybackTargetAvailabilityListener | MediaProducer::HasAudioOrVideo;
218 if ((oldFlags & updateConfigurationFlags) != (newFlags & updateConfigurationFlags))
219 scheduleDelayedTask(TargetMonitoringConfigurationTask);
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);
228 if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute() || !flagsAreSet(newFlags, MediaProducer::ExternalDeviceAutoPlayCandidate))
231 // Do not interrupt another element already playing to a device.
232 for (auto& state : m_clientState) {
233 if (state == changedClientState)
236 if (flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice) && flagsAreSet(state->flags, MediaProducer::IsPlayingVideo))
240 // Do not begin playing to the device unless playback has just started.
241 if (!flagsAreSet(newFlags, MediaProducer::IsPlayingVideo) || flagsAreSet(oldFlags, MediaProducer::IsPlayingVideo))
244 for (auto& state : m_clientState) {
245 if (state == changedClientState)
247 state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
250 changedClientState->client.setShouldPlayToPlaybackTarget(changedClientState->contextId, true);
252 if (index && m_clientState.size() > 1)
253 std::swap(m_clientState.at(index), m_clientState.at(0));
256 void WebMediaSessionManager::setPlaybackTarget(Ref<MediaPlaybackTarget>&& target)
258 m_playbackTarget = WTFMove(target);
259 m_targetChanged = true;
260 scheduleDelayedTask(TargetClientsConfigurationTask);
263 void WebMediaSessionManager::externalOutputDeviceAvailableDidChange(bool available)
265 LOG(Media, "WebMediaSessionManager::externalOutputDeviceAvailableDidChange - clients = %zu, available = %i", m_clientState.size(), (int)available);
267 m_externalOutputDeviceAvailable = available;
268 for (auto& state : m_clientState)
269 state->client.externalOutputDeviceAvailableDidChange(state->contextId, available);
272 void WebMediaSessionManager::configureNewClients()
274 for (auto& state : m_clientState) {
275 if (!state->configurationRequired)
278 state->configurationRequired = false;
279 if (m_externalOutputDeviceAvailable)
280 state->client.externalOutputDeviceAvailableDidChange(state->contextId, true);
282 if (m_playbackTarget)
283 state->client.setPlaybackTarget(state->contextId, *m_playbackTarget.copyRef());
287 void WebMediaSessionManager::configurePlaybackTargetClients()
289 if (m_clientState.isEmpty())
292 size_t indexOfClientThatRequestedPicker = notFound;
293 size_t indexOfLastClientToRequestPicker = notFound;
294 size_t indexOfClientWillPlayToTarget = notFound;
295 bool haveActiveRoute = m_playbackTarget && m_playbackTarget->hasActiveRoute();
297 for (size_t i = 0; i < m_clientState.size(); ++i) {
298 auto& state = m_clientState[i];
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());
302 if (m_targetChanged && state->requestedPicker)
303 indexOfClientThatRequestedPicker = i;
305 if (indexOfClientWillPlayToTarget == notFound && flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice))
306 indexOfClientWillPlayToTarget = i;
308 if (indexOfClientWillPlayToTarget == notFound && haveActiveRoute && state->previouslyRequestedPicker)
309 indexOfLastClientToRequestPicker = i;
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;
319 LOG(Media, "WebMediaSessionManager::configurePlaybackTargetClients - indexOfClientWillPlayToTarget = %zu", indexOfClientWillPlayToTarget);
321 for (size_t i = 0; i < m_clientState.size(); ++i) {
322 auto& state = m_clientState[i];
324 if (m_playbackTarget)
325 state->client.setPlaybackTarget(state->contextId, *m_playbackTarget.copyRef());
327 if (i != indexOfClientWillPlayToTarget || !haveActiveRoute)
328 state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
330 state->configurationRequired = false;
332 state->requestedPicker = false;
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);
341 m_targetChanged = false;
342 configureWatchdogTimer();
345 void WebMediaSessionManager::configurePlaybackTargetMonitoring()
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;
355 if (state->flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
356 hasAvailabilityListener = true;
357 if (state->flags & MediaProducer::HasAudioOrVideo)
358 haveClientWithMedia = true;
361 LOG(Media, "WebMediaSessionManager::configurePlaybackTargetMonitoring - monitoringRequired = %i", static_cast<int>(monitoringRequired || (hasAvailabilityListener && haveClientWithMedia)));
363 if (monitoringRequired || (hasAvailabilityListener && haveClientWithMedia))
364 targetPicker().startingMonitoringPlaybackTargets();
366 targetPicker().stopMonitoringPlaybackTargets();
370 String WebMediaSessionManager::toString(ConfigurationTasks tasks)
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");
384 string.resize(string.length() - 2);
386 return string.toString();
390 void WebMediaSessionManager::scheduleDelayedTask(ConfigurationTasks tasks)
392 LOG(Media, "WebMediaSessionManager::scheduleDelayedTask - %s", toString(tasks).utf8().data());
394 m_taskFlags |= tasks;
395 m_taskTimer.startOneShot(taskDelayInterval);
398 void WebMediaSessionManager::taskTimerFired()
400 LOG(Media, "WebMediaSessionManager::taskTimerFired - tasks = %s", toString(m_taskFlags).utf8().data());
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();
411 m_taskFlags = NoTask;
414 size_t WebMediaSessionManager::find(WebMediaSessionManagerClient* client, uint64_t contextId)
416 for (size_t i = 0; i < m_clientState.size(); ++i) {
417 if (m_clientState[i]->contextId == contextId && &m_clientState[i]->client == client)
424 void WebMediaSessionManager::configureWatchdogTimer()
426 static const double watchdogTimerIntervalAfterPausing = 60 * 60;
427 static const double watchdogTimerIntervalAfterPlayingToEnd = 8 * 60;
429 if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute()) {
430 m_watchdogTimer.stop();
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))
439 if (state->playedToEnd)
441 state->playedToEnd = false;
445 m_currentWatchdogInterval = 0;
446 m_watchdogTimer.stop();
447 LOG(Media, "WebMediaSessionManager::configureWatchdogTimer - timer stopped");
449 double 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", interval);
454 m_currentWatchdogInterval = interval;
458 void WebMediaSessionManager::watchdogTimerFired()
460 LOG(Media, "WebMediaSessionManager::watchdogTimerFired");
461 if (!m_playbackTarget)
464 targetPicker().invalidatePlaybackTargets();
467 } // namespace WebCore
469 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)