Title: [272445] trunk
Revision
272445
Author
[email protected]
Date
2021-02-05 15:18:55 -0800 (Fri, 05 Feb 2021)

Log Message

[Mac] Connect MediaSession with MediaRemote and NowPlaying
https://bugs.webkit.org/show_bug.cgi?id=221431
<rdar://problem/74000363>

Reviewed by Jer Noble.

Source/WebCore:

Route all remote control commands to navigator.mediaSession when it has any
registered action handlers, and pass navigator.mediaSession.metadata to NowPlaying.

Tested with new API tests in Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm.

* Modules/mediasession/MediaImage.h:
(WebCore::MediaImage::encode const):
(WebCore::MediaImage::decode):
* Modules/mediasession/MediaMetadata.cpp:
(WebCore::MediaMetadata::setTitle):
(WebCore::MediaMetadata::setArtist):
(WebCore::MediaMetadata::setAlbum):
(WebCore::MediaMetadata::setArtwork):
* Modules/mediasession/MediaMetadata.h:
(WebCore::MediaMetadata::title const):
(WebCore::MediaMetadata::artist const):
(WebCore::MediaMetadata::album const):
(WebCore::MediaMetadata::artwork const):
(WebCore::MediaMetadata::metadata const):
* Modules/mediasession/MediaMetadataInit.h:
(WebCore::MediaMetadataInit::encode const):
(WebCore::MediaMetadataInit::decode):
* Modules/mediasession/MediaSession.cpp:
(WebCore::nextLogIdentifier):
(WebCore::logChannel):
(WebCore::logClassName):
(WebCore::platformCommandForMediaSessionAction):
(WebCore::MediaSession::MediaSession):
(WebCore::MediaSession::setMetadata):
(WebCore::MediaSession::setPlaybackState):
(WebCore::MediaSession::setActionHandler):
(WebCore::MediaSession::setPositionState):
* Modules/mediasession/MediaSession.h:
(WebCore::MediaSession::hasActiveActionHandlers const):
(WebCore::MediaSession::logger const):
(WebCore::MediaSession::logIdentifier const):
(WTF::LogArgument<WebCore::MediaSessionPlaybackState>::toString):
(WTF::LogArgument<WebCore::MediaSessionAction>::toString):
* WebCore.xcodeproj/project.pbxproj:
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::didReceiveRemoteControlCommand):
* html/MediaElementSession.cpp:
(WebCore::MediaElementSession::didReceiveRemoteControlCommand):
(WebCore::MediaElementSession::nowPlayingInfo const):
* html/MediaElementSession.h:
* page/ChromeClient.h:
* platform/RemoteCommandListener.cpp:
(WebCore::RemoteCommandListener::scheduleSupportedCommandsUpdate):
(WebCore::RemoteCommandListener::addSupportedCommand):
(WebCore::RemoteCommandListener::removeSupportedCommand):
* platform/RemoteCommandListener.h:
* platform/audio/NowPlayingInfo.h:
(WebCore::NowPlayingInfo::decode):
* platform/audio/PlatformMediaSession.cpp:
(WebCore::convertEnumerationToString):
* platform/audio/PlatformMediaSession.h:
* platform/audio/PlatformMediaSessionManager.h:
(WebCore::PlatformMediaSessionManager::addSupportedCommand):
(WebCore::PlatformMediaSessionManager::removeSupportedCommand):
* platform/audio/cocoa/MediaSessionManagerCocoa.h:
* platform/audio/cocoa/MediaSessionManagerCocoa.mm:
(WebCore::MediaSessionManagerCocoa::scheduleSessionStatusUpdate):
(WebCore::MediaSessionManagerCocoa::sessionCanProduceAudioChanged):
(WebCore::MediaSessionManagerCocoa::addSupportedCommand):
(WebCore::MediaSessionManagerCocoa::removeSupportedCommand):
(WebCore::MediaSessionManagerCocoa::setNowPlayingInfo):
* platform/mac/MediaRemoteSoftLink.cpp:
* platform/mac/MediaRemoteSoftLink.h:
* platform/mac/RemoteCommandListenerMac.h:
* platform/mac/RemoteCommandListenerMac.mm:
(WebCore::mediaRemoteCommandForPlatformCommand):
(WebCore::RemoteCommandListenerMac::defaultCommands):
(WebCore::isSeekCommand):
(WebCore::RemoteCommandListenerMac::updateSupportedCommands):
(WebCore::RemoteCommandListenerMac::RemoteCommandListenerMac):

Source/WebCore/PAL:

* pal/spi/mac/MediaRemoteSPI.h:

Source/WebKit:

Add a private preference so the new MediaSession API test can enable the feature.

* UIProcess/API/Cocoa/WKPreferences.mm:
(-[WKPreferences _mediaSessionEnabled]):
(-[WKPreferences _setMediaSessionEnabled:]):
* UIProcess/API/Cocoa/WKPreferencesPrivate.h:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm: Added.
(TestWebKitAPI::MediaSessionTest::webView):
(TestWebKitAPI::MediaSessionTest::webViewPid):
(TestWebKitAPI::MediaSessionTest::getNowPlayingClient):
(TestWebKitAPI::MediaSessionTest::getNowPlayingClientPid):
(TestWebKitAPI::MediaSessionTest::loadPageAndBecomeNowPlaying):
(TestWebKitAPI::MediaSessionTest::runScriptWithUserGesture):
(TestWebKitAPI::MediaSessionTest::play):
(TestWebKitAPI::MediaSessionTest::pause):
(TestWebKitAPI::MediaSessionTest::sendMediaRemoteCommand):
(TestWebKitAPI::MediaSessionTest::sendMediaRemoteSeekCommand):
(TestWebKitAPI::MediaSessionTest::listenForEventMessages):
(TestWebKitAPI::MediaSessionTest::eventListenerWasCalled):
(TestWebKitAPI::MediaSessionTest::waitForEventListenerToBeCalled):
(TestWebKitAPI::MediaSessionTest::listenForSessionHandlerMessages):
(TestWebKitAPI::MediaSessionTest::sessionHandlerWasCalled):
(TestWebKitAPI::MediaSessionTest::waitForSessionHandlerToBeCalled):
(TestWebKitAPI::MediaSessionTest::getSupportedCommands):
(TestWebKitAPI::TEST_F):
* TestWebKitAPI/Tests/WebKitCocoa/media-remote.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (272444 => 272445)


--- trunk/Source/WebCore/ChangeLog	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/ChangeLog	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,3 +1,87 @@
+2021-02-05  Eric Carlson  <[email protected]>
+
+        [Mac] Connect MediaSession with MediaRemote and NowPlaying
+        https://bugs.webkit.org/show_bug.cgi?id=221431
+        <rdar://problem/74000363>
+
+        Reviewed by Jer Noble.
+
+        Route all remote control commands to navigator.mediaSession when it has any 
+        registered action handlers, and pass navigator.mediaSession.metadata to NowPlaying.
+
+        Tested with new API tests in Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm.
+
+        * Modules/mediasession/MediaImage.h:
+        (WebCore::MediaImage::encode const):
+        (WebCore::MediaImage::decode):
+        * Modules/mediasession/MediaMetadata.cpp:
+        (WebCore::MediaMetadata::setTitle):
+        (WebCore::MediaMetadata::setArtist):
+        (WebCore::MediaMetadata::setAlbum):
+        (WebCore::MediaMetadata::setArtwork):
+        * Modules/mediasession/MediaMetadata.h:
+        (WebCore::MediaMetadata::title const):
+        (WebCore::MediaMetadata::artist const):
+        (WebCore::MediaMetadata::album const):
+        (WebCore::MediaMetadata::artwork const):
+        (WebCore::MediaMetadata::metadata const):
+        * Modules/mediasession/MediaMetadataInit.h:
+        (WebCore::MediaMetadataInit::encode const):
+        (WebCore::MediaMetadataInit::decode):
+        * Modules/mediasession/MediaSession.cpp:
+        (WebCore::nextLogIdentifier):
+        (WebCore::logChannel):
+        (WebCore::logClassName):
+        (WebCore::platformCommandForMediaSessionAction):
+        (WebCore::MediaSession::MediaSession):
+        (WebCore::MediaSession::setMetadata):
+        (WebCore::MediaSession::setPlaybackState):
+        (WebCore::MediaSession::setActionHandler):
+        (WebCore::MediaSession::setPositionState):
+        * Modules/mediasession/MediaSession.h:
+        (WebCore::MediaSession::hasActiveActionHandlers const):
+        (WebCore::MediaSession::logger const):
+        (WebCore::MediaSession::logIdentifier const):
+        (WTF::LogArgument<WebCore::MediaSessionPlaybackState>::toString):
+        (WTF::LogArgument<WebCore::MediaSessionAction>::toString):
+        * WebCore.xcodeproj/project.pbxproj:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::didReceiveRemoteControlCommand):
+        * html/MediaElementSession.cpp:
+        (WebCore::MediaElementSession::didReceiveRemoteControlCommand):
+        (WebCore::MediaElementSession::nowPlayingInfo const):
+        * html/MediaElementSession.h:
+        * page/ChromeClient.h:
+        * platform/RemoteCommandListener.cpp:
+        (WebCore::RemoteCommandListener::scheduleSupportedCommandsUpdate):
+        (WebCore::RemoteCommandListener::addSupportedCommand):
+        (WebCore::RemoteCommandListener::removeSupportedCommand):
+        * platform/RemoteCommandListener.h:
+        * platform/audio/NowPlayingInfo.h:
+        (WebCore::NowPlayingInfo::decode):
+        * platform/audio/PlatformMediaSession.cpp:
+        (WebCore::convertEnumerationToString):
+        * platform/audio/PlatformMediaSession.h:
+        * platform/audio/PlatformMediaSessionManager.h:
+        (WebCore::PlatformMediaSessionManager::addSupportedCommand):
+        (WebCore::PlatformMediaSessionManager::removeSupportedCommand):
+        * platform/audio/cocoa/MediaSessionManagerCocoa.h:
+        * platform/audio/cocoa/MediaSessionManagerCocoa.mm:
+        (WebCore::MediaSessionManagerCocoa::scheduleSessionStatusUpdate):
+        (WebCore::MediaSessionManagerCocoa::sessionCanProduceAudioChanged):
+        (WebCore::MediaSessionManagerCocoa::addSupportedCommand):
+        (WebCore::MediaSessionManagerCocoa::removeSupportedCommand):
+        (WebCore::MediaSessionManagerCocoa::setNowPlayingInfo):
+        * platform/mac/MediaRemoteSoftLink.cpp:
+        * platform/mac/MediaRemoteSoftLink.h:
+        * platform/mac/RemoteCommandListenerMac.h:
+        * platform/mac/RemoteCommandListenerMac.mm:
+        (WebCore::mediaRemoteCommandForPlatformCommand):
+        (WebCore::RemoteCommandListenerMac::defaultCommands):
+        (WebCore::isSeekCommand):
+        (WebCore::RemoteCommandListenerMac::updateSupportedCommands):
+        (WebCore::RemoteCommandListenerMac::RemoteCommandListenerMac):
+
 2021-02-05  Jer Noble  <[email protected]>
 
         [Cocoa] CRASH in MediaPlayerPrivateMediaSourceAVFObjC::removeAudioRenderer()

Modified: trunk/Source/WebCore/Modules/mediasession/MediaImage.h (272444 => 272445)


--- trunk/Source/WebCore/Modules/mediasession/MediaImage.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/Modules/mediasession/MediaImage.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -33,8 +33,33 @@
     String src;
     String sizes;
     String type;
+
+    template<class Encoder> void encode(Encoder&) const;
+    template<class Decoder> static Optional<MediaImage> decode(Decoder&);
 };
 
+template<class Encoder> inline void MediaImage::encode(Encoder& encoder) const
+{
+    encoder << src << sizes << type;
 }
 
+template<class Decoder> inline Optional<MediaImage> MediaImage::decode(Decoder& decoder)
+{
+    String src;
+    if (!decoder.decode(src))
+        return { };
+
+    String sizes;
+    if (!decoder.decode(sizes))
+        return { };
+
+    String type;
+    if (!decoder.decode(type))
+        return { };
+
+    return MediaImage { WTFMove(src), WTFMove(sizes), WTFMove(type) };
+}
+
+}
+
 #endif // ENABLE(MEDIA_SESSION)

Modified: trunk/Source/WebCore/Modules/mediasession/MediaMetadata.cpp (272444 => 272445)


--- trunk/Source/WebCore/Modules/mediasession/MediaMetadata.cpp	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/Modules/mediasession/MediaMetadata.cpp	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -64,25 +64,28 @@
 
 void MediaMetadata::setTitle(const String& title)
 {
-    if (m_title == title)
+    if (m_metadata.title == title)
         return;
-    m_title = title;
+
+    m_metadata.title = title;
     metadataUpdated();
 }
 
 void MediaMetadata::setArtist(const String& artist)
 {
-    if (m_artist == artist)
+    if (m_metadata.artist == artist)
         return;
-    m_artist = artist;
+
+    m_metadata.artist = artist;
     metadataUpdated();
 }
 
 void MediaMetadata::setAlbum(const String& album)
 {
-    if (m_album == album)
+    if (m_metadata.album == album)
         return;
-    m_album = album;
+
+    m_metadata.album = album;
     metadataUpdated();
 }
 
@@ -97,7 +100,7 @@
         resolvedArtwork.uncheckedAppend(MediaImage { resolvedSrc.string(), image.sizes, image.type });
     }
 
-    m_artwork = WTFMove(resolvedArtwork);
+    m_metadata.artwork = WTFMove(resolvedArtwork);
     metadataUpdated();
     return { };
 }

Modified: trunk/Source/WebCore/Modules/mediasession/MediaMetadata.h (272444 => 272445)


--- trunk/Source/WebCore/Modules/mediasession/MediaMetadata.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/Modules/mediasession/MediaMetadata.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,6 +27,7 @@
 
 #if ENABLE(MEDIA_SESSION)
 
+#include "MediaMetadataInit.h"
 #include "MediaSession.h"
 #include <wtf/Optional.h>
 #include <wtf/Vector.h>
@@ -34,11 +35,10 @@
 
 namespace WebCore {
 
-class Document;
-class MediaSession;
 struct MediaImage;
-struct MediaMetadataInit;
 
+using MediaSessionMetadata = MediaMetadataInit;
+
 class MediaMetadata : public RefCounted<MediaMetadata> {
 public:
     static ExceptionOr<Ref<MediaMetadata>> create(ScriptExecutionContext&, Optional<MediaMetadataInit>&&);
@@ -47,27 +47,26 @@
     void setMediaSession(MediaSession&);
     void resetMediaSession();
 
-    const String& title() const { return m_title; }
+    const String& title() const { return m_metadata.title; }
     void setTitle(const String&);
 
-    const String& artist() const { return m_artist; }
+    const String& artist() const { return m_metadata.artist; }
     void setArtist(const String&);
 
-    const String& album() const { return m_album; }
+    const String& album() const { return m_metadata.album; }
     void setAlbum(const String&);
 
-    const Vector<MediaImage>& artwork() const { return m_artwork; }
+    const Vector<MediaImage>& artwork() const { return m_metadata.artwork; }
     ExceptionOr<void> setArtwork(ScriptExecutionContext&, Vector<MediaImage>&&);
 
+    const MediaSessionMetadata& metadata() const { return m_metadata; }
+
 private:
     MediaMetadata();
     void metadataUpdated();
 
     WeakPtr<MediaSession> m_session;
-    String m_title;
-    String m_artist;
-    String m_album;
-    Vector<MediaImage> m_artwork;
+    MediaSessionMetadata m_metadata;
 };
 
 }

Modified: trunk/Source/WebCore/Modules/mediasession/MediaMetadataInit.h (272444 => 272445)


--- trunk/Source/WebCore/Modules/mediasession/MediaMetadataInit.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/Modules/mediasession/MediaMetadataInit.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -36,8 +36,37 @@
     String artist;
     String album;
     Vector<MediaImage> artwork;
+
+    template<class Encoder> void encode(Encoder&) const;
+    template<class Decoder> static Optional<MediaMetadataInit> decode(Decoder&);
 };
 
+template<class Encoder> inline void MediaMetadataInit::encode(Encoder& encoder) const
+{
+    encoder << title << artist << album << artwork;
 }
 
+template<class Decoder> inline Optional<MediaMetadataInit> MediaMetadataInit::decode(Decoder& decoder)
+{
+    String title;
+    if (!decoder.decode(title))
+        return { };
+
+    String artist;
+    if (!decoder.decode(artist))
+        return { };
+
+    String album;
+    if (!decoder.decode(album))
+        return { };
+
+    Vector<MediaImage> artwork;
+    if (!decoder.decode(artwork))
+        return { };
+
+    return MediaMetadataInit { WTFMove(title), WTFMove(artist), WTFMove(album), WTFMove(artwork) };
+}
+
+}
+
 #endif // ENABLE(MEDIA_SESSION)

Modified: trunk/Source/WebCore/Modules/mediasession/MediaSession.cpp (272444 => 272445)


--- trunk/Source/WebCore/Modules/mediasession/MediaSession.cpp	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/Modules/mediasession/MediaSession.cpp	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -28,11 +28,46 @@
 
 #if ENABLE(MEDIA_SESSION)
 
+#include "Logging.h"
 #include "MediaMetadata.h"
 #include "Navigator.h"
+#include "PlatformMediaSessionManager.h"
 
 namespace WebCore {
 
+static const void* nextLogIdentifier()
+{
+    static uint64_t logIdentifier = cryptographicallyRandomNumber();
+    return reinterpret_cast<const void*>(++logIdentifier);
+}
+
+static WTFLogChannel& logChannel() { return LogMedia; }
+static const char* logClassName() { return "MediaSession"; }
+
+static PlatformMediaSession::RemoteControlCommandType platformCommandForMediaSessionAction(MediaSessionAction action)
+{
+    static const auto commandMap = makeNeverDestroyed([] {
+        using ActionToCommandMap = HashMap<MediaSessionAction, PlatformMediaSession::RemoteControlCommandType, WTF::IntHash<MediaSessionAction>, WTF::StrongEnumHashTraits<MediaSessionAction>>;
+
+        return ActionToCommandMap {
+            { MediaSessionAction::Play, PlatformMediaSession::PlayCommand },
+            { MediaSessionAction::Pause, PlatformMediaSession::PauseCommand },
+            { MediaSessionAction::Seekforward, PlatformMediaSession::SkipForwardCommand },
+            { MediaSessionAction::Seekbackward, PlatformMediaSession::SkipBackwardCommand },
+            { MediaSessionAction::Previoustrack, PlatformMediaSession::NextTrackCommand },
+            { MediaSessionAction::Nexttrack, PlatformMediaSession::PreviousTrackCommand },
+            { MediaSessionAction::Stop, PlatformMediaSession::StopCommand },
+            { MediaSessionAction::Seekto, PlatformMediaSession::SeekToPlaybackPositionCommand },
+        };
+    }());
+
+    auto it = commandMap.get().find(action);
+    if (it != commandMap.get().end())
+        return it->value;
+
+    return PlatformMediaSession::NoCommand;
+}
+
 Ref<MediaSession> MediaSession::create(Navigator& navigator)
 {
     return adoptRef(*new MediaSession(navigator));
@@ -41,6 +76,10 @@
 MediaSession::MediaSession(Navigator& navigator)
     : m_navigator(makeWeakPtr(navigator))
 {
+    m_logger = makeRefPtr(Document::sharedLogger());
+    m_logIdentifier = nextLogIdentifier();
+
+    ALWAYS_LOG(LOGIDENTIFIER);
 }
 
 MediaSession::~MediaSession() = default;
@@ -47,6 +86,8 @@
 
 void MediaSession::setMetadata(RefPtr<MediaMetadata>&& metadata)
 {
+    ALWAYS_LOG(LOGIDENTIFIER);
+
     if (m_metadata)
         m_metadata->resetMediaSession();
     m_metadata = WTFMove(metadata);
@@ -60,6 +101,8 @@
     if (m_playbackState == state)
         return;
 
+    ALWAYS_LOG(LOGIDENTIFIER, state);
+
     auto currentPosition = this->currentPosition();
     if (m_positionState && currentPosition) {
         m_positionState->position = *currentPosition;
@@ -70,10 +113,18 @@
 
 void MediaSession::setActionHandler(MediaSessionAction action, RefPtr<MediaSessionActionHandler>&& handler)
 {
-    if (handler)
+    if (handler) {
+        ALWAYS_LOG(LOGIDENTIFIER, "adding ", action);
         m_actionHandlers.set(action, handler);
-    else
-        m_actionHandlers.remove(action);
+        PlatformMediaSessionManager::sharedManager().addSupportedCommand(platformCommandForMediaSessionAction(action));
+    } else {
+        if (m_actionHandlers.contains(action)) {
+            ALWAYS_LOG(LOGIDENTIFIER, "removing ", action);
+            m_actionHandlers.remove(action);
+        }
+        PlatformMediaSessionManager::sharedManager().removeSupportedCommand(platformCommandForMediaSessionAction(action));
+    }
+    
     actionHandlersUpdated();
 }
 
@@ -89,6 +140,8 @@
 
 ExceptionOr<void> MediaSession::setPositionState(Optional<MediaPositionState>&& state)
 {
+    ALWAYS_LOG(LOGIDENTIFIER);
+
     if (!state) {
         m_positionState = WTF::nullopt;
         return { };

Modified: trunk/Source/WebCore/Modules/mediasession/MediaSession.h (272444 => 272445)


--- trunk/Source/WebCore/Modules/mediasession/MediaSession.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/Modules/mediasession/MediaSession.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,10 +27,14 @@
 
 #if ENABLE(MEDIA_SESSION)
 
+#include "JSMediaPositionState.h"
+#include "JSMediaSessionAction.h"
+#include "JSMediaSessionPlaybackState.h"
 #include "MediaPositionState.h"
 #include "MediaSessionAction.h"
 #include "MediaSessionActionHandler.h"
 #include "MediaSessionPlaybackState.h"
+#include <wtf/Logger.h>
 #include <wtf/MonotonicTime.h>
 #include <wtf/Optional.h>
 #include <wtf/UniqueRef.h>
@@ -57,13 +61,18 @@
     WEBCORE_EXPORT Optional<double> currentPosition() const;
 
     void metadataUpdated();
+
     void actionHandlersUpdated();
     bool hasActionHandler(MediaSessionAction) const;
     WEBCORE_EXPORT RefPtr<MediaSessionActionHandler> handlerForAction(MediaSessionAction) const;
+    bool hasActiveActionHandlers() const { return !m_actionHandlers.isEmpty(); }
 
 private:
     explicit MediaSession(Navigator&);
 
+    const Logger& logger() const { return *m_logger.get(); }
+    const void* logIdentifier() const { return m_logIdentifier; }
+
     WeakPtr<Navigator> m_navigator;
     RefPtr<MediaMetadata> m_metadata;
     MediaSessionPlaybackState m_playbackState { MediaSessionPlaybackState::None };
@@ -71,8 +80,22 @@
     Optional<double> m_lastReportedPosition;
     MonotonicTime m_timeAtLastPositionUpdate;
     HashMap<MediaSessionAction, RefPtr<MediaSessionActionHandler>, WTF::IntHash<MediaSessionAction>, WTF::StrongEnumHashTraits<MediaSessionAction>> m_actionHandlers;
+    RefPtr<const Logger> m_logger;
+    const void* m_logIdentifier;
 };
 
 }
 
+namespace WTF {
+
+template<> struct LogArgument<WebCore::MediaSessionPlaybackState> {
+    static String toString(WebCore::MediaSessionPlaybackState state) { return convertEnumerationToString(state); }
+};
+
+template<> struct LogArgument<WebCore::MediaSessionAction> {
+    static String toString(WebCore::MediaSessionAction action) { return convertEnumerationToString(action); }
+};
+
+}
+
 #endif // ENABLE(MEDIA_SESSION)

Modified: trunk/Source/WebCore/PAL/ChangeLog (272444 => 272445)


--- trunk/Source/WebCore/PAL/ChangeLog	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/PAL/ChangeLog	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,3 +1,13 @@
+2021-02-05  Eric Carlson  <[email protected]>
+
+        [Mac] Connect MediaSession with MediaRemote and NowPlaying
+        https://bugs.webkit.org/show_bug.cgi?id=221431
+        <rdar://problem/74000363>
+
+        Reviewed by Jer Noble.
+
+        * pal/spi/mac/MediaRemoteSPI.h:
+
 2021-02-01  Jer Noble  <[email protected]>
 
         [Cocoa] Disable interstitial events on AVPlayerItem.

Modified: trunk/Source/WebCore/PAL/pal/spi/mac/MediaRemoteSPI.h (272444 => 272445)


--- trunk/Source/WebCore/PAL/pal/spi/mac/MediaRemoteSPI.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/PAL/pal/spi/mac/MediaRemoteSPI.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -92,6 +92,8 @@
 typedef uint32_t MRNowPlayingClientVisibility;
 
 typedef uint32_t MRMediaRemoteError;
+typedef uint32_t MRSendCommandAppOptions;
+typedef uint32_t MRSendCommandError;
 typedef struct _MROrigin *MROriginRef;
 typedef struct _MRMediaRemoteCommandInfo *MRMediaRemoteCommandInfoRef;
 typedef void *MRNowPlayingClientRef;
@@ -103,8 +105,10 @@
 
 void* MRMediaRemoteAddAsyncCommandHandlerBlock(MRMediaRemoteAsyncCommandHandlerBlock);
 void MRMediaRemoteRemoveCommandHandlerBlock(void *observer);
-void MRMediaRemoteSetSupportedCommands(CFArrayRef commands, MROriginRef, dispatch_queue_t replyQ, void(^completion)(MRMediaRemoteError err));
+void MRMediaRemoteSetSupportedCommands(CFArrayRef, MROriginRef, dispatch_queue_t, void(^completion)(MRMediaRemoteError));
+void MRMediaRemoteGetSupportedCommandsForOrigin(MROriginRef, dispatch_queue_t, void(^completion)(CFArrayRef));
 void MRMediaRemoteSetNowPlayingVisibility(MROriginRef, MRNowPlayingClientVisibility);
+Boolean MRMediaRemoteSendCommandToApp(MRMediaRemoteCommand, CFDictionaryRef, MROriginRef, CFStringRef, MRSendCommandAppOptions, dispatch_queue_t, void(^completion)(MRSendCommandError, CFArrayRef));
 
 #pragma mark - MROrigin
 

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (272444 => 272445)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2021-02-05 23:18:55 UTC (rev 272445)
@@ -139,6 +139,10 @@
 		077AF14318F4B1BB0001ED61 /* SerializedPlatformDataCueMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 077AF14118F4B1BB0001ED61 /* SerializedPlatformDataCueMac.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		077B64131B94F12E003E9AD5 /* MediaPlaybackTargetPickerMock.h in Headers */ = {isa = PBXBuildFile; fileRef = 077B64111B94F12E003E9AD5 /* MediaPlaybackTargetPickerMock.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		077B64171B95F703003E9AD5 /* MediaPlaybackTargetMock.h in Headers */ = {isa = PBXBuildFile; fileRef = 077B64151B95F703003E9AD5 /* MediaPlaybackTargetMock.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		0782894825C23FE500A7BA03 /* MediaMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = CDDDEA2D2538CE0400A1300C /* MediaMetadata.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		0782894A25C36FCF00A7BA03 /* MediaSession.h in Headers */ = {isa = PBXBuildFile; fileRef = CDDDEA232538CD8000A1300C /* MediaSession.h */; };
+		0782894B25C36FED00A7BA03 /* MediaImage.h in Headers */ = {isa = PBXBuildFile; fileRef = CDDDEA322538CE3500A1300C /* MediaImage.h */; };
+		0782894C25C3700B00A7BA03 /* MediaMetadataInit.h in Headers */ = {isa = PBXBuildFile; fileRef = CDDDEA302538CE1E00A1300C /* MediaMetadataInit.h */; };
 		0783228518013ED800999E0C /* MediaStreamAudioSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 0783228318013ED800999E0C /* MediaStreamAudioSource.h */; };
 		07846343145B151A00A58DF1 /* JSTrackEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 07846341145B151A00A58DF1 /* JSTrackEvent.h */; };
 		07846385145B1B8E00A58DF1 /* JSTrackCustom.h in Headers */ = {isa = PBXBuildFile; fileRef = 07846384145B1B8E00A58DF1 /* JSTrackCustom.h */; };
@@ -33702,10 +33706,13 @@
 				E44613AD0CD6331000FADA75 /* MediaError.h in Headers */,
 				4E1959220A39DABA00220FE5 /* MediaFeatureNames.h in Headers */,
 				07A6D1EC1491137700051D0C /* MediaFragmentURIParser.h in Headers */,
+				0782894B25C36FED00A7BA03 /* MediaImage.h in Headers */,
 				CDF4B71A1E01D3D000E235A2 /* MediaKeySessionType.idl in Headers */,
 				CDF4B7281E03C57300E235A2 /* MediaKeysRequirement.idl in Headers */,
 				CDF4B7181E01CB9100E235A2 /* MediaKeysRestrictions.h in Headers */,
 				A8EA800E0A19516E00A8EF5F /* MediaList.h in Headers */,
+				0782894825C23FE500A7BA03 /* MediaMetadata.h in Headers */,
+				0782894C25C3700B00A7BA03 /* MediaMetadataInit.h in Headers */,
 				5EBB89311C7777FF00C65D41 /* MediaPayload.h in Headers */,
 				07E3DFD11A9E786500764CA8 /* MediaPlaybackTarget.h in Headers */,
 				079216551AA560AA00A3C049 /* MediaPlaybackTargetClient.h in Headers */,
@@ -33746,6 +33753,7 @@
 				1B124D8D1D380B7000ECDFB0 /* MediaSampleAVFObjC.h in Headers */,
 				CDBEAEAD19D92B6C00BEBA88 /* MediaSelectionGroupAVFObjC.h in Headers */,
 				A17D275E1EAC579800BF01E7 /* MediaSelectionOption.h in Headers */,
+				0782894A25C36FCF00A7BA03 /* MediaSession.h in Headers */,
 				416ECAE525B58CC400B34DA5 /* MediaSessionGroupIdentifier.h in Headers */,
 				CDA9593F2412BAE000910EEF /* MediaSessionHelperIOS.h in Headers */,
 				414460A22412994500814BE7 /* MediaSessionIdentifier.h in Headers */,

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (272444 => 272445)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2007-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -7519,6 +7519,7 @@
     ALWAYS_LOG(LOGIDENTIFIER, command);
 
     UserGestureIndicator remoteControlUserGesture(ProcessingUserGesture, &document());
+    double offset = 15;
     switch (command) {
     case PlatformMediaSession::PlayCommand:
         play();
@@ -7540,6 +7541,16 @@
     case PlatformMediaSession::EndSeekingForwardCommand:
         endScanning();
         break;
+    case PlatformMediaSession::SkipForwardCommand:
+        if (argument)
+            offset = argument->asDouble;
+        handleSeekToPlaybackPosition(offset);
+        break;
+    case PlatformMediaSession::SkipBackwardCommand:
+        if (argument)
+            offset = argument->asDouble;
+        handleSeekToPlaybackPosition(0 - offset);
+        break;
     case PlatformMediaSession::SeekToPlaybackPositionCommand:
         ASSERT(argument);
         if (argument)

Modified: trunk/Source/WebCore/html/MediaElementSession.cpp (272444 => 272445)


--- trunk/Source/WebCore/html/MediaElementSession.cpp	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/html/MediaElementSession.cpp	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -54,6 +54,12 @@
 #include "SourceBuffer.h"
 #include <wtf/text/StringBuilder.h>
 
+#if ENABLE(MEDIA_SESSION)
+#include "MediaMetadata.h"
+#include "MediaSession.h"
+#include "NavigatorMediaSession.h"
+#endif
+
 #if PLATFORM(IOS_FAMILY)
 #include "AudioSession.h"
 #include "RuntimeApplicationChecks.h"
@@ -1021,6 +1027,70 @@
     return page && page->allowsPlaybackControlsForAutoplayingAudio();
 }
 
+#if ENABLE(MEDIA_SESSION)
+void MediaElementSession::didReceiveRemoteControlCommand(RemoteControlCommandType commandType, const RemoteCommandArgument* argument)
+{
+    auto* window = m_element.document().domWindow();
+    auto* session = window ? &NavigatorMediaSession::mediaSession(window->navigator()) : nullptr;
+    if (!session || !session->hasActiveActionHandlers()) {
+        PlatformMediaSession::didReceiveRemoteControlCommand(commandType, argument);
+        return;
+    }
+
+    MediaSessionActionDetails actionDetails;
+    switch (commandType) {
+    case NoCommand:
+        return;
+    case PlayCommand:
+        actionDetails.action = ""
+        break;
+    case PauseCommand:
+        actionDetails.action = ""
+        break;
+    case StopCommand:
+        actionDetails.action = ""
+        break;
+    case TogglePlayPauseCommand:
+        actionDetails.action = "" ? MediaSessionAction::Play : MediaSessionAction::Pause;
+        break;
+    case SeekToPlaybackPositionCommand:
+        ASSERT(argument);
+        if (!argument)
+            return;
+        actionDetails.action = ""
+        actionDetails.seekTime = argument->asDouble;
+        break;
+    case SkipForwardCommand:
+        if (argument)
+            actionDetails.seekOffset = argument->asDouble;
+        actionDetails.action = ""
+        break;
+    case SkipBackwardCommand:
+        if (argument)
+            actionDetails.seekOffset = argument->asDouble;
+        actionDetails.action = ""
+        break;
+    case NextTrackCommand:
+        actionDetails.action = ""
+        break;
+    case PreviousTrackCommand:
+        actionDetails.action = ""
+        break;
+    case BeginSeekingBackwardCommand:
+    case EndSeekingBackwardCommand:
+    case BeginSeekingForwardCommand:
+    case EndSeekingForwardCommand:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+    
+    if (auto handler = session->handlerForAction(actionDetails.action))
+        handler->handleEvent(actionDetails);
+    else
+        ALWAYS_LOG(LOGIDENTIFIER, "Ignoring command, no action handler registered for ", actionDetails.action);
+}
+#endif
+
 Optional<NowPlayingInfo> MediaElementSession::nowPlayingInfo() const
 {
     auto* page = m_element.document().page();
@@ -1032,7 +1102,14 @@
     if (!std::isfinite(currentTime) || !supportsSeeking)
         currentTime = MediaPlayer::invalidTime();
 
-    return NowPlayingInfo { m_element.mediaSessionTitle(), m_element.sourceApplicationIdentifier(), duration, currentTime, supportsSeeking, m_element.mediaSessionUniqueIdentifier(), isPlaying, allowsNowPlayingControlsVisibility };
+#if ENABLE(MEDIA_SESSION)
+    auto* window = m_element.document().domWindow();
+    auto* sessionMetadata = window ? NavigatorMediaSession::mediaSession(window->navigator()).metadata() : nullptr;
+    if (sessionMetadata)
+        return NowPlayingInfo { sessionMetadata->title(), sessionMetadata->artist(), sessionMetadata->album(), m_element.sourceApplicationIdentifier(), duration, currentTime, supportsSeeking, m_element.mediaSessionUniqueIdentifier(), isPlaying, allowsNowPlayingControlsVisibility };
+#endif
+
+    return NowPlayingInfo { m_element.mediaSessionTitle(), emptyString(), emptyString(), m_element.sourceApplicationIdentifier(), duration, currentTime, supportsSeeking, m_element.mediaSessionUniqueIdentifier(), isPlaying, allowsNowPlayingControlsVisibility };
 }
 
 void MediaElementSession::updateMediaUsageIfChanged()

Modified: trunk/Source/WebCore/html/MediaElementSession.h (272444 => 272445)


--- trunk/Source/WebCore/html/MediaElementSession.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/html/MediaElementSession.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -174,6 +174,10 @@
     const char* logClassName() const final { return "MediaElementSession"; }
 #endif
 
+#if ENABLE(MEDIA_SESSION)
+    void didReceiveRemoteControlCommand(RemoteControlCommandType, const RemoteCommandArgument* = nullptr) final;
+#endif
+
 private:
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)

Modified: trunk/Source/WebCore/page/ChromeClient.h (272444 => 272445)


--- trunk/Source/WebCore/page/ChromeClient.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/page/ChromeClient.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -101,7 +101,6 @@
 class HTMLVideoElement;
 class HitTestResult;
 class IntRect;
-class MediaSessionMetadata;
 class NavigationAction;
 class Node;
 class Page;

Modified: trunk/Source/WebCore/platform/RemoteCommandListener.cpp (272444 => 272445)


--- trunk/Source/WebCore/platform/RemoteCommandListener.cpp	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/RemoteCommandListener.cpp	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -37,4 +37,25 @@
 
 #endif
 
+void RemoteCommandListener::scheduleSupportedCommandsUpdate()
+{
+    if (!m_updateCommandsTask.hasPendingTask()) {
+        m_updateCommandsTask.scheduleTask([this] ()  {
+            updateSupportedCommands();
+        });
+    }
 }
+
+void RemoteCommandListener::addSupportedCommand(PlatformMediaSession::RemoteControlCommandType command)
+{
+    m_registeredCommands.add(command);
+    scheduleSupportedCommandsUpdate();
+}
+
+void RemoteCommandListener::removeSupportedCommand(PlatformMediaSession::RemoteControlCommandType command)
+{
+    m_registeredCommands.remove(command);
+    scheduleSupportedCommandsUpdate();
+}
+
+}

Modified: trunk/Source/WebCore/platform/RemoteCommandListener.h (272444 => 272445)


--- trunk/Source/WebCore/platform/RemoteCommandListener.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/RemoteCommandListener.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -26,6 +26,7 @@
 #ifndef RemoteCommandListener_h
 #define RemoteCommandListener_h
 
+#include "DeferrableTask.h"
 #include "PlatformMediaSession.h"
 
 namespace WebCore {
@@ -45,12 +46,20 @@
     RemoteCommandListener(RemoteCommandListenerClient& client) : m_client(client) { }
     virtual ~RemoteCommandListener() = default;
 
+    void addSupportedCommand(PlatformMediaSession::RemoteControlCommandType);
+    void removeSupportedCommand(PlatformMediaSession::RemoteControlCommandType);
     virtual void updateSupportedCommands() { }
+    void scheduleSupportedCommandsUpdate();
 
     RemoteCommandListenerClient& client() const { return m_client; }
 
 protected:
     RemoteCommandListenerClient& m_client;
+
+    using RemoteCommandsSet = HashSet<PlatformMediaSession::RemoteControlCommandType, WTF::IntHash<PlatformMediaSession::RemoteControlCommandType>, WTF::StrongEnumHashTraits<PlatformMediaSession::RemoteControlCommandType>>;
+    RemoteCommandsSet m_registeredCommands;
+
+    DeferrableTask<Timer> m_updateCommandsTask;
 };
 
 }

Modified: trunk/Source/WebCore/platform/audio/NowPlayingInfo.h (272444 => 272445)


--- trunk/Source/WebCore/platform/audio/NowPlayingInfo.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/audio/NowPlayingInfo.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -32,6 +32,8 @@
 
 struct NowPlayingInfo {
     String title;
+    String artist;
+    String album;
     String sourceApplicationIdentifier;
     double duration { 0 };
     double currentTime { 0 };
@@ -55,6 +57,14 @@
     if (!decoder.decode(title))
         return { };
 
+    String artist;
+    if (!decoder.decode(artist))
+        return { };
+
+    String album;
+    if (!decoder.decode(album))
+        return { };
+
     String sourceApplicationIdentifier;
     if (!decoder.decode(sourceApplicationIdentifier))
         return { };
@@ -83,7 +93,7 @@
     if (!decoder.decode(allowsNowPlayingControlsVisibility))
         return { };
 
-    return NowPlayingInfo { WTFMove(title), WTFMove(sourceApplicationIdentifier), duration, currentTime, supportsSeeking, uniqueIdentifier, isPlaying, allowsNowPlayingControlsVisibility };
+    return NowPlayingInfo { WTFMove(title), WTFMove(artist), WTFMove(album), WTFMove(sourceApplicationIdentifier), duration, currentTime, supportsSeeking, uniqueIdentifier, isPlaying, allowsNowPlayingControlsVisibility };
 }
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/platform/audio/PlatformMediaSession.cpp (272444 => 272445)


--- trunk/Source/WebCore/platform/audio/PlatformMediaSession.cpp	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/audio/PlatformMediaSession.cpp	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -93,6 +93,10 @@
         MAKE_STATIC_STRING_IMPL("BeginSeekingForwardCommand"),
         MAKE_STATIC_STRING_IMPL("EndSeekingForwardCommand"),
         MAKE_STATIC_STRING_IMPL("SeekToPlaybackPositionCommand"),
+        MAKE_STATIC_STRING_IMPL("SkipForwardCommand"),
+        MAKE_STATIC_STRING_IMPL("SkipBackwardCommand"),
+        MAKE_STATIC_STRING_IMPL("NextTrackCommand"),
+        MAKE_STATIC_STRING_IMPL("PreviousTrackCommand"),
     };
     static_assert(!static_cast<size_t>(PlatformMediaSession::NoCommand), "PlatformMediaSession::NoCommand is not 0 as expected");
     static_assert(static_cast<size_t>(PlatformMediaSession::PlayCommand) == 1, "PlatformMediaSession::PlayCommand is not 1 as expected");
@@ -104,6 +108,10 @@
     static_assert(static_cast<size_t>(PlatformMediaSession::BeginSeekingForwardCommand) == 7, "PlatformMediaSession::BeginSeekingForwardCommand is not 7 as expected");
     static_assert(static_cast<size_t>(PlatformMediaSession::EndSeekingForwardCommand) == 8, "PlatformMediaSession::EndSeekingForwardCommand is not 8 as expected");
     static_assert(static_cast<size_t>(PlatformMediaSession::SeekToPlaybackPositionCommand) == 9, "PlatformMediaSession::SeekToPlaybackPositionCommand is not 9 as expected");
+    static_assert(static_cast<size_t>(PlatformMediaSession::SkipForwardCommand) == 10, "PlatformMediaSession::SkipForwardCommand is not 10 as expected");
+    static_assert(static_cast<size_t>(PlatformMediaSession::SkipBackwardCommand) == 11, "PlatformMediaSession::SkipBackwardCommand is not 11 as expected");
+    static_assert(static_cast<size_t>(PlatformMediaSession::NextTrackCommand) == 12, "PlatformMediaSession::NextTrackCommand is not 12 as expected");
+    static_assert(static_cast<size_t>(PlatformMediaSession::PreviousTrackCommand) == 13, "PlatformMediaSession::PreviousTrackCommand is not 13 as expected");
     ASSERT(static_cast<size_t>(command) < WTF_ARRAY_LENGTH(values));
     return values[static_cast<size_t>(command)];
 }

Modified: trunk/Source/WebCore/platform/audio/PlatformMediaSession.h (272444 => 272445)


--- trunk/Source/WebCore/platform/audio/PlatformMediaSession.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/audio/PlatformMediaSession.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -130,9 +130,13 @@
         BeginSeekingForwardCommand,
         EndSeekingForwardCommand,
         SeekToPlaybackPositionCommand,
+        SkipForwardCommand,
+        SkipBackwardCommand,
+        NextTrackCommand,
+        PreviousTrackCommand,
     };
     bool canReceiveRemoteControlCommands() const;
-    void didReceiveRemoteControlCommand(RemoteControlCommandType, const RemoteCommandArgument* argument = nullptr);
+    virtual void didReceiveRemoteControlCommand(RemoteControlCommandType, const RemoteCommandArgument* = nullptr);
     bool supportsSeeking() const;
 
     enum DisplayType : uint8_t {

Modified: trunk/Source/WebCore/platform/audio/PlatformMediaSessionManager.h (272444 => 272445)


--- trunk/Source/WebCore/platform/audio/PlatformMediaSessionManager.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/audio/PlatformMediaSessionManager.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -152,6 +152,9 @@
     bool isInterrupted() const { return m_interrupted; }
     bool hasNoSession() const;
 
+    virtual void addSupportedCommand(PlatformMediaSession::RemoteControlCommandType) { };
+    virtual void removeSupportedCommand(PlatformMediaSession::RemoteControlCommandType) { };
+
 protected:
     friend class PlatformMediaSession;
     PlatformMediaSessionManager();

Modified: trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.h (272444 => 272445)


--- trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2020-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -84,6 +84,9 @@
 
     GenericTaskQueue<Timer>& taskQueue() { return m_taskQueue; }
 
+    void addSupportedCommand(PlatformMediaSession::RemoteControlCommandType) final;
+    void removeSupportedCommand(PlatformMediaSession::RemoteControlCommandType) final;
+
 private:
 #if !RELEASE_LOG_DISABLED
     const char* logClassName() const override { return "MediaSessionManagerCocoa"; }

Modified: trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm (272444 => 272445)


--- trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -153,6 +153,9 @@
 void MediaSessionManagerCocoa::scheduleSessionStatusUpdate()
 {
     m_taskQueue.enqueueTask([this] () mutable {
+        if (m_remoteCommandListener)
+            m_remoteCommandListener->updateSupportedCommands();
+
         updateNowPlayingInfo();
 
         forEachSession([] (auto& session) {
@@ -232,10 +235,23 @@
 
 void MediaSessionManagerCocoa::sessionCanProduceAudioChanged()
 {
+    ALWAYS_LOG(LOGIDENTIFIER);
     PlatformMediaSessionManager::sessionCanProduceAudioChanged();
     scheduleSessionStatusUpdate();
 }
 
+void MediaSessionManagerCocoa::addSupportedCommand(PlatformMediaSession::RemoteControlCommandType command)
+{
+    if (m_remoteCommandListener)
+        m_remoteCommandListener->addSupportedCommand(command);
+}
+
+void MediaSessionManagerCocoa::removeSupportedCommand(PlatformMediaSession::RemoteControlCommandType command)
+{
+    if (m_remoteCommandListener)
+        m_remoteCommandListener->removeSupportedCommand(command);
+}
+
 void MediaSessionManagerCocoa::clearNowPlayingInfo()
 {
     if (canLoad_MediaRemote_MRMediaRemoteSetNowPlayingVisibility())
@@ -260,6 +276,12 @@
 
     auto info = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
 
+    if (!nowPlayingInfo.artist.isEmpty())
+        CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoArtist, nowPlayingInfo.artist.createCFString().get());
+
+    if (!nowPlayingInfo.album.isEmpty())
+        CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoAlbum, nowPlayingInfo.album.createCFString().get());
+
     if (!nowPlayingInfo.title.isEmpty())
         CFDictionarySetValue(info.get(), kMRMediaRemoteNowPlayingInfoTitle, nowPlayingInfo.title.createCFString().get());
 

Modified: trunk/Source/WebCore/platform/mac/MediaRemoteSoftLink.h (272444 => 272445)


--- trunk/Source/WebCore/platform/mac/MediaRemoteSoftLink.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/mac/MediaRemoteSoftLink.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -58,6 +58,14 @@
 #define MRMediaRemoteSetParentApplication softLink_MediaRemote_MRMediaRemoteSetParentApplication
 SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoTitle, CFStringRef);
 #define kMRMediaRemoteNowPlayingInfoTitle get_MediaRemote_kMRMediaRemoteNowPlayingInfoTitle()
+SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoArtist, CFStringRef);
+#define kMRMediaRemoteNowPlayingInfoArtist get_MediaRemote_kMRMediaRemoteNowPlayingInfoArtist()
+SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoAlbum, CFStringRef);
+#define kMRMediaRemoteNowPlayingInfoAlbum get_MediaRemote_kMRMediaRemoteNowPlayingInfoAlbum()
+SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoArtworkData, CFStringRef);
+#define kMRMediaRemoteNowPlayingInfoArtworkData get_MediaRemote_kMRMediaRemoteNowPlayingInfoArtworkData()
+SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoArtworkMIMEType, CFStringRef);
+#define kMRMediaRemoteNowPlayingInfoArtworkMIMEType get_MediaRemote_kMRMediaRemoteNowPlayingInfoArtworkMIMEType()
 SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoDuration, CFStringRef);
 #define kMRMediaRemoteNowPlayingInfoDuration get_MediaRemote_kMRMediaRemoteNowPlayingInfoDuration()
 SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoElapsedTime, CFStringRef);
@@ -68,6 +76,8 @@
 #define kMRMediaRemoteOptionPlaybackPosition get_MediaRemote_kMRMediaRemoteOptionPlaybackPosition()
 SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoUniqueIdentifier, CFStringRef);
 #define kMRMediaRemoteNowPlayingInfoUniqueIdentifier get_MediaRemote_kMRMediaRemoteNowPlayingInfoUniqueIdentifier()
+SOFT_LINK_CONSTANT_FOR_HEADER(WebCore, MediaRemote, kMRMediaRemoteOptionSkipInterval, CFStringRef);
+#define kMRMediaRemoteOptionSkipInterval get_MediaRemote_kMRMediaRemoteOptionSkipInterval()
 
 #if PLATFORM(IOS_FAMILY)
 SOFT_LINK_FUNCTION_FOR_HEADER(WebCore, MediaRemote, MRMediaRemoteCopyPickableRoutes, CFArrayRef, (), ())

Modified: trunk/Source/WebCore/platform/mac/MediaRemoteSoftLink.mm (272444 => 272445)


--- trunk/Source/WebCore/platform/mac/MediaRemoteSoftLink.mm	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/mac/MediaRemoteSoftLink.mm	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -44,11 +44,16 @@
 SOFT_LINK_FUNCTION_FOR_SOURCE(WebCore, MediaRemote, MRMediaRemoteSetNowPlayingApplicationPlaybackStateForOrigin, void, (MROriginRef origin, MRPlaybackState playbackState, dispatch_queue_t replyQ, void(^completion)(MRMediaRemoteError)), (origin, playbackState, replyQ, completion))
 SOFT_LINK_FUNCTION_MAY_FAIL_FOR_SOURCE(WebCore, MediaRemote, MRMediaRemoteSetParentApplication, void, (MROriginRef origin, CFStringRef parentAppDisplayID), (origin, parentAppDisplayID))
 SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoTitle, CFStringRef);
+SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoArtist, CFStringRef);
+SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoAlbum, CFStringRef);
+SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoArtworkData, CFStringRef);
+SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoArtworkMIMEType, CFStringRef);
 SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoDuration, CFStringRef);
 SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoElapsedTime, CFStringRef);
 SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoPlaybackRate, CFStringRef);
 SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteOptionPlaybackPosition, CFStringRef);
 SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteNowPlayingInfoUniqueIdentifier, CFStringRef);
+SOFT_LINK_CONSTANT_FOR_SOURCE(WebCore, MediaRemote, kMRMediaRemoteOptionSkipInterval, CFStringRef);
 
 #if PLATFORM(IOS_FAMILY)
 SOFT_LINK_FUNCTION_FOR_SOURCE(WebCore, MediaRemote, MRMediaRemoteCopyPickableRoutes, CFArrayRef, (), ());

Modified: trunk/Source/WebCore/platform/mac/RemoteCommandListenerMac.h (272444 => 272445)


--- trunk/Source/WebCore/platform/mac/RemoteCommandListenerMac.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/mac/RemoteCommandListenerMac.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -39,9 +39,13 @@
     virtual ~RemoteCommandListenerMac();
 
 protected:
-    void updateSupportedCommands() override;
+    void updateSupportedCommands() final;
 
     void* m_commandHandler { nullptr };
+
+    const RemoteCommandsSet& defaultCommands();
+    RemoteCommandsSet m_currentCommands;
+    bool m_supportsSeeking { false };
 };
     
 }

Modified: trunk/Source/WebCore/platform/mac/RemoteCommandListenerMac.mm (272444 => 272445)


--- trunk/Source/WebCore/platform/mac/RemoteCommandListenerMac.mm	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebCore/platform/mac/RemoteCommandListenerMac.mm	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -35,42 +35,95 @@
 
 namespace WebCore {
 
+static Optional<MRMediaRemoteCommand> mediaRemoteCommandForPlatformCommand(PlatformMediaSession::RemoteControlCommandType command)
+{
+    static const auto commandMap = makeNeverDestroyed([] {
+        using CommandToActionMap = HashMap<PlatformMediaSession::RemoteControlCommandType, MRMediaRemoteCommand, WTF::IntHash<PlatformMediaSession::RemoteControlCommandType>, WTF::StrongEnumHashTraits<PlatformMediaSession::RemoteControlCommandType>>;
+
+        return CommandToActionMap {
+            { PlatformMediaSession::PlayCommand, MRMediaRemoteCommandPlay },
+            { PlatformMediaSession::PauseCommand, MRMediaRemoteCommandPause },
+            { PlatformMediaSession::StopCommand, MRMediaRemoteCommandStop },
+            { PlatformMediaSession::TogglePlayPauseCommand, MRMediaRemoteCommandTogglePlayPause },
+            { PlatformMediaSession::BeginSeekingBackwardCommand, MRMediaRemoteCommandBeginRewind },
+            { PlatformMediaSession::EndSeekingBackwardCommand, MRMediaRemoteCommandEndRewind },
+            { PlatformMediaSession::BeginSeekingForwardCommand, MRMediaRemoteCommandBeginFastForward },
+            { PlatformMediaSession::EndSeekingForwardCommand, MRMediaRemoteCommandEndFastForward },
+            { PlatformMediaSession::SeekToPlaybackPositionCommand, MRMediaRemoteCommandSeekToPlaybackPosition },
+            { PlatformMediaSession::SkipForwardCommand, MRMediaRemoteCommandSkipForward },
+            { PlatformMediaSession::SkipBackwardCommand, MRMediaRemoteCommandSkipBackward },
+            { PlatformMediaSession::NextTrackCommand, MRMediaRemoteCommandNextTrack },
+            { PlatformMediaSession::PreviousTrackCommand, MRMediaRemoteCommandPreviousTrack },
+        };
+    }());
+
+    auto it = commandMap.get().find(command);
+    if (it != commandMap.get().end())
+        return { it->value };
+
+    return { };
+}
+
 std::unique_ptr<RemoteCommandListener> RemoteCommandListener::create(RemoteCommandListenerClient& client)
 {
     return makeUnique<RemoteCommandListenerMac>(client);
 }
 
+const RemoteCommandListener::RemoteCommandsSet& RemoteCommandListenerMac::defaultCommands()
+{
+    static NeverDestroyed<RemoteCommandsSet> commands(std::initializer_list<PlatformMediaSession::RemoteControlCommandType> {
+        PlatformMediaSession::PlayCommand,
+        PlatformMediaSession::PauseCommand,
+        PlatformMediaSession::TogglePlayPauseCommand,
+        PlatformMediaSession::BeginSeekingForwardCommand,
+        PlatformMediaSession::EndSeekingForwardCommand,
+        PlatformMediaSession::BeginSeekingBackwardCommand,
+        PlatformMediaSession::EndSeekingBackwardCommand,
+        PlatformMediaSession::SeekToPlaybackPositionCommand,
+        PlatformMediaSession::SkipForwardCommand,
+        PlatformMediaSession::SkipBackwardCommand,
+    });
+
+    return commands;
+}
+
+static bool isSeekCommand(PlatformMediaSession::RemoteControlCommandType command)
+{
+    return command == PlatformMediaSession::SeekToPlaybackPositionCommand
+        || command == PlatformMediaSession::SkipForwardCommand
+        || command == PlatformMediaSession::SkipBackwardCommand
+        || command == PlatformMediaSession::BeginSeekingForwardCommand
+        || command == PlatformMediaSession::BeginSeekingBackwardCommand;
+}
+
 void RemoteCommandListenerMac::updateSupportedCommands()
 {
     if (!isMediaRemoteFrameworkAvailable())
         return;
 
-    static const MRMediaRemoteCommand supportedCommands[] = {
-        MRMediaRemoteCommandPlay,
-        MRMediaRemoteCommandPause,
-        MRMediaRemoteCommandTogglePlayPause,
-        MRMediaRemoteCommandBeginFastForward,
-        MRMediaRemoteCommandEndFastForward,
-        MRMediaRemoteCommandBeginRewind,
-        MRMediaRemoteCommandEndRewind,
-        MRMediaRemoteCommandSeekToPlaybackPosition,
-    };
+    auto& supportedCommands = !m_registeredCommands.isEmpty() ? m_registeredCommands : defaultCommands();
+    if (m_supportsSeeking == client().supportsSeeking() && m_currentCommands == supportedCommands)
+        return;
 
-    auto commandInfoArray = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, sizeof(supportedCommands) / sizeof(MRMediaRemoteCommand), &kCFTypeArrayCallBacks));
+    auto commandInfoArray = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, supportedCommands.size(), &kCFTypeArrayCallBacks));
+    for (auto platformCommand : supportedCommands) {
+        if (isSeekCommand(platformCommand) && !client().supportsSeeking())
+            continue;
 
-    for (auto command : supportedCommands) {
+        auto command = mediaRemoteCommandForPlatformCommand(platformCommand);
+        ASSERT(command);
+        if (!command)
+            continue;
+
         auto commandInfo = adoptCF(MRMediaRemoteCommandInfoCreate(kCFAllocatorDefault));
-        MRMediaRemoteCommandInfoSetCommand(commandInfo.get(), command);
+        MRMediaRemoteCommandInfoSetCommand(commandInfo.get(), command.value());
         MRMediaRemoteCommandInfoSetEnabled(commandInfo.get(), true);
         CFArrayAppendValue(commandInfoArray.get(), commandInfo.get());
     }
 
-    auto seekCommandInfo = adoptCF(MRMediaRemoteCommandInfoCreate(kCFAllocatorDefault));
-    MRMediaRemoteCommandInfoSetCommand(seekCommandInfo.get(), MRMediaRemoteCommandSeekToPlaybackPosition);
-    MRMediaRemoteCommandInfoSetEnabled(seekCommandInfo.get(), client().supportsSeeking());
-    CFArrayAppendValue(commandInfoArray.get(), seekCommandInfo.get());
-
     MRMediaRemoteSetSupportedCommands(commandInfoArray.get(), MRMediaRemoteGetLocalOrigin(), nullptr, nullptr);
+    m_currentCommands = supportedCommands;
+    m_supportsSeeking = client().supportsSeeking();
 }
 
 RemoteCommandListenerMac::RemoteCommandListenerMac(RemoteCommandListenerClient& client)
@@ -79,7 +132,7 @@
     if (!isMediaRemoteFrameworkAvailable())
         return;
 
-    updateSupportedCommands();
+    scheduleSupportedCommandsUpdate();
 
     auto weakThis = makeWeakPtr(*this);
     m_commandHandler = MRMediaRemoteAddAsyncCommandHandlerBlock(^(MRMediaRemoteCommand command, CFDictionaryRef options, void(^completion)(CFArrayRef)) {
@@ -88,6 +141,7 @@
 
         PlatformMediaSession::RemoteControlCommandType platformCommand { PlatformMediaSession::NoCommand };
         PlatformMediaSession::RemoteCommandArgument argument { 0 };
+        PlatformMediaSession::RemoteCommandArgument* argumentPtr = nullptr;
         MRMediaRemoteCommandHandlerStatus status = MRMediaRemoteCommandHandlerStatusSuccess;
 
         switch (command) {
@@ -128,9 +182,30 @@
             }
 
             CFNumberGetValue(positionRef, kCFNumberDoubleType, &argument.asDouble);
+            argumentPtr = &argument;
             platformCommand = PlatformMediaSession::SeekToPlaybackPositionCommand;
             break;
         }
+        case MRMediaRemoteCommandSkipForward:
+        case MRMediaRemoteCommandSkipBackward:
+            if (!client.supportsSeeking()) {
+                status = MRMediaRemoteCommandHandlerStatusCommandFailed;
+                break;
+            }
+
+            if (auto positionRef = static_cast<CFNumberRef>(CFDictionaryGetValue(options, kMRMediaRemoteOptionSkipInterval))) {
+                CFNumberGetValue(positionRef, kCFNumberDoubleType, &argument.asDouble);
+                argumentPtr = &argument;
+            }
+
+            platformCommand = (command == MRMediaRemoteCommandSkipForward) ? PlatformMediaSession::SkipForwardCommand : PlatformMediaSession::SkipBackwardCommand;
+            break;
+        case MRMediaRemoteCommandNextTrack:
+            platformCommand = PlatformMediaSession::NextTrackCommand;
+            break;
+        case MRMediaRemoteCommandPreviousTrack:
+            platformCommand = PlatformMediaSession::PreviousTrackCommand;
+            break;
         default:
             LOG(Media, "RemoteCommandListenerMac::RemoteCommandListenerMac - command %u not supported!", command);
             status = MRMediaRemoteCommandHandlerStatusCommandFailed;
@@ -137,7 +212,7 @@
         };
 
         if (weakThis && status != MRMediaRemoteCommandHandlerStatusCommandFailed)
-            weakThis->m_client.didReceiveRemoteControlCommand(platformCommand, &argument);
+            weakThis->m_client.didReceiveRemoteControlCommand(platformCommand, argumentPtr);
 
         completion((__bridge CFArrayRef)@[@(status)]);
     });

Modified: trunk/Source/WebKit/ChangeLog (272444 => 272445)


--- trunk/Source/WebKit/ChangeLog	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebKit/ChangeLog	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,3 +1,18 @@
+2021-02-05  Eric Carlson  <[email protected]>
+
+        [Mac] Connect MediaSession with MediaRemote and NowPlaying
+        https://bugs.webkit.org/show_bug.cgi?id=221431
+        <rdar://problem/74000363>
+
+        Reviewed by Jer Noble.
+
+        Add a private preference so the new MediaSession API test can enable the feature.
+
+        * UIProcess/API/Cocoa/WKPreferences.mm:
+        (-[WKPreferences _mediaSessionEnabled]):
+        (-[WKPreferences _setMediaSessionEnabled:]):
+        * UIProcess/API/Cocoa/WKPreferencesPrivate.h:
+
 2021-02-05  Youenn Fablet  <[email protected]>
 
         Enable audio capture for speech recognition in GPUProcess

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm (272444 => 272445)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1479,6 +1479,16 @@
     _preferences->setPitchCorrectionAlgorithm(pitchCorrectionAlgorithm);
 }
 
+- (BOOL)_mediaSessionEnabled
+{
+    return _preferences->mediaSessionEnabled();
+}
+
+- (void)_setMediaSessionEnabled:(BOOL)mediaSessionEnabled
+{
+    _preferences->setMediaSessionEnabled(mediaSessionEnabled);
+}
+
 @end
 
 

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h (272444 => 272445)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h	2021-02-05 23:18:55 UTC (rev 272445)
@@ -170,6 +170,7 @@
 @property (nonatomic, setter=_setSpeechRecognitionEnabled:) BOOL _speechRecognitionEnabled WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setPrivateClickMeasurementEnabled:) BOOL _privateClickMeasurementEnabled WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setPitchCorrectionAlgorithm:) _WKPitchCorrectionAlgorithm _pitchCorrectionAlgorithm WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+@property (nonatomic, setter=_setMediaSessionEnabled:) BOOL _mediaSessionEnabled WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 #if !TARGET_OS_IPHONE
 @property (nonatomic, setter=_setWebGLEnabled:) BOOL _webGLEnabled WK_API_AVAILABLE(macos(10.13.4));

Modified: trunk/Tools/ChangeLog (272444 => 272445)


--- trunk/Tools/ChangeLog	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Tools/ChangeLog	2021-02-05 23:18:55 UTC (rev 272445)
@@ -1,3 +1,33 @@
+2021-02-05  Eric Carlson  <[email protected]>
+
+        [Mac] Connect MediaSession with MediaRemote and NowPlaying
+        https://bugs.webkit.org/show_bug.cgi?id=221431
+        <rdar://problem/74000363>
+
+        Reviewed by Jer Noble.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm: Added.
+        (TestWebKitAPI::MediaSessionTest::webView):
+        (TestWebKitAPI::MediaSessionTest::webViewPid):
+        (TestWebKitAPI::MediaSessionTest::getNowPlayingClient):
+        (TestWebKitAPI::MediaSessionTest::getNowPlayingClientPid):
+        (TestWebKitAPI::MediaSessionTest::loadPageAndBecomeNowPlaying):
+        (TestWebKitAPI::MediaSessionTest::runScriptWithUserGesture):
+        (TestWebKitAPI::MediaSessionTest::play):
+        (TestWebKitAPI::MediaSessionTest::pause):
+        (TestWebKitAPI::MediaSessionTest::sendMediaRemoteCommand):
+        (TestWebKitAPI::MediaSessionTest::sendMediaRemoteSeekCommand):
+        (TestWebKitAPI::MediaSessionTest::listenForEventMessages):
+        (TestWebKitAPI::MediaSessionTest::eventListenerWasCalled):
+        (TestWebKitAPI::MediaSessionTest::waitForEventListenerToBeCalled):
+        (TestWebKitAPI::MediaSessionTest::listenForSessionHandlerMessages):
+        (TestWebKitAPI::MediaSessionTest::sessionHandlerWasCalled):
+        (TestWebKitAPI::MediaSessionTest::waitForSessionHandlerToBeCalled):
+        (TestWebKitAPI::MediaSessionTest::getSupportedCommands):
+        (TestWebKitAPI::TEST_F):
+        * TestWebKitAPI/Tests/WebKitCocoa/media-remote.html: Added.
+
 2021-02-05  Sam Weinig  <[email protected]>
 
         Generalize color conversion code to reduce number of overloads required

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (272444 => 272445)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2021-02-05 23:09:46 UTC (rev 272444)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2021-02-05 23:18:55 UTC (rev 272445)
@@ -56,6 +56,8 @@
 		074994521EA5034B000DA44E /* getUserMedia2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 41BAF4E225AC9DB800D82F32 /* getUserMedia2.html */; };
 		076E507F1F4513D6006E9F5A /* Logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 076E507E1F45031E006E9F5A /* Logging.cpp */; };
 		077A5AF3230638A600A7105C /* AccessibilityTestPlugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0746645822FF630500E3451A /* AccessibilityTestPlugin.mm */; };
+		0794740D25CA0BDE00C597EB /* MediaSession.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0794740C25CA0BDE00C597EB /* MediaSession.mm */; };
+		0794742D25CB33FD00C597EB /* media-remote.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 0794742C25CB33B000C597EB /* media-remote.html */; };
 		0799C3491EBA2D7B003B7532 /* UserMediaDisabled.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07EDEFAC1EB9400C00D43292 /* UserMediaDisabled.mm */; };
 		0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */; };
 		07C046CA1E4262A8007201E7 /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07C046C91E42573E007201E7 /* CARingBuffer.cpp */; };
@@ -1559,6 +1561,7 @@
 				AD57AC221DA7466E00FF1BDE /* many-iframes.html in Copy Resources */,
 				7772ECE122FE06C60009A799 /* many-same-origin-iframes.html in Copy Resources */,
 				CD8394DF232AF7C000149495 /* media-loading.html in Copy Resources */,
+				0794742D25CB33FD00C597EB /* media-remote.html in Copy Resources */,
 				CDA3159A1ED548F1009F60D3 /* MediaPlaybackSleepAssertion.html in Copy Resources */,
 				CDC9442F1EF205D60059C3C4 /* mediastreamtrack-detached.html in Copy Resources */,
 				E1220DCA155B28AA0013E2FC /* MemoryCacheDisableWithinResourceLoadDelegate.html in Copy Resources */,
@@ -1722,6 +1725,8 @@
 		07492B3A1DF8AE2D00633DE1 /* EnumerateMediaDevices.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EnumerateMediaDevices.cpp; sourceTree = "<group>"; };
 		0766DD1F1A5AD5200023E3BB /* PendingAPIRequestURL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PendingAPIRequestURL.cpp; sourceTree = "<group>"; };
 		076E507E1F45031E006E9F5A /* Logging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Logging.cpp; sourceTree = "<group>"; };
+		0794740C25CA0BDE00C597EB /* MediaSession.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaSession.mm; sourceTree = "<group>"; };
+		0794742C25CB33B000C597EB /* media-remote.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "media-remote.html"; sourceTree = "<group>"; };
 		0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = disableGetUserMedia.html; sourceTree = "<group>"; };
 		07C046C91E42573E007201E7 /* CARingBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CARingBuffer.cpp; sourceTree = "<group>"; };
 		07CC7DFD2266330800E39181 /* MediaBufferingPolicy.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaBufferingPolicy.mm; sourceTree = "<group>"; };
@@ -3359,6 +3364,7 @@
 				95B6B3B6251EBF2F00FC4382 /* MediaDocument.mm */,
 				CD0370E224A44B7A00BA3CAE /* MediaLoading.mm */,
 				07EF76D42540FC060053ED53 /* MediaMutedState.mm */,
+				0794740C25CA0BDE00C597EB /* MediaSession.mm */,
 				51BE9E652376089500B4E117 /* MediaType.mm */,
 				5165FE03201EE617009F7EC3 /* MessagePortProviders.mm */,
 				51CD1C6A1B38CE3600142CA5 /* ModalAlerts.mm */,
@@ -3922,6 +3928,7 @@
 				46C519E31D35629600DAA51A /* LocalStorageNullEntries.localstorage */,
 				46C519E41D35629600DAA51A /* LocalStorageNullEntries.localstorage-shm */,
 				7A6A2C711DCCFB0200C0D085 /* LocalStorageQuirkEnabled.html */,
+				0794742C25CB33B000C597EB /* media-remote.html */,
 				9B59F12920340854009E63D5 /* mso-list-compat-mode.html */,
 				9BCD4119206D5ED7001D71BE /* mso-list-on-h4.html */,
 				9BF356CC202D44F200F71160 /* mso-list.html */,
@@ -5383,6 +5390,7 @@
 				CD0370E324A44D9600BA3CAE /* MediaLoading.mm in Sources */,
 				07EF76D52540FC060053ED53 /* MediaMutedState.mm in Sources */,
 				CDA315981ED53651009F60D3 /* MediaPlaybackSleepAssertion.mm in Sources */,
+				0794740D25CA0BDE00C597EB /* MediaSession.mm in Sources */,
 				CDC9442E1EF1FC080059C3C4 /* MediaStreamTrackDetached.mm in Sources */,
 				51BE9E662376089F00B4E117 /* MediaType.mm in Sources */,
 				7CCE7EC51A411A7E00447C4C /* MemoryCacheDisableWithinResourceLoadDelegate.mm in Sources */,
@@ -5421,6 +5429,7 @@
 				C104BC1F2547237100C078C9 /* OverrideAppleLanguagesPreference.mm in Sources */,
 				CEBCA12F1E3A660100C73293 /* OverrideContentSecurityPolicy.mm in Sources */,
 				2DA2586F225C67DC00B45C1C /* OverrideViewportArguments.mm in Sources */,
+				953ABB3525C0D682004C8B73 /* PageExtendedBackgroundColor.mm in Sources */,
 				7CCB4DA91C83AE7300CC6918 /* PageGroup.cpp in Sources */,
 				7CCE7F071A411AE600447C4C /* PageLoadBasic.cpp in Sources */,
 				7CCE7F081A411AE600447C4C /* PageLoadDidChangeLocationWithinPageForFrame.cpp in Sources */,
@@ -5464,7 +5473,6 @@
 				518C1153205B0504001FF4AE /* ProcessSwapOnNavigation.mm in Sources */,
 				FEC2A85624CEB65F00ADBC35 /* PropertySlot.cpp in Sources */,
 				7C83E0C11D0A652F00FEBCF3 /* ProvisionalURLNotChange.mm in Sources */,
-				953ABB3525C0D682004C8B73 /* PageExtendedBackgroundColor.mm in Sources */,
 				5CFACF65226FD2DC0056C7D0 /* Proxy.mm in Sources */,
 				041A1E34216FFDBC00789E0A /* PublicSuffix.cpp in Sources */,
 				7C83E0C21D0A653500FEBCF3 /* QuickLook.mm in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm (0 => 272445)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/MediaSession.mm	2021-02-05 23:18:55 UTC (rev 272445)
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#import "config.h"
+
+#if PLATFORM(MAC) && ENABLE(MEDIA_SESSION)
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/WebViewPrivate.h>
+#import <pal/spi/mac/MediaRemoteSPI.h>
+#import <wtf/Function.h>
+#import <wtf/HashSet.h>
+#import <wtf/NeverDestroyed.h>
+#import <wtf/SoftLinking.h>
+#import <wtf/text/StringHash.h>
+#import <wtf/text/WTFString.h>
+
+SOFT_LINK_PRIVATE_FRAMEWORK(MediaRemote)
+
+SOFT_LINK(MediaRemote, MRMediaRemoteSendCommandToApp, Boolean, (MRMediaRemoteCommand command, CFDictionaryRef options, MROriginRef origin, CFStringRef appDisplayID, MRSendCommandAppOptions appOptions, dispatch_queue_t replyQ, void(^completion)(MRSendCommandError err, CFArrayRef handlerReturnStatuses)), (command, options, origin, appDisplayID, appOptions, replyQ, completion));
+#define MRMediaRemoteSendCommandToApp softLinkMRMediaRemoteSendCommandToApp
+
+SOFT_LINK(MediaRemote, MRMediaRemoteGetLocalOrigin, MROriginRef, (), ());
+#define MRMediaRemoteGetLocalOrigin softLinkMRMediaRemoteGetLocalOrigin
+
+SOFT_LINK(MediaRemote, MRMediaRemoteGetSupportedCommandsForOrigin, void, (MROriginRef origin, dispatch_queue_t queue, void(^completion)(CFArrayRef commands)), (origin, queue, completion));
+#define MRMediaRemoteGetSupportedCommandsForOrigin softLinkMRMediaRemoteGetSupportedCommandsForOrigin
+
+SOFT_LINK(MediaRemote, MRMediaRemoteGetNowPlayingClient, void, (dispatch_queue_t queue, void(^completion)(MRNowPlayingClientRef, CFErrorRef)), (queue, completion))
+#define MRMediaRemoteGetNowPlayingClient softLinkMRMediaRemoteGetNowPlayingClient
+
+SOFT_LINK(MediaRemote, MRNowPlayingClientGetProcessIdentifier, pid_t, (MRNowPlayingClientRef client), (client))
+#define MRNowPlayingClientGetProcessIdentifier softLinkMRNowPlayingClientGetProcessIdentifier
+
+SOFT_LINK_CONSTANT(MediaRemote, kMRMediaRemoteOptionSkipInterval, CFStringRef)
+#define kMRMediaRemoteOptionSkipInterval getkMRMediaRemoteOptionSkipInterval()
+
+SOFT_LINK_CONSTANT(MediaRemote, kMRMediaRemoteOptionPlaybackPosition, CFStringRef)
+#define kMRMediaRemoteOptionPlaybackPosition getkMRMediaRemoteOptionPlaybackPosition()
+
+#if !USE(APPLE_INTERNAL_SDK)
+@interface MRCommandInfo : NSObject
+@property (nonatomic, readonly) MRMediaRemoteCommand command;
+@property (nonatomic, readonly, getter=isEnabled) BOOL enabled;
+@property (nonatomic, readonly, nullable, copy) NSDictionary *options;
+@end
+#endif
+
+namespace TestWebKitAPI {
+
+class MediaSessionTest : public testing::Test {
+public:
+    void SetUp() final
+    {
+        _configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+        [_configuration setMediaTypesRequiringUserActionForPlayback:WKAudiovisualMediaTypeAudio];
+
+        _webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:_configuration.get() addToWindow:YES]);
+
+        WKPreferences *preferences = [_webView configuration].preferences;
+        preferences._mediaSessionEnabled = YES;
+
+        _messageHandlers = adoptNS([[NSMutableArray alloc] init]);
+    }
+
+    void TearDown() override
+    {
+        [_webView clearMessageHandlers:_messageHandlers.get()];
+    }
+
+    TestWKWebView* webView() { return _webView.get(); }
+
+    pid_t webViewPid() { return [_webView _webProcessIdentifier]; }
+
+    RetainPtr<MRNowPlayingClientRef> getNowPlayingClient()
+    {
+        bool gotNowPlaying = false;
+        RetainPtr<MRNowPlayingClientRef> nowPlayingClient;
+        MRMediaRemoteGetNowPlayingClient(dispatch_get_main_queue(), [&] (MRNowPlayingClientRef player, CFErrorRef error) {
+            if (!error && player)
+                nowPlayingClient = player;
+            gotNowPlaying = true;
+        });
+        TestWebKitAPI::Util::run(&gotNowPlaying);
+        return nowPlayingClient;
+    }
+
+    pid_t getNowPlayingClientPid()
+    {
+        return MRNowPlayingClientGetProcessIdentifier(getNowPlayingClient().get());
+    }
+
+    void loadPageAndBecomeNowPlaying(const String& pageName)
+    {
+        [_webView synchronouslyLoadTestPageNamed:pageName];
+
+        bool canplaythrough = false;
+        [webView() performAfterReceivingMessage:@"canplaythrough event" action:[&] {
+            canplaythrough = true;
+        }];
+        runScriptWithUserGesture("load()");
+        Util::run(&canplaythrough);
+
+        play();
+        pause();
+        ASSERT_EQ(webViewPid(), getNowPlayingClientPid());
+    }
+
+    void runScriptWithUserGesture(const String& script)
+    {
+        bool complete = false;
+        [_webView evaluateJavaScript:script completionHandler:[&] (id, NSError *) { complete = true; }];
+        TestWebKitAPI::Util::run(&complete);
+    }
+
+    void play()
+    {
+        bool playing = false;
+        [_webView performAfterReceivingMessage:@"play event" action:[&] { playing = true; }];
+        runScriptWithUserGesture("audio.play()");
+        Util::run(&playing);
+    }
+
+    void pause()
+    {
+        bool paused = false;
+        [_webView performAfterReceivingMessage:@"pause event" action:[&] { paused = true; }];
+        runScriptWithUserGesture("audio.pause()");
+        Util::run(&paused);
+    }
+
+    bool sendMediaRemoteCommand(MRMediaRemoteCommand command, CFDictionaryRef options = nullptr)
+    {
+        bool completed = false;
+        bool success;
+
+        MRMediaRemoteSendCommandToApp(command, options, NULL, NULL, static_cast<MRSendCommandAppOptions>(0), NULL, [&] (MRSendCommandError error, CFArrayRef) {
+            success = !error;
+            completed = true;
+        });
+        TestWebKitAPI::Util::run(&completed);
+
+        return success;
+    }
+
+    bool sendMediaRemoteSeekCommand(MRMediaRemoteCommand command, double interval)
+    {
+        CFStringRef seekInterval = (command == MRMediaRemoteCommandSeekToPlaybackPosition) ? kMRMediaRemoteOptionPlaybackPosition : kMRMediaRemoteOptionSkipInterval;
+        NSDictionary *options = @{(__bridge NSString *)seekInterval : @(interval)};
+        return sendMediaRemoteCommand(command, (__bridge CFDictionaryRef)options);
+    }
+
+    void listenForEventMessages(std::initializer_list<const char*> events)
+    {
+        for (auto* event : events) {
+            auto eventMessage = makeString(event, " event");
+            [_messageHandlers addObject:eventMessage];
+            [webView() performAfterReceivingMessage:eventMessage action:[this, eventMessage = WTFMove(eventMessage)] {
+                _eventListenersCalled.add(eventMessage);
+            }];
+        }
+    }
+
+    bool eventListenerWasCalled(const String& event)
+    {
+        return _eventListenersCalled.contains(makeString(event, " event"));
+    }
+
+    void clearEventListenerState()
+    {
+        _eventListenersCalled.clear();
+    }
+
+    void waitForEventListenerToBeCalled(const String& event)
+    {
+        int tries = 0;
+        do {
+            if (eventListenerWasCalled(event))
+                return;
+            Util::sleep(0.1);
+        } while (++tries <= 50);
+
+        return;
+    }
+
+    void listenForSessionHandlerMessages(std::initializer_list<const char*> handlers)
+    {
+        for (auto* handler : handlers) {
+            auto handlerMessage = makeString(handler, " handler");
+            [_messageHandlers addObject:handlerMessage];
+            [webView() performAfterReceivingMessage:handlerMessage action:[this, handlerMessage = WTFMove(handlerMessage)] {
+                _mediaSessionHandlersCalled.add(handlerMessage);
+            }];
+        }
+    }
+
+    bool sessionHandlerWasCalled(const String& handler)
+    {
+        return _mediaSessionHandlersCalled.contains(makeString(handler, " handler"));
+    }
+
+    void waitForSessionHandlerToBeCalled(const String& handler)
+    {
+        int tries = 0;
+        do {
+            if (sessionHandlerWasCalled(handler))
+                return;
+            Util::sleep(0.1);
+        } while (++tries <= 50);
+
+        return;
+    }
+
+    RetainPtr<NSArray> getSupportedCommands()
+    {
+        bool completed = false;
+        RetainPtr<NSArray> result;
+
+        MRMediaRemoteGetSupportedCommandsForOrigin(MRMediaRemoteGetLocalOrigin(), dispatch_get_main_queue(), [&] (CFArrayRef commands) {
+            result = (__bridge NSArray *)commands;
+            completed = true;
+        });
+
+        TestWebKitAPI::Util::run(&completed);
+
+        return result;
+    }
+
+private:
+    RetainPtr<WKWebViewConfiguration> _configuration;
+    RetainPtr<TestWKWebView> _webView;
+
+    HashSet<String> _mediaSessionHandlersCalled;
+    HashSet<String> _eventListenersCalled;
+    RetainPtr<NSMutableArray> _messageHandlers;
+};
+
+TEST_F(MediaSessionTest, OnlyOneHandler)
+{
+    loadPageAndBecomeNowPlaying("media-remote");
+
+    [webView() objectByEvaluatingJavaScript:@"setEmptyActionHandlers([ 'play' ])"];
+
+    listenForSessionHandlerMessages({ "play", "pause", "seekto", "seekforward", "seekbackward", "previoustrack", "nexttrack" });
+    listenForEventMessages({ "play", "pause", "seeked" });
+
+    static Vector<MRMediaRemoteCommand> registeredCommands = { MRMediaRemoteCommandPlay };
+    auto currentCommands = getSupportedCommands();
+    for (MRCommandInfo *command in currentCommands.get()) {
+        if (!command.enabled)
+            continue;
+        
+        ASSERT_TRUE(registeredCommands.contains(command.command));
+    }
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPlay));
+    waitForSessionHandlerToBeCalled("play");
+    ASSERT_TRUE(sessionHandlerWasCalled("play"));
+    ASSERT_FALSE(eventListenerWasCalled("play"));
+
+    // The media session only registered for Play, but no other commands should reach HTMLMediaElement.
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSkipForward, 1));
+    ASSERT_FALSE(sessionHandlerWasCalled("seekforward"));
+    ASSERT_FALSE(eventListenerWasCalled("seeked"));
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSkipBackward, 10));
+    ASSERT_FALSE(sessionHandlerWasCalled("seekbackward"));
+    ASSERT_FALSE(eventListenerWasCalled("seeked"));
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSeekToPlaybackPosition, 6));
+    ASSERT_FALSE(sessionHandlerWasCalled("seekto"));
+    ASSERT_FALSE(eventListenerWasCalled("seeked"));
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandNextTrack));
+    ASSERT_FALSE(sessionHandlerWasCalled("nexttrack"));
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPreviousTrack));
+    ASSERT_FALSE(sessionHandlerWasCalled("previoustrack"));
+}
+
+TEST_F(MediaSessionTest, RemoteCommands)
+{
+    loadPageAndBecomeNowPlaying("media-remote");
+
+    [webView() objectByEvaluatingJavaScript:@"setEmptyActionHandlers([ 'play', 'pause', 'seekto', 'seekforward', 'seekbackward', 'previoustrack', 'nexttrack' ])"];
+
+    listenForSessionHandlerMessages({ "play", "pause", "seekto", "seekforward", "seekbackward", "previoustrack", "nexttrack" });
+    listenForEventMessages({ "play", "pause", "seeked" });
+
+    static Vector<MRMediaRemoteCommand> registeredCommands = { MRMediaRemoteCommandPlay, MRMediaRemoteCommandPause, MRMediaRemoteCommandSeekToPlaybackPosition, MRMediaRemoteCommandSkipForward, MRMediaRemoteCommandSkipBackward, MRMediaRemoteCommandPreviousTrack, MRMediaRemoteCommandNextTrack };
+    auto currentCommands = getSupportedCommands();
+    for (MRCommandInfo *command in currentCommands.get()) {
+        if (!command.enabled)
+            continue;
+        
+        ASSERT_TRUE(registeredCommands.contains(command.command));
+    }
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPlay));
+    waitForSessionHandlerToBeCalled("play");
+    ASSERT_TRUE(sessionHandlerWasCalled("play"));
+    ASSERT_FALSE(eventListenerWasCalled("play"));
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPause));
+    waitForSessionHandlerToBeCalled("pause");
+    ASSERT_TRUE(sessionHandlerWasCalled("pause"));
+    ASSERT_FALSE(eventListenerWasCalled("pause"));
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSkipForward, 1));
+    waitForSessionHandlerToBeCalled("seekforward");
+    ASSERT_TRUE(sessionHandlerWasCalled("seekforward"));
+    ASSERT_FALSE(eventListenerWasCalled("seeked"));
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSkipBackward, 10));
+    waitForSessionHandlerToBeCalled("seekbackward");
+    ASSERT_TRUE(sessionHandlerWasCalled("seekbackward"));
+    ASSERT_FALSE(eventListenerWasCalled("seeked"));
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSeekToPlaybackPosition, 6));
+    waitForSessionHandlerToBeCalled("seekto");
+    ASSERT_TRUE(sessionHandlerWasCalled("seekto"));
+    ASSERT_FALSE(eventListenerWasCalled("seeked"));
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandNextTrack));
+    waitForSessionHandlerToBeCalled("nexttrack");
+    ASSERT_TRUE(sessionHandlerWasCalled("nexttrack"));
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPreviousTrack));
+    waitForSessionHandlerToBeCalled("previoustrack");
+    ASSERT_TRUE(sessionHandlerWasCalled("previoustrack"));
+
+    // Unregister action handlers, supported commands should go to HTMLMediaElement.
+    [webView() objectByEvaluatingJavaScript:@"clearActionHandlers()"];
+    clearEventListenerState();
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPlay));
+    waitForEventListenerToBeCalled("play");
+    ASSERT_TRUE(eventListenerWasCalled("play"));
+
+    ASSERT_TRUE(sendMediaRemoteCommand(MRMediaRemoteCommandPause));
+    waitForEventListenerToBeCalled("pause");
+    ASSERT_TRUE(eventListenerWasCalled("pause"));
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSkipForward, 1));
+    waitForEventListenerToBeCalled("seeked");
+    ASSERT_TRUE(eventListenerWasCalled("seeked"));
+    clearEventListenerState();
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSkipBackward, 10));
+    waitForEventListenerToBeCalled("seeked");
+    ASSERT_TRUE(eventListenerWasCalled("seeked"));
+    clearEventListenerState();
+
+    ASSERT_TRUE(sendMediaRemoteSeekCommand(MRMediaRemoteCommandSeekToPlaybackPosition, 6));
+    waitForEventListenerToBeCalled("seeked");
+    ASSERT_TRUE(eventListenerWasCalled("seeked"));
+}
+
+}
+
+#endif // PLATFORM(MAC) && ENABLE(MEDIA_SESSION)

Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/media-remote.html (0 => 272445)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/media-remote.html	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/media-remote.html	2021-02-05 23:18:55 UTC (rev 272445)
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    #eventslog {
+        font-size: 10px; 
+        height: calc(100% - (320px));
+        overflow-y: scroll;
+        border: 1px solid rgba(147, 128, 108, 0.25);
+    }
+</style>
+    <script>
+
+        let playlist = {
+            current: 0,
+            metadata: [
+                {
+                    url: 'video-with-audio.mp4',
+                    title: 'Bip Bop With Audio',
+                    artist: 'The Bip Bop Authors',
+                    album: 'WebKit Greatest Hits',
+                    artwork: [
+
+                    ],
+                },
+                {
+                    url: 'video-without-audio.mp4',
+                    title: 'Bip Bop Without Audio',
+                    artist: 'The Bip Bop Authors',
+                    album: 'WebKit Original Test Content',
+                    artwork: [
+
+                    ],
+                },
+            ],
+        };
+
+        function setMetadata(data)
+        {
+            navigator.mediaSession.metadata = new MediaMetadata(data);
+            postMessage('set metadata')
+        }
+
+        function setPlaylistMetadata(index)
+        {
+            setMetadata(playlist.metadata[index]);
+        
+        }
+
+        function clearActionHandlers()
+        {
+            setEmptyActionHandlers([]);
+        }
+
+        function setEmptyActionHandlers(handlers)
+        {
+            const actions = {
+                'play' : () => { postMessage('play handler'); },
+
+                'pause' : () => { postMessage('pause handler') },
+                
+                'seekto' : (details) => { postMessage('seekto handler') },
+
+                'seekforward' : (details) => { postMessage('seekforward handler') },
+
+                'seekbackward' : (details) => { postMessage('seekbackward handler') }, 
+
+                'previoustrack' : () => { postMessage('previoustrack handler') }, 
+
+                'nexttrack' : () => { postMessage('nexttrack handler') }, 
+            };
+
+            Object.keys(actions).forEach(action ="" {  navigator.mediaSession.setActionHandler(action, null); });
+
+            handlers.forEach(handler => {
+                if (!actions[handler]) {
+                    log(`asked to register handler for unknown action '${handler}'`);
+                    return;
+                }
+
+                log(`registering '${handler}' handler`);
+                navigator.mediaSession.setActionHandler(handler, actions[handler]); 
+            });
+        }
+
+        function postMessage(message)
+        {
+            log(`${message}`)
+            if (window.webkit)
+                window.webkit.messageHandlers.testHandler.postMessage(message);
+        }
+
+        function postEvent(evt) 
+        {
+            postMessage(`${evt.type} event`);
+        }
+        
+        function load()
+        {
+            let src = ""
+            if (!window.webkit)
+                src = ""
+            audio.src = ""
+            audio.load();
+        }
+        
+        function log(msg)
+        {
+            let eventLog = document.getElementById('eventslog')
+            let note = document.createElement('div');
+            note.innerHTML = msg;
+            eventLog.insertBefore(note, eventLog.firstChild);
+        }
+
+        
+        window.addEventListener("load", evt => {
+            audio = document.getElementsByTagName('audio')[0];
+            audio.addEventListener('canplaythrough', postEvent);
+            audio.addEventListener('play', postEvent);
+            audio.addEventListener('pause', postEvent);
+            audio.addEventListener('seeked', postEvent);
+        }, false);
+
+    </script>
+</head>
+<body>
+    <audio controls> </audio>
+    <br>
+    <button _onclick_="setEmptyActionHandlers(['play','pause','seekto'])">Empty Play, Pause, SeekTo</button>
+    <br>
+    <button _onclick_="setEmptyActionHandlers(['play','pause','seekforward','seekbackward'])">Empty Play, Pause, Seek Forward, Seek Backward</button>
+    <br>
+    <button _onclick_="load()">Load</button>
+    <br>
+    <button _onclick_="setPlaylistMetadata(0)">Set Metadata 1</button><button _onclick_="setPlaylistMetadata(1)">Set Metadata 2</button>
+    <br>
+    <div id='eventslog'></div>
+</body>
+</html>
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to