diff options
Diffstat (limited to 'Source/WebCore/Modules/mediasession')
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) |