summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/mediasession
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/Modules/mediasession')
-rw-r--r--Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.cpp59
-rw-r--r--Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.h45
-rw-r--r--Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.idl32
-rw-r--r--Source/WebCore/Modules/mediasession/MediaRemoteControls.cpp74
-rw-r--r--Source/WebCore/Modules/mediasession/MediaRemoteControls.h76
-rw-r--r--Source/WebCore/Modules/mediasession/MediaRemoteControls.idl36
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSession.cpp282
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSession.h111
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSession.idl53
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSessionEvents.h40
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSessionManager.cpp171
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSessionManager.h66
-rw-r--r--Source/WebCore/Modules/mediasession/MediaSessionMetadata.h58
-rw-r--r--Source/WebCore/Modules/mediasession/WebMediaSessionManager.cpp469
-rw-r--r--Source/WebCore/Modules/mediasession/WebMediaSessionManager.h113
-rw-r--r--Source/WebCore/Modules/mediasession/WebMediaSessionManagerClient.h47
16 files changed, 1732 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.cpp b/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.cpp
new file mode 100644
index 000000000..5469ca1f1
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "HTMLMediaElementMediaSession.h"
+
+#if ENABLE(MEDIA_SESSION) && ENABLE(VIDEO)
+
+namespace WebCore {
+
+const String& HTMLMediaElementMediaSession::kind(HTMLMediaElement* element)
+{
+ ASSERT(element);
+ return element->kind();
+}
+
+void HTMLMediaElementMediaSession::setKind(WebCore::HTMLMediaElement* element, const String& kind)
+{
+ ASSERT(element);
+ return element->setKind(kind);
+}
+
+MediaSession* HTMLMediaElementMediaSession::session(HTMLMediaElement* element)
+{
+ ASSERT(element);
+ return element->session();
+}
+
+void HTMLMediaElementMediaSession::setSession(HTMLMediaElement* element, MediaSession* session)
+{
+ ASSERT(element);
+ element->setSession(session);
+}
+
+} // namespace WebCore
+
+#endif /* ENABLE(MEDIA_SESSION) && ENABLE(VIDEO) */
diff --git a/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.h b/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.h
new file mode 100644
index 000000000..ec1718b5e
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(MEDIA_SESSION) && ENABLE(VIDEO)
+
+#include "HTMLMediaElement.h"
+
+namespace WebCore {
+
+class HTMLMediaElementMediaSession {
+public:
+ static const String& kind(HTMLMediaElement*);
+ static void setKind(HTMLMediaElement*, const String&);
+
+ static MediaSession* session(HTMLMediaElement*);
+ static void setSession(HTMLMediaElement*, MediaSession*);
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_SESSION) && ENABLE(VIDEO)
diff --git a/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.idl b/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.idl
new file mode 100644
index 000000000..9357abc0c
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/HTMLMediaElementMediaSession.idl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+[
+ Conditional=VIDEO&MEDIA_SESSION,
+] partial interface HTMLMediaElement {
+ attribute DOMString kind;
+
+ attribute MediaSession? session;
+};
diff --git a/Source/WebCore/Modules/mediasession/MediaRemoteControls.cpp b/Source/WebCore/Modules/mediasession/MediaRemoteControls.cpp
new file mode 100644
index 000000000..4a82775b0
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaRemoteControls.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MediaRemoteControls.h"
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "MediaSession.h"
+
+namespace WebCore {
+
+MediaRemoteControls::MediaRemoteControls(ScriptExecutionContext& context, MediaSession* session)
+ : m_scriptExecutionContext(context)
+ , m_session(session)
+{
+}
+
+void MediaRemoteControls::clearSession()
+{
+ m_session = nullptr;
+}
+
+MediaRemoteControls::~MediaRemoteControls()
+{
+}
+
+void MediaRemoteControls::setPreviousTrackEnabled(bool isEnabled)
+{
+ if (m_previousTrackEnabled == isEnabled)
+ return;
+
+ m_previousTrackEnabled = isEnabled;
+
+ if (m_session)
+ m_session->controlIsEnabledDidChange();
+}
+
+void MediaRemoteControls::setNextTrackEnabled(bool isEnabled)
+{
+ if (m_nextTrackEnabled == isEnabled)
+ return;
+
+ m_nextTrackEnabled = isEnabled;
+
+ if (m_session)
+ m_session->controlIsEnabledDidChange();
+}
+
+}
+
+#endif /* ENABLE(MEDIA_SESSION) */
diff --git a/Source/WebCore/Modules/mediasession/MediaRemoteControls.h b/Source/WebCore/Modules/mediasession/MediaRemoteControls.h
new file mode 100644
index 000000000..cf24dea09
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaRemoteControls.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "EventTarget.h"
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class MediaSession;
+
+class MediaRemoteControls : public RefCounted<MediaRemoteControls>, public EventTargetWithInlineData {
+public:
+ static Ref<MediaRemoteControls> create(ScriptExecutionContext& context, MediaSession* session = nullptr)
+ {
+ return adoptRef(*new MediaRemoteControls(context, session));
+ }
+
+ bool previousTrackEnabled() const { return m_previousTrackEnabled; }
+ void setPreviousTrackEnabled(bool);
+
+ bool nextTrackEnabled() const { return m_nextTrackEnabled; }
+ void setNextTrackEnabled(bool);
+
+ using RefCounted<MediaRemoteControls>::ref;
+ using RefCounted<MediaRemoteControls>::deref;
+
+ void clearSession();
+
+ virtual ~MediaRemoteControls();
+
+ EventTargetInterface eventTargetInterface() const override { return MediaRemoteControlsEventTargetInterfaceType; }
+ ScriptExecutionContext* scriptExecutionContext() const override { return &m_scriptExecutionContext; }
+
+private:
+ MediaRemoteControls(ScriptExecutionContext&, MediaSession*);
+
+ ScriptExecutionContext& m_scriptExecutionContext;
+
+ bool m_previousTrackEnabled { false };
+ bool m_nextTrackEnabled { false };
+
+ MediaSession* m_session { nullptr };
+
+ void refEventTarget() final { ref(); }
+ void derefEventTarget() final { deref(); }
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_SESSION)
diff --git a/Source/WebCore/Modules/mediasession/MediaRemoteControls.idl b/Source/WebCore/Modules/mediasession/MediaRemoteControls.idl
new file mode 100644
index 000000000..0ac900736
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaRemoteControls.idl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+[
+ Conditional=MEDIA_SESSION,
+ Constructor,
+ ConstructorCallWith=ScriptExecutionContext,
+] interface MediaRemoteControls : EventTarget {
+ attribute boolean previousTrackEnabled;
+ attribute boolean nextTrackEnabled;
+
+ attribute EventHandler onprevioustrack;
+ attribute EventHandler onnexttrack;
+};
diff --git a/Source/WebCore/Modules/mediasession/MediaSession.cpp b/Source/WebCore/Modules/mediasession/MediaSession.cpp
new file mode 100644
index 000000000..5d141d3bc
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSession.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MediaSession.h"
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "Chrome.h"
+#include "ChromeClient.h"
+#include "Event.h"
+#include "EventNames.h"
+#include "HTMLMediaElement.h"
+#include "MediaSessionManager.h"
+#include "Page.h"
+
+namespace WebCore {
+
+MediaSession::MediaSession(Document& document, Kind kind)
+ : m_document(document)
+ , m_kind(kind)
+{
+ // 4. Media Sessions
+ // 3. If media session's current media session type is "content", then create a new media remote controller for media
+ // session. (Otherwise media session has no media remote controller.)
+ if (m_kind == Kind::Content)
+ m_controls = MediaRemoteControls::create(document, this);
+
+ MediaSessionManager::singleton().addMediaSession(*this);
+}
+
+MediaSession::~MediaSession()
+{
+ MediaSessionManager::singleton().removeMediaSession(*this);
+
+ if (m_controls)
+ m_controls->clearSession();
+}
+
+MediaRemoteControls* MediaSession::controls()
+{
+ return m_controls.get();
+}
+
+void MediaSession::addMediaElement(HTMLMediaElement& element)
+{
+ ASSERT(!m_participatingElements.contains(&element));
+ m_participatingElements.add(&element);
+}
+
+void MediaSession::removeMediaElement(HTMLMediaElement& element)
+{
+ ASSERT(m_participatingElements.contains(&element));
+ m_participatingElements.remove(&element);
+
+ changeActiveMediaElements([&]() {
+ m_activeParticipatingElements.remove(&element);
+ });
+
+ if (m_iteratedActiveParticipatingElements)
+ m_iteratedActiveParticipatingElements->remove(&element);
+}
+
+void MediaSession::changeActiveMediaElements(std::function<void(void)> worker)
+{
+ if (Page *page = m_document.page()) {
+ bool hadActiveMediaElements = MediaSessionManager::singleton().hasActiveMediaElements();
+
+ worker();
+
+ bool hasActiveMediaElements = MediaSessionManager::singleton().hasActiveMediaElements();
+ if (hadActiveMediaElements != hasActiveMediaElements)
+ page->chrome().client().hasMediaSessionWithActiveMediaElementsDidChange(hasActiveMediaElements);
+ } else
+ worker();
+}
+
+void MediaSession::addActiveMediaElement(HTMLMediaElement& element)
+{
+ changeActiveMediaElements([&]() {
+ m_activeParticipatingElements.add(&element);
+ });
+}
+
+bool MediaSession::isMediaElementActive(HTMLMediaElement& element)
+{
+ return m_activeParticipatingElements.contains(&element);
+}
+
+bool MediaSession::hasActiveMediaElements() const
+{
+ return !m_activeParticipatingElements.isEmpty();
+}
+
+void MediaSession::setMetadata(const std::optional<Metadata>& optionalMetadata)
+{
+ if (!optionalMetadata)
+ m_metadata = { };
+ else {
+ auto& metadata = optionalMetadata.value();
+ m_metadata = { metadata.title, metadata.artist, metadata.album, m_document.completeURL(metadata.artwork) };
+ }
+
+ if (auto* page = m_document.page())
+ page->chrome().client().mediaSessionMetadataDidChange(m_metadata);
+}
+
+void MediaSession::deactivate()
+{
+ // 5.1.2. Object members
+ // When the deactivate() method is invoked, the user agent must run the following steps:
+ // 1. Let media session be the current media session.
+ // 2. Indefinitely pause all of media session’s audio-producing participants.
+ // 3. Set media session's resume list to an empty list.
+ // 4. Set media session's audio-producing participants to an empty list.
+ changeActiveMediaElements([&]() {
+ while (!m_activeParticipatingElements.isEmpty())
+ m_activeParticipatingElements.takeAny()->pause();
+ });
+
+ // 5. Run the media session deactivation algorithm for media session.
+ releaseInternal();
+}
+
+void MediaSession::releaseInternal()
+{
+ // 6.5. Releasing a media session
+ // 1. If current media session's current state is idle, then terminate these steps.
+ if (m_currentState == State::Idle)
+ return;
+
+ // 2. If current media session still has one or more active participating media elements, then terminate these steps.
+ if (!m_activeParticipatingElements.isEmpty())
+ return;
+
+ // 3. Optionally, based on platform conventions, the user agent must release any currently held platform media focus
+ // for current media session.
+ // 4. Optionally, based on platform conventions, the user agent must remove any previously established ongoing media
+ // interface in the underlying platform’s notifications area and any ongoing media interface in the underlying
+ // platform's lock screen area for current media session, if any.
+ // 5. Optionally, based on platform conventions, the user agent must prevent any hardware and/or software media keys
+ // from controlling playback of current media session's active participating media elements.
+ // 6. Set current media session's current state to idle.
+ m_currentState = State::Idle;
+}
+
+bool MediaSession::invoke()
+{
+ // 4.4 Activating a media session
+ // 1. If we're already ACTIVE then return success.
+ if (m_currentState == State::Active)
+ return true;
+
+ // 2. Optionally, based on platform conventions, request the most appropriate platform-level media focus for media
+ // session based on its current media session type.
+
+ // 3. Run these substeps...
+
+ // 4. Set our current state to ACTIVE and return success.
+ m_currentState = State::Active;
+ return true;
+}
+
+void MediaSession::handleDuckInterruption()
+{
+ for (auto* element : m_activeParticipatingElements)
+ element->setShouldDuck(true);
+
+ m_currentState = State::Interrupted;
+}
+
+void MediaSession::handleUnduckInterruption()
+{
+ for (auto* element : m_activeParticipatingElements)
+ element->setShouldDuck(false);
+
+ m_currentState = State::Active;
+}
+
+void MediaSession::handleIndefinitePauseInterruption()
+{
+ safelyIterateActiveMediaElements([](HTMLMediaElement* element) {
+ element->pause();
+ });
+
+ m_activeParticipatingElements.clear();
+ m_currentState = State::Idle;
+}
+
+void MediaSession::handlePauseInterruption()
+{
+ m_currentState = State::Interrupted;
+
+ safelyIterateActiveMediaElements([](HTMLMediaElement* element) {
+ element->pause();
+ });
+}
+
+void MediaSession::handleUnpauseInterruption()
+{
+ m_currentState = State::Active;
+
+ safelyIterateActiveMediaElements([](HTMLMediaElement* element) {
+ element->play();
+ });
+}
+
+void MediaSession::togglePlayback()
+{
+ safelyIterateActiveMediaElements([](HTMLMediaElement* element) {
+ if (element->paused())
+ element->play();
+ else
+ element->pause();
+ });
+}
+
+void MediaSession::safelyIterateActiveMediaElements(std::function<void(HTMLMediaElement*)> handler)
+{
+ ASSERT(!m_iteratedActiveParticipatingElements);
+
+ HashSet<HTMLMediaElement*> activeParticipatingElementsCopy = m_activeParticipatingElements;
+ m_iteratedActiveParticipatingElements = &activeParticipatingElementsCopy;
+
+ while (!activeParticipatingElementsCopy.isEmpty())
+ handler(activeParticipatingElementsCopy.takeAny());
+
+ m_iteratedActiveParticipatingElements = nullptr;
+}
+
+void MediaSession::skipToNextTrack()
+{
+ if (m_controls && m_controls->nextTrackEnabled())
+ m_controls->dispatchEvent(Event::create(eventNames().nexttrackEvent, false, false));
+}
+
+void MediaSession::skipToPreviousTrack()
+{
+ if (m_controls && m_controls->previousTrackEnabled())
+ m_controls->dispatchEvent(Event::create(eventNames().previoustrackEvent, false, false));
+}
+
+void MediaSession::controlIsEnabledDidChange()
+{
+ // Media remote controls are only allowed on Content media sessions.
+ ASSERT(m_kind == Kind::Content);
+
+ // Media elements belonging to Content media sessions have mutually-exclusive playback.
+ ASSERT(m_activeParticipatingElements.size() <= 1);
+
+ if (m_activeParticipatingElements.isEmpty())
+ return;
+
+ HTMLMediaElement* element = *m_activeParticipatingElements.begin();
+ m_document.updateIsPlayingMedia(element->elementID());
+}
+
+}
+
+#endif /* ENABLE(MEDIA_SESSION) */
diff --git a/Source/WebCore/Modules/mediasession/MediaSession.h b/Source/WebCore/Modules/mediasession/MediaSession.h
new file mode 100644
index 000000000..fb2b6d1ea
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSession.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "MediaRemoteControls.h"
+#include "MediaSessionMetadata.h"
+#include <wtf/HashSet.h>
+
+namespace WebCore {
+
+class Document;
+class HTMLMediaElement;
+
+class MediaSession final : public RefCounted<MediaSession> {
+public:
+ enum class Kind { Content, Transient, TransientSolo, Ambient };
+ enum class State { Idle, Active, Interrupted };
+
+ struct Metadata {
+ String title;
+ String artist;
+ String album;
+ String artwork;
+ };
+
+ static Ref<MediaSession> create(Document& document, Kind kind)
+ {
+ return adoptRef(*new MediaSession(document, kind));
+ }
+
+ ~MediaSession();
+
+ Kind kind() const { return m_kind; }
+ MediaRemoteControls* controls();
+
+ WEBCORE_EXPORT State currentState() const { return m_currentState; }
+ bool hasActiveMediaElements() const;
+
+ void setMetadata(const std::optional<Metadata>&);
+
+ void deactivate();
+
+ // Runs the media session invocation algorithm and returns true on success.
+ bool invoke();
+
+ void handleDuckInterruption();
+ void handleIndefinitePauseInterruption();
+ void handlePauseInterruption();
+ void handleUnduckInterruption();
+ void handleUnpauseInterruption();
+
+ void togglePlayback();
+ void skipToNextTrack();
+ void skipToPreviousTrack();
+
+ void controlIsEnabledDidChange();
+
+private:
+ friend class HTMLMediaElement;
+
+ MediaSession(Document&, Kind);
+
+ void addMediaElement(HTMLMediaElement&);
+ void removeMediaElement(HTMLMediaElement&);
+
+ void safelyIterateActiveMediaElements(std::function<void(HTMLMediaElement*)>);
+ void changeActiveMediaElements(std::function<void(void)>);
+ void addActiveMediaElement(HTMLMediaElement&);
+ bool isMediaElementActive(HTMLMediaElement&);
+
+ void releaseInternal();
+
+ State m_currentState { State::Idle };
+ HashSet<HTMLMediaElement*> m_participatingElements;
+ HashSet<HTMLMediaElement*> m_activeParticipatingElements;
+ HashSet<HTMLMediaElement*>* m_iteratedActiveParticipatingElements { nullptr };
+
+ Document& m_document;
+ const Kind m_kind;
+ RefPtr<MediaRemoteControls> m_controls;
+ MediaSessionMetadata m_metadata;
+};
+
+} // namespace WebCore
+
+#endif /* ENABLE(MEDIA_SESSION) */
diff --git a/Source/WebCore/Modules/mediasession/MediaSession.idl b/Source/WebCore/Modules/mediasession/MediaSession.idl
new file mode 100644
index 000000000..174306146
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSession.idl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+[
+ Conditional=MEDIA_SESSION,
+ ConstructorCallWith=ScriptExecutionContext,
+ CustomConstructor(optional MediaSessionKind kind = "content"),
+ ExportMacro=WEBCORE_EXPORT,
+ ImplementationLacksVTable,
+] interface MediaSession {
+ readonly attribute MediaSessionKind kind;
+ readonly attribute MediaRemoteControls? controls;
+
+ void setMetadata(MediaMetadata? metadata);
+
+ void deactivate();
+};
+
+enum MediaSessionKind {
+ "content",
+ "transient",
+ "transient-solo",
+ "ambient"
+};
+
+dictionary MediaMetadata {
+ DOMString title;
+ DOMString artist;
+ DOMString album;
+ USVString artwork;
+};
diff --git a/Source/WebCore/Modules/mediasession/MediaSessionEvents.h b/Source/WebCore/Modules/mediasession/MediaSessionEvents.h
new file mode 100644
index 000000000..a4a50d828
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSessionEvents.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(MEDIA_SESSION)
+
+namespace WebCore {
+
+enum MediaEventType {
+ PlayPause,
+ TrackNext,
+ TrackPrevious
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_SESSION)
diff --git a/Source/WebCore/Modules/mediasession/MediaSessionManager.cpp b/Source/WebCore/Modules/mediasession/MediaSessionManager.cpp
new file mode 100644
index 000000000..dd901a2ec
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSessionManager.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MediaSessionManager.h"
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "MediaSession.h"
+#include "MediaSessionInterruptionProviderMac.h"
+
+namespace WebCore {
+
+static const char* contentSessionKind = "content";
+
+MediaSessionManager& MediaSessionManager::singleton()
+{
+ static NeverDestroyed<MediaSessionManager> manager;
+ return manager;
+}
+
+MediaSessionManager::MediaSessionManager()
+{
+#if PLATFORM(MAC)
+ m_interruptionProvider = adoptRef(new MediaSessionInterruptionProviderMac(*this));
+ m_interruptionProvider->beginListeningForInterruptions();
+#endif
+}
+
+bool MediaSessionManager::hasActiveMediaElements() const
+{
+ for (auto* session : m_sessions) {
+ if (session->hasActiveMediaElements())
+ return true;
+ }
+
+ return false;
+}
+
+void MediaSessionManager::addMediaSession(MediaSession& session)
+{
+ m_sessions.add(&session);
+}
+
+void MediaSessionManager::removeMediaSession(MediaSession& session)
+{
+ m_sessions.remove(&session);
+}
+
+void MediaSessionManager::togglePlayback()
+{
+ for (auto* session : m_sessions) {
+ String sessionKind = session->kind();
+ if (session->currentState() == MediaSession::State::Active && (sessionKind == contentSessionKind || sessionKind == ""))
+ session->togglePlayback();
+ }
+}
+
+void MediaSessionManager::skipToNextTrack()
+{
+ // 5.2.2 When the user presses the MediaTrackNext media key, then for each Content-based media session that is
+ // currently ACTIVE and has a media remote controller with its nextTrackEnabled attribute set to true, queue a task
+ // to fire a simple event named nexttrack at its media remote controller.
+ for (auto* session : m_sessions) {
+ if (session->currentState() == MediaSession::State::Active && session->kind() == contentSessionKind)
+ session->skipToNextTrack();
+ }
+}
+
+void MediaSessionManager::skipToPreviousTrack()
+{
+ // 5.2.2 When the user presses the MediaTrackPrevious media key, then for each Content-based media session that is
+ // currently ACTIVE and has a media remote controller with its previousTrackEnabled attribute set to true, queue a task
+ // to fire a simple event named previoustrack at its media remote controller.
+ for (auto* session : m_sessions) {
+ if (session->currentState() == MediaSession::State::Active && session->kind() == contentSessionKind)
+ session->skipToPreviousTrack();
+ }
+}
+
+void MediaSessionManager::didReceiveStartOfInterruptionNotification(MediaSessionInterruptingCategory interruptingCategory)
+{
+ // 4.5.2 Interrupting a media session
+ // When a start-of-interruption notification event is received from the platform, then the user agent must run the
+ // media session interruption algorithm against all known media sessions, passing in each media session as media session.
+ for (auto* session : m_sessions) {
+ // 1. If media session's current state is not active, then terminate these steps.
+ if (session->currentState() != MediaSession::State::Active)
+ continue;
+
+ // 2. Let interrupting media session category be the media session category that triggered this interruption.
+ // If this interruption has no known media session category, let interrupting media session category be Default.
+
+ // 3. Run these substeps:
+ if (interruptingCategory == MediaSessionInterruptingCategory::Content) {
+ // - If interrupting media session category is Content:
+ // If media session's current media session type is Default or Content then indefinitely pause all of media
+ // session's active audio-producing participants and set media session's current state to idle.
+ if (session->kind() == MediaSessionKind::Default || session->kind() == MediaSessionKind::Content)
+ session->handleIndefinitePauseInterruption();
+ } else if (interruptingCategory == MediaSessionInterruptingCategory::Transient) {
+ // - If interrupting media session category is Transient:
+ // If media session's current media session type is Default or Content then duck all of media session's active
+ // audio-producing participants and set media session's current state to interrupted.
+ if (session->kind() == MediaSessionKind::Default || session->kind() == MediaSessionKind::Content)
+ session->handleDuckInterruption();
+ } else if (interruptingCategory == MediaSessionInterruptingCategory::TransientSolo) {
+ // - If interrupting media session category is Transient Solo:
+ // If media session's current media session type is Default, Content, Transient or Transient Solo then pause
+ // all of media session's active audio-producing participants and set current media session's current state to interrupted.
+ if (session->kind() != MediaSessionKind::Ambient)
+ session->handlePauseInterruption();
+ }
+ }
+}
+
+void MediaSessionManager::didReceiveEndOfInterruptionNotification(MediaSessionInterruptingCategory interruptingCategory)
+{
+ // 4.5.2 Interrupting a media session
+ // When an end-of-interruption notification event is received from the platform, then the user agent must run the
+ // media session continuation algorithm against all known media sessions, passing in each media session as media session.
+ for (auto* session : m_sessions) {
+ // 1. If media session's current state is not interrupted, then terminate these steps.
+ if (session->currentState() != MediaSession::State::Interrupted)
+ continue;
+
+ // 2. Let interrupting media session category be the media session category that initially triggered this interruption.
+ // If this interruption has no known media session category, let interrupting media session category be Default.
+
+ // 3. Run these substeps:
+ if (interruptingCategory == MediaSessionInterruptingCategory::Transient) {
+ // - If interrupting media session category is Transient:
+ // If media session's current media session type is Default or Content, then unduck all of media session's
+ // active audio-producing participants and set media session's current state to active.
+ if (session->kind() == MediaSessionKind::Default || session->kind() == MediaSessionKind::Content)
+ session->handleUnduckInterruption();
+ } else if (interruptingCategory == MediaSessionInterruptingCategory::TransientSolo) {
+ // - If interrupting media session category is Transient Solo:
+ // If media session's current media session type is Default, Content, Transient, or Transient Solo, then
+ // unpause the media session's active audio-producing participants and set media session's current state to active.
+ if (session->kind() != MediaSessionKind::Ambient)
+ session->handleUnpauseInterruption();
+ }
+ }
+}
+
+} // namespace WebCore
+
+#endif /* ENABLE(MEDIA_SESSION) */
diff --git a/Source/WebCore/Modules/mediasession/MediaSessionManager.h b/Source/WebCore/Modules/mediasession/MediaSessionManager.h
new file mode 100644
index 000000000..4cde38abc
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSessionManager.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "MediaSessionInterruptionProvider.h"
+#include <wtf/HashSet.h>
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+
+class MediaSession;
+
+class MediaSessionManager : public MediaSessionInterruptionProviderClient {
+ friend class NeverDestroyed<MediaSessionManager>;
+public:
+ WEBCORE_EXPORT static MediaSessionManager& singleton();
+
+ WEBCORE_EXPORT void togglePlayback();
+ WEBCORE_EXPORT void skipToNextTrack();
+ WEBCORE_EXPORT void skipToPreviousTrack();
+
+ WEBCORE_EXPORT void didReceiveStartOfInterruptionNotification(MediaSessionInterruptingCategory) override;
+ WEBCORE_EXPORT void didReceiveEndOfInterruptionNotification(MediaSessionInterruptingCategory) override;
+
+private:
+ friend class MediaSession;
+
+ MediaSessionManager();
+
+ bool hasActiveMediaElements() const;
+
+ void addMediaSession(MediaSession&);
+ void removeMediaSession(MediaSession&);
+
+ RefPtr<MediaSessionInterruptionProvider> m_interruptionProvider;
+ HashSet<MediaSession*> m_sessions;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_SESSION)
diff --git a/Source/WebCore/Modules/mediasession/MediaSessionMetadata.h b/Source/WebCore/Modules/mediasession/MediaSessionMetadata.h
new file mode 100644
index 000000000..e3644a875
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/MediaSessionMetadata.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(MEDIA_SESSION)
+
+#include "URL.h"
+
+namespace WebCore {
+
+class MediaSessionMetadata final {
+public:
+ MediaSessionMetadata() = default;
+
+ MediaSessionMetadata(const String& title, const String& artist, const String& album, const URL& artworkURL)
+ : m_title(title)
+ , m_artist(artist)
+ , m_album(album)
+ , m_artworkURL(artworkURL) { }
+
+ String title() const { return m_title; }
+ String artist() const { return m_artist; }
+ String album() const { return m_album; }
+ URL artworkURL() const { return m_artworkURL; }
+
+private:
+ String m_title;
+ String m_artist;
+ String m_album;
+ URL m_artworkURL;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_SESSION)
diff --git a/Source/WebCore/Modules/mediasession/WebMediaSessionManager.cpp b/Source/WebCore/Modules/mediasession/WebMediaSessionManager.cpp
new file mode 100644
index 000000000..7a3369f10
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/WebMediaSessionManager.cpp
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "WebMediaSessionManager.h"
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
+
+#include "FloatRect.h"
+#include "Logging.h"
+#include "MediaPlaybackTargetPickerMock.h"
+#include "WebMediaSessionManagerClient.h"
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+static const double taskDelayInterval = 1.0 / 10.0;
+
+struct ClientState {
+ explicit ClientState(WebMediaSessionManagerClient& client, uint64_t contextId)
+ : client(client)
+ , contextId(contextId)
+ {
+ }
+
+ bool operator == (ClientState const& other) const
+ {
+ return contextId == other.contextId && &client == &other.client;
+ }
+
+ WebMediaSessionManagerClient& client;
+ uint64_t contextId { 0 };
+ WebCore::MediaProducer::MediaStateFlags flags { WebCore::MediaProducer::IsNotPlaying };
+ bool requestedPicker { false };
+ bool previouslyRequestedPicker { false };
+ bool configurationRequired { true };
+ bool playedToEnd { false };
+};
+
+static bool flagsAreSet(MediaProducer::MediaStateFlags value, unsigned flags)
+{
+ return value & flags;
+}
+
+#if !LOG_DISABLED
+static String mediaProducerStateString(MediaProducer::MediaStateFlags flags)
+{
+ StringBuilder string;
+ if (flags & MediaProducer::IsPlayingAudio)
+ string.append("IsPlayingAudio + ");
+ if (flags & MediaProducer::IsPlayingVideo)
+ string.append("IsPlayingVideo + ");
+ if (flags & MediaProducer::IsPlayingToExternalDevice)
+ string.append("IsPlayingToExternalDevice + ");
+ if (flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
+ string.append("HasPlaybackTargetAvailabilityListener + ");
+ if (flags & MediaProducer::RequiresPlaybackTargetMonitoring)
+ string.append("RequiresPlaybackTargetMonitoring + ");
+ if (flags & MediaProducer::ExternalDeviceAutoPlayCandidate)
+ string.append("ExternalDeviceAutoPlayCandidate + ");
+ if (flags & MediaProducer::DidPlayToEnd)
+ string.append("DidPlayToEnd + ");
+ if (flags & MediaProducer::HasAudioOrVideo)
+ string.append("HasAudioOrVideo + ");
+ if (string.isEmpty())
+ string.append("IsNotPlaying");
+ else
+ string.resize(string.length() - 2);
+
+ return string.toString();
+}
+#endif
+
+void WebMediaSessionManager::setMockMediaPlaybackTargetPickerEnabled(bool enabled)
+{
+ LOG(Media, "WebMediaSessionManager::setMockMediaPlaybackTargetPickerEnabled - enabled = %i", (int)enabled);
+
+ if (m_mockPickerEnabled == enabled)
+ return;
+
+ m_mockPickerEnabled = enabled;
+}
+
+void WebMediaSessionManager::setMockMediaPlaybackTargetPickerState(const String& name, MediaPlaybackTargetContext::State state)
+{
+ LOG(Media, "WebMediaSessionManager::setMockMediaPlaybackTargetPickerState - name = %s, state = %i", name.utf8().data(), (int)state);
+
+ mockPicker().setState(name, state);
+}
+
+MediaPlaybackTargetPickerMock& WebMediaSessionManager::mockPicker()
+{
+ if (!m_pickerOverride)
+ m_pickerOverride = std::make_unique<MediaPlaybackTargetPickerMock>(*this);
+
+ return *m_pickerOverride.get();
+}
+
+WebCore::MediaPlaybackTargetPicker& WebMediaSessionManager::targetPicker()
+{
+ if (m_mockPickerEnabled)
+ return mockPicker();
+
+ return platformPicker();
+}
+
+WebMediaSessionManager::WebMediaSessionManager()
+ : m_taskTimer(RunLoop::current(), this, &WebMediaSessionManager::taskTimerFired)
+ , m_watchdogTimer(RunLoop::current(), this, &WebMediaSessionManager::watchdogTimerFired)
+{
+}
+
+WebMediaSessionManager::~WebMediaSessionManager()
+{
+}
+
+uint64_t WebMediaSessionManager::addPlaybackTargetPickerClient(WebMediaSessionManagerClient& client, uint64_t contextId)
+{
+ size_t index = find(&client, contextId);
+ ASSERT(index == notFound);
+ if (index != notFound)
+ return 0;
+
+ LOG(Media, "WebMediaSessionManager::addPlaybackTargetPickerClient(%p + %llu)", &client, contextId);
+
+ m_clientState.append(std::make_unique<ClientState>(client, contextId));
+
+ if (m_externalOutputDeviceAvailable || m_playbackTarget)
+ scheduleDelayedTask(InitialConfigurationTask | TargetClientsConfigurationTask);
+
+ return contextId;
+}
+
+void WebMediaSessionManager::removePlaybackTargetPickerClient(WebMediaSessionManagerClient& client, uint64_t contextId)
+{
+ size_t index = find(&client, contextId);
+ ASSERT(index != notFound);
+ if (index == notFound)
+ return;
+
+ LOG(Media, "WebMediaSessionManager::removePlaybackTargetPickerClient(%p + %llu)", &client, contextId);
+
+ m_clientState.remove(index);
+ scheduleDelayedTask(TargetMonitoringConfigurationTask | TargetClientsConfigurationTask);
+}
+
+void WebMediaSessionManager::removeAllPlaybackTargetPickerClients(WebMediaSessionManagerClient& client)
+{
+ if (m_clientState.isEmpty())
+ return;
+
+ LOG(Media, "WebMediaSessionManager::removeAllPlaybackTargetPickerClients(%p)", &client);
+
+ for (size_t i = m_clientState.size(); i > 0; --i) {
+ if (&m_clientState[i - 1]->client == &client)
+ m_clientState.remove(i - 1);
+ }
+ scheduleDelayedTask(TargetMonitoringConfigurationTask | TargetClientsConfigurationTask);
+}
+
+void WebMediaSessionManager::showPlaybackTargetPicker(WebMediaSessionManagerClient& client, uint64_t contextId, const IntRect& rect, bool)
+{
+ size_t index = find(&client, contextId);
+ ASSERT(index != notFound);
+ if (index == notFound)
+ return;
+
+ auto& clientRequestingPicker = m_clientState[index];
+ for (auto& state : m_clientState) {
+ state->requestedPicker = state == clientRequestingPicker;
+ state->previouslyRequestedPicker = state == clientRequestingPicker;
+ }
+
+ bool hasActiveRoute = flagsAreSet(m_clientState[index]->flags, MediaProducer::IsPlayingToExternalDevice);
+ LOG(Media, "WebMediaSessionManager::showPlaybackTargetPicker(%p + %llu) - hasActiveRoute = %i", &client, contextId, (int)hasActiveRoute);
+ targetPicker().showPlaybackTargetPicker(FloatRect(rect), hasActiveRoute);
+}
+
+void WebMediaSessionManager::clientStateDidChange(WebMediaSessionManagerClient& client, uint64_t contextId, MediaProducer::MediaStateFlags newFlags)
+{
+ size_t index = find(&client, contextId);
+ ASSERT(index != notFound);
+ if (index == notFound)
+ return;
+
+ auto& changedClientState = m_clientState[index];
+ MediaProducer::MediaStateFlags oldFlags = changedClientState->flags;
+ if (newFlags == oldFlags)
+ return;
+
+ LOG(Media, "WebMediaSessionManager::clientStateDidChange(%p + %llu) - new flags = %s, old flags = %s", &client, contextId, mediaProducerStateString(newFlags).utf8().data(), mediaProducerStateString(oldFlags).utf8().data());
+
+ changedClientState->flags = newFlags;
+
+ MediaProducer::MediaStateFlags updateConfigurationFlags = MediaProducer::RequiresPlaybackTargetMonitoring | MediaProducer::HasPlaybackTargetAvailabilityListener | MediaProducer::HasAudioOrVideo;
+ if ((oldFlags & updateConfigurationFlags) != (newFlags & updateConfigurationFlags))
+ scheduleDelayedTask(TargetMonitoringConfigurationTask);
+
+ MediaProducer::MediaStateFlags playingToTargetFlags = MediaProducer::IsPlayingToExternalDevice | MediaProducer::IsPlayingVideo;
+ if ((oldFlags & playingToTargetFlags) != (newFlags & playingToTargetFlags)) {
+ if (flagsAreSet(oldFlags, MediaProducer::IsPlayingVideo) && !flagsAreSet(newFlags, MediaProducer::IsPlayingVideo) && flagsAreSet(newFlags, MediaProducer::DidPlayToEnd))
+ changedClientState->playedToEnd = true;
+ scheduleDelayedTask(WatchdogTimerConfigurationTask);
+ }
+
+ if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute() || !flagsAreSet(newFlags, MediaProducer::ExternalDeviceAutoPlayCandidate))
+ return;
+
+ // Do not interrupt another element already playing to a device.
+ for (auto& state : m_clientState) {
+ if (state == changedClientState)
+ continue;
+
+ if (flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice) && flagsAreSet(state->flags, MediaProducer::IsPlayingVideo))
+ return;
+ }
+
+ // Do not begin playing to the device unless playback has just started.
+ if (!flagsAreSet(newFlags, MediaProducer::IsPlayingVideo) || flagsAreSet(oldFlags, MediaProducer::IsPlayingVideo))
+ return;
+
+ for (auto& state : m_clientState) {
+ if (state == changedClientState)
+ continue;
+ state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
+ }
+
+ changedClientState->client.setShouldPlayToPlaybackTarget(changedClientState->contextId, true);
+
+ if (index && m_clientState.size() > 1)
+ std::swap(m_clientState.at(index), m_clientState.at(0));
+}
+
+void WebMediaSessionManager::setPlaybackTarget(Ref<MediaPlaybackTarget>&& target)
+{
+ m_playbackTarget = WTFMove(target);
+ m_targetChanged = true;
+ scheduleDelayedTask(TargetClientsConfigurationTask);
+}
+
+void WebMediaSessionManager::externalOutputDeviceAvailableDidChange(bool available)
+{
+ LOG(Media, "WebMediaSessionManager::externalOutputDeviceAvailableDidChange - clients = %zu, available = %i", m_clientState.size(), (int)available);
+
+ m_externalOutputDeviceAvailable = available;
+ for (auto& state : m_clientState)
+ state->client.externalOutputDeviceAvailableDidChange(state->contextId, available);
+}
+
+void WebMediaSessionManager::configureNewClients()
+{
+ for (auto& state : m_clientState) {
+ if (!state->configurationRequired)
+ continue;
+
+ state->configurationRequired = false;
+ if (m_externalOutputDeviceAvailable)
+ state->client.externalOutputDeviceAvailableDidChange(state->contextId, true);
+
+ if (m_playbackTarget)
+ state->client.setPlaybackTarget(state->contextId, *m_playbackTarget.copyRef());
+ }
+}
+
+void WebMediaSessionManager::configurePlaybackTargetClients()
+{
+ if (m_clientState.isEmpty())
+ return;
+
+ size_t indexOfClientThatRequestedPicker = notFound;
+ size_t indexOfLastClientToRequestPicker = notFound;
+ size_t indexOfClientWillPlayToTarget = notFound;
+ bool haveActiveRoute = m_playbackTarget && m_playbackTarget->hasActiveRoute();
+
+ for (size_t i = 0; i < m_clientState.size(); ++i) {
+ auto& state = m_clientState[i];
+
+ LOG(Media, "WebMediaSessionManager::configurePlaybackTargetClients %zu - client (%p + %llu) requestedPicker = %i, flags = %s", i, &state->client, state->contextId, state->requestedPicker, mediaProducerStateString(state->flags).utf8().data());
+
+ if (m_targetChanged && state->requestedPicker)
+ indexOfClientThatRequestedPicker = i;
+
+ if (indexOfClientWillPlayToTarget == notFound && flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice))
+ indexOfClientWillPlayToTarget = i;
+
+ if (indexOfClientWillPlayToTarget == notFound && haveActiveRoute && state->previouslyRequestedPicker)
+ indexOfLastClientToRequestPicker = i;
+ }
+
+ if (indexOfClientThatRequestedPicker != notFound)
+ indexOfClientWillPlayToTarget = indexOfClientThatRequestedPicker;
+ if (indexOfClientWillPlayToTarget == notFound && indexOfLastClientToRequestPicker != notFound)
+ indexOfClientWillPlayToTarget = indexOfLastClientToRequestPicker;
+ if (indexOfClientWillPlayToTarget == notFound && haveActiveRoute && flagsAreSet(m_clientState[0]->flags, MediaProducer::ExternalDeviceAutoPlayCandidate) && !flagsAreSet(m_clientState[0]->flags, MediaProducer::IsPlayingVideo))
+ indexOfClientWillPlayToTarget = 0;
+
+ LOG(Media, "WebMediaSessionManager::configurePlaybackTargetClients - indexOfClientWillPlayToTarget = %zu", indexOfClientWillPlayToTarget);
+
+ for (size_t i = 0; i < m_clientState.size(); ++i) {
+ auto& state = m_clientState[i];
+
+ if (m_playbackTarget)
+ state->client.setPlaybackTarget(state->contextId, *m_playbackTarget.copyRef());
+
+ if (i != indexOfClientWillPlayToTarget || !haveActiveRoute)
+ state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
+
+ state->configurationRequired = false;
+ if (m_targetChanged)
+ state->requestedPicker = false;
+ }
+
+ if (haveActiveRoute && indexOfClientWillPlayToTarget != notFound) {
+ auto& state = m_clientState[indexOfClientWillPlayToTarget];
+ if (!flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice))
+ state->client.setShouldPlayToPlaybackTarget(state->contextId, true);
+ }
+
+ m_targetChanged = false;
+ configureWatchdogTimer();
+}
+
+void WebMediaSessionManager::configurePlaybackTargetMonitoring()
+{
+ bool monitoringRequired = false;
+ bool hasAvailabilityListener = false;
+ bool haveClientWithMedia = false;
+ for (auto& state : m_clientState) {
+ if (state->flags & MediaProducer::RequiresPlaybackTargetMonitoring) {
+ monitoringRequired = true;
+ break;
+ }
+ if (state->flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
+ hasAvailabilityListener = true;
+ if (state->flags & MediaProducer::HasAudioOrVideo)
+ haveClientWithMedia = true;
+ }
+
+ LOG(Media, "WebMediaSessionManager::configurePlaybackTargetMonitoring - monitoringRequired = %i", static_cast<int>(monitoringRequired || (hasAvailabilityListener && haveClientWithMedia)));
+
+ if (monitoringRequired || (hasAvailabilityListener && haveClientWithMedia))
+ targetPicker().startingMonitoringPlaybackTargets();
+ else
+ targetPicker().stopMonitoringPlaybackTargets();
+}
+
+#if !LOG_DISABLED
+String WebMediaSessionManager::toString(ConfigurationTasks tasks)
+{
+ StringBuilder string;
+ if (tasks & InitialConfigurationTask)
+ string.append("InitialConfigurationTask + ");
+ if (tasks & TargetClientsConfigurationTask)
+ string.append("TargetClientsConfigurationTask + ");
+ if (tasks & TargetMonitoringConfigurationTask)
+ string.append("TargetMonitoringConfigurationTask + ");
+ if (tasks & WatchdogTimerConfigurationTask)
+ string.append("WatchdogTimerConfigurationTask + ");
+ if (string.isEmpty())
+ string.append("NoTask");
+ else
+ string.resize(string.length() - 2);
+
+ return string.toString();
+}
+#endif
+
+void WebMediaSessionManager::scheduleDelayedTask(ConfigurationTasks tasks)
+{
+ LOG(Media, "WebMediaSessionManager::scheduleDelayedTask - %s", toString(tasks).utf8().data());
+
+ m_taskFlags |= tasks;
+ m_taskTimer.startOneShot(taskDelayInterval);
+}
+
+void WebMediaSessionManager::taskTimerFired()
+{
+ LOG(Media, "WebMediaSessionManager::taskTimerFired - tasks = %s", toString(m_taskFlags).utf8().data());
+
+ if (m_taskFlags & InitialConfigurationTask)
+ configureNewClients();
+ if (m_taskFlags & TargetClientsConfigurationTask)
+ configurePlaybackTargetClients();
+ if (m_taskFlags & TargetMonitoringConfigurationTask)
+ configurePlaybackTargetMonitoring();
+ if (m_taskFlags & WatchdogTimerConfigurationTask)
+ configureWatchdogTimer();
+
+ m_taskFlags = NoTask;
+}
+
+size_t WebMediaSessionManager::find(WebMediaSessionManagerClient* client, uint64_t contextId)
+{
+ for (size_t i = 0; i < m_clientState.size(); ++i) {
+ if (m_clientState[i]->contextId == contextId && &m_clientState[i]->client == client)
+ return i;
+ }
+
+ return notFound;
+}
+
+void WebMediaSessionManager::configureWatchdogTimer()
+{
+ static const double watchdogTimerIntervalAfterPausing = 60 * 60;
+ static const double watchdogTimerIntervalAfterPlayingToEnd = 8 * 60;
+
+ if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute()) {
+ m_watchdogTimer.stop();
+ return;
+ }
+
+ bool stopTimer = false;
+ bool didPlayToEnd = false;
+ for (auto& state : m_clientState) {
+ if (flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice) && flagsAreSet(state->flags, MediaProducer::IsPlayingVideo))
+ stopTimer = true;
+ if (state->playedToEnd)
+ didPlayToEnd = true;
+ state->playedToEnd = false;
+ }
+
+ if (stopTimer) {
+ m_currentWatchdogInterval = 0;
+ m_watchdogTimer.stop();
+ LOG(Media, "WebMediaSessionManager::configureWatchdogTimer - timer stopped");
+ } else {
+ double interval = didPlayToEnd ? watchdogTimerIntervalAfterPlayingToEnd : watchdogTimerIntervalAfterPausing;
+ if (interval != m_currentWatchdogInterval || !m_watchdogTimer.isActive()) {
+ m_watchdogTimer.startOneShot(interval);
+ LOG(Media, "WebMediaSessionManager::configureWatchdogTimer - timer scheduled for %.0f", interval);
+ }
+ m_currentWatchdogInterval = interval;
+ }
+}
+
+void WebMediaSessionManager::watchdogTimerFired()
+{
+ LOG(Media, "WebMediaSessionManager::watchdogTimerFired");
+ if (!m_playbackTarget)
+ return;
+
+ targetPicker().invalidatePlaybackTargets();
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
diff --git a/Source/WebCore/Modules/mediasession/WebMediaSessionManager.h b/Source/WebCore/Modules/mediasession/WebMediaSessionManager.h
new file mode 100644
index 000000000..57d58ef91
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/WebMediaSessionManager.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
+
+#include "MediaPlaybackTargetContext.h"
+#include "MediaPlaybackTargetPicker.h"
+#include "MediaPlaybackTargetPickerMock.h"
+#include "MediaProducer.h"
+#include "Timer.h"
+#include <wtf/Ref.h>
+#include <wtf/RefPtr.h>
+#include <wtf/RunLoop.h>
+
+namespace WebCore {
+
+struct ClientState;
+class IntRect;
+class WebMediaSessionManagerClient;
+
+class WebMediaSessionManager : public MediaPlaybackTargetPicker::Client {
+ WTF_MAKE_NONCOPYABLE(WebMediaSessionManager);
+public:
+
+ WEBCORE_EXPORT static WebMediaSessionManager& shared();
+
+ WEBCORE_EXPORT void setMockMediaPlaybackTargetPickerEnabled(bool);
+ WEBCORE_EXPORT void setMockMediaPlaybackTargetPickerState(const String&, MediaPlaybackTargetContext::State);
+
+ WEBCORE_EXPORT uint64_t addPlaybackTargetPickerClient(WebMediaSessionManagerClient&, uint64_t);
+ WEBCORE_EXPORT void removePlaybackTargetPickerClient(WebMediaSessionManagerClient&, uint64_t);
+ WEBCORE_EXPORT void removeAllPlaybackTargetPickerClients(WebMediaSessionManagerClient&);
+ WEBCORE_EXPORT void showPlaybackTargetPicker(WebMediaSessionManagerClient&, uint64_t, const IntRect&, bool);
+ WEBCORE_EXPORT void clientStateDidChange(WebMediaSessionManagerClient&, uint64_t, WebCore::MediaProducer::MediaStateFlags);
+
+protected:
+ WebMediaSessionManager();
+ virtual ~WebMediaSessionManager();
+
+ virtual WebCore::MediaPlaybackTargetPicker& platformPicker() = 0;
+ static WebMediaSessionManager& platformManager();
+
+private:
+
+ WebCore::MediaPlaybackTargetPicker& targetPicker();
+ WebCore::MediaPlaybackTargetPickerMock& mockPicker();
+
+ // MediaPlaybackTargetPicker::Client
+ void setPlaybackTarget(Ref<WebCore::MediaPlaybackTarget>&&) override;
+ void externalOutputDeviceAvailableDidChange(bool) override;
+
+ size_t find(WebMediaSessionManagerClient*, uint64_t);
+
+ void configurePlaybackTargetClients();
+ void configureNewClients();
+ void configurePlaybackTargetMonitoring();
+ void configureWatchdogTimer();
+
+ enum ConfigurationTaskFlags {
+ NoTask = 0,
+ InitialConfigurationTask = 1 << 0,
+ TargetClientsConfigurationTask = 1 << 1,
+ TargetMonitoringConfigurationTask = 1 << 2,
+ WatchdogTimerConfigurationTask = 1 << 3,
+ };
+ typedef unsigned ConfigurationTasks;
+ String toString(ConfigurationTasks);
+
+ void scheduleDelayedTask(ConfigurationTasks);
+ void taskTimerFired();
+
+ void watchdogTimerFired();
+
+ RunLoop::Timer<WebMediaSessionManager> m_taskTimer;
+ RunLoop::Timer<WebMediaSessionManager> m_watchdogTimer;
+
+ Vector<std::unique_ptr<ClientState>> m_clientState;
+ RefPtr<MediaPlaybackTarget> m_playbackTarget;
+ std::unique_ptr<WebCore::MediaPlaybackTargetPickerMock> m_pickerOverride;
+ ConfigurationTasks m_taskFlags { NoTask };
+ double m_currentWatchdogInterval { 0 };
+ bool m_externalOutputDeviceAvailable { false };
+ bool m_targetChanged { false };
+ bool m_mockPickerEnabled { false };
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS)
diff --git a/Source/WebCore/Modules/mediasession/WebMediaSessionManagerClient.h b/Source/WebCore/Modules/mediasession/WebMediaSessionManagerClient.h
new file mode 100644
index 000000000..4dad21153
--- /dev/null
+++ b/Source/WebCore/Modules/mediasession/WebMediaSessionManagerClient.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+
+#include "MediaPlaybackTarget.h"
+#include "MediaProducer.h"
+#include <wtf/Ref.h>
+
+namespace WebCore {
+
+class WebMediaSessionManagerClient {
+public:
+ virtual ~WebMediaSessionManagerClient() { }
+
+ virtual void setPlaybackTarget(uint64_t, Ref<MediaPlaybackTarget>&&) = 0;
+ virtual void externalOutputDeviceAvailableDidChange(uint64_t, bool) = 0;
+ virtual void setShouldPlayToPlaybackTarget(uint64_t, bool) = 0;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET)