Title: [275787] trunk
Revision
275787
Author
[email protected]
Date
2021-04-09 17:45:37 -0700 (Fri, 09 Apr 2021)

Log Message

Media Session action should default to the MediaElement's default when no MediaSession handler are set
https://bugs.webkit.org/show_bug.cgi?id=224278
<rdar://problem/76339841>

Reviewed by Youenn Fablet .

Source/WebCore:

When a media session doesn't explicitly define an action handler, we use the media element
default action of the same type.
Media Session doesn't track a particular media element, instead it loosely defines a guessed playback state
that tracks if some element in the current document is playing and not muted or otherwise paused.
(see https://w3c.github.io/mediasession/#playback-state-model)
We therefore need to determine what is currently the most suitable media element available in this document.
Unlike the Media Controller and the Now Playing policy that will only ever select a media
if it is currently playing and not muted, for the Media Session we may have to interact with paused media elements.
For this we add a new PlaybackControlsPurpose named MediaSession defining new search criterias.
A media element will be up for selection if it's playable (according to autoplay policy). From then,
the best element will be selected accoring to its visibility, its size, if it's beeing played previously and
the last time it was interacted with.

Test: media/media-session/default-actionHandlers.html

* Modules/mediasession/MediaSession.cpp:
(WebCore::platformCommandForMediaSessionAction): New convenience method to convert one datatype into another.
(WebCore::MediaSession::callActionHandler): Use user defined handler if present or run the related action on
the most suitable media element
(WebCore::MediaSession::activeMediaElement const): Determine the currently active media element in the current
Media Session's document.
* Modules/mediasession/MediaSession.h:
* html/HTMLMediaElement.cpp:
(WebCore::mediaElementSessionInfoForSession): Add new hasEverNotifiedAboutPlaying member.
(WebCore::preferMediaControlsForCandidateSessionOverOtherCandidateSession): Amend sorting algorithm to cater
for the MediaSession criterias described above.
(WebCore::mediaSessionMayBeConfusedWithMainContent): When using MediaSession purpose, there is no possible
ambiguity, amend as such.
(WebCore::HTMLMediaElement::bestMediaElementForRemoteControls): Renamed from bestMediaElementForShowingPlaybackControlsManager
method to better match how the method is actually used.
* html/HTMLMediaElement.h:
* html/MediaElementSession.cpp:
(WebCore::MediaElementSession::canShowControlsManager const): Amend for new Media Session criterias.
(WebCore::MediaElementSession::didReceiveRemoteControlCommand): Always let MediaSession manage the actions handling.
* html/MediaElementSession.h:
* page/Page.cpp:
(WebCore::Page::playbackControlsManagerUpdateTimerFired): amend following method rename.
* platform/audio/cocoa/MediaSessionManagerCocoa.mm:
(WebCore::MediaSessionManagerCocoa::nowPlayingEligibleSession): amend following method rename.
* testing/Internals.cpp:
(WebCore::Internals::bestMediaElementForRemoteControls): Renamed from bestMediaElementForShowingPlaybackControlsManager as above.
(WebCore::Internals::sendMediaSessionAction): amend following method rename.
* testing/Internals.h: Rename method
* testing/Internals.idl: Rename method

LayoutTests:

* media/audio-background-playback-playlist-expected.txt: Renamed method
* media/audio-background-playback-playlist.html: Renamed method
* media/media-session/default-actionHandlers-expected.txt: Added.
* media/media-session/default-actionHandlers.html: Added.
* platform/mac/media/video-best-element-for-playback-controls-purpose-expected.txt: Renamed method
* platform/mac/media/video-best-element-for-playback-controls-purpose.html: Renamed method

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (275786 => 275787)


--- trunk/LayoutTests/ChangeLog	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/LayoutTests/ChangeLog	2021-04-10 00:45:37 UTC (rev 275787)
@@ -1,3 +1,18 @@
+2021-04-09  Jean-Yves Avenard  <[email protected]>
+
+        Media Session action should default to the MediaElement's default when no MediaSession handler are set
+        https://bugs.webkit.org/show_bug.cgi?id=224278
+        <rdar://problem/76339841>
+
+        Reviewed by Youenn Fablet .
+
+        * media/audio-background-playback-playlist-expected.txt: Renamed method
+        * media/audio-background-playback-playlist.html: Renamed method
+        * media/media-session/default-actionHandlers-expected.txt: Added.
+        * media/media-session/default-actionHandlers.html: Added.
+        * platform/mac/media/video-best-element-for-playback-controls-purpose-expected.txt: Renamed method
+        * platform/mac/media/video-best-element-for-playback-controls-purpose.html: Renamed method
+
 2021-04-09  Robert Jenner  <[email protected]>
 
         [ macOS ] webgl/1.0.3/conformance/glsl/constructors/glsl-construct-ivec2.html is a flakey timeout

Modified: trunk/LayoutTests/media/audio-background-playback-playlist-expected.txt (275786 => 275787)


--- trunk/LayoutTests/media/audio-background-playback-playlist-expected.txt	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/LayoutTests/media/audio-background-playback-playlist-expected.txt	2021-04-10 00:45:37 UTC (rev 275787)
@@ -4,18 +4,18 @@
 EVENT(canplaythrough)
 RUN(audio.play())
 EVENT(playing)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("NowPlaying") == '[object HTMLAudioElement]') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("NowPlaying") == '[object HTMLAudioElement]') OK
 RUN(internals.applicationDidEnterBackground(true))
 RUN(audio.currentTime = audio.duration - 0.1)
 EVENT(ended)
 RUN(audio.src = ""
 RUN(audio.load())
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("NowPlaying") == '[object HTMLAudioElement]') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("NowPlaying") == '[object HTMLAudioElement]') OK
 RUN(audio.src = "" "content/test"))
 RUN(audio.load())
 EVENT(canplaythrough)
 RUN(audio.play())
 EVENT(playing)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("NowPlaying") == '[object HTMLAudioElement]') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("NowPlaying") == '[object HTMLAudioElement]') OK
 END OF TEST
 

Modified: trunk/LayoutTests/media/audio-background-playback-playlist.html (275786 => 275787)


--- trunk/LayoutTests/media/audio-background-playback-playlist.html	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/LayoutTests/media/audio-background-playback-playlist.html	2021-04-10 00:45:37 UTC (rev 275787)
@@ -11,19 +11,19 @@
         await waitFor(audio, 'canplaythrough');
         runWithKeyDown('audio.play()');
         await waitFor(audio, 'playing');
-        testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("NowPlaying")', audio);
+        testExpected('internals.bestMediaElementForRemoteControls("NowPlaying")', audio);
         run('internals.applicationDidEnterBackground(true)');
         run('audio.currentTime = audio.duration - 0.1')
         await waitFor(audio, 'ended');
         run('audio.src = ""
         run('audio.load()');
-        testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("NowPlaying")', audio);
+        testExpected('internals.bestMediaElementForRemoteControls("NowPlaying")', audio);
         run('audio.src = "" "content/test")');
         run('audio.load()');
         await waitFor(audio, 'canplaythrough');
         run('audio.play()');
         await waitFor(audio, 'playing');
-        testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("NowPlaying")', audio);
+        testExpected('internals.bestMediaElementForRemoteControls("NowPlaying")', audio);
         endTest();        
     });
     </script>

Added: trunk/LayoutTests/media/media-session/default-actionHandlers-expected.txt (0 => 275787)


--- trunk/LayoutTests/media/media-session/default-actionHandlers-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/media-session/default-actionHandlers-expected.txt	2021-04-10 00:45:37 UTC (rev 275787)
@@ -0,0 +1,29 @@
+
+RUN(video.src = "" "../content/test"))
+EVENT(loadeddata)
+Test that default media element action will be run when no media session handlers exist for that action.
+EXPECTED (video.paused == 'true') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "play"}))
+EXPECTED (video.paused == 'false') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "pause"}))
+EXPECTED (video.paused == 'true') OK
+RUN(video.currentTime = 0)
+EXPECTED (video.currentTime == '0') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "seekto", seekTime: 1}))
+EXPECTED (video.currentTime == '1') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "seekforward", seekOffset: 5}))
+EXPECTED (video.currentTime == '6') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "seekbackward", seekOffset: 5}))
+EXPECTED (video.currentTime == '1') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "play"}))
+ACTION: play
+EXPECTED (video.paused == 'true') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "play"}))
+EXPECTED (video.paused == 'false') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "pause"}))
+ACTION: pause
+EXPECTED (video.paused == 'false') OK
+RUN(internals.sendMediaSessionAction(navigator.mediaSession, {action: "pause"}))
+EXPECTED (video.paused == 'true') OK
+END OF TEST
+

Added: trunk/LayoutTests/media/media-session/default-actionHandlers.html (0 => 275787)


--- trunk/LayoutTests/media/media-session/default-actionHandlers.html	                        (rev 0)
+++ trunk/LayoutTests/media/media-session/default-actionHandlers.html	2021-04-10 00:45:37 UTC (rev 275787)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>default-actionHandlers</title>
+    <script src=""
+    <script src=""
+    <script>
+
+    async function runTest() {
+        if (!window.internals) {
+            failTest('This test requires Internals');
+            return;
+        }
+
+        findMediaElement();
+        run('video.src = "" "../content/test")');
+        await waitFor(video, 'loadeddata');
+
+        consoleWrite('Test that default media element action will be run when no media session handlers exist for that action.');
+
+        testExpected("video.paused", true);
+
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "play"})');
+        testExpected("video.paused", false);
+
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "pause"})');
+        testExpected("video.paused", true);
+
+        run('video.currentTime = 0');
+        testExpected("video.currentTime", 0);
+
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "seekto", seekTime: 1})');
+        testExpected("video.currentTime", 1);
+
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "seekforward", seekOffset: 5})');
+        testExpected("video.currentTime", 6);
+
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "seekbackward", seekOffset: 5})');
+        testExpected("video.currentTime", 1);
+
+        navigator.mediaSession.setActionHandler("play", actionDetails => {
+            consoleWrite(`ACTION: ${actionDetails.action}`);
+        });
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "play"})');
+        // Playback shouldn't have started if a handler for the play action was defined and it did nothing.
+        testExpected("video.paused", true);
+
+        navigator.mediaSession.setActionHandler("play", null);
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "play"})');
+        testExpected("video.paused", false);
+
+        navigator.mediaSession.setActionHandler("pause", actionDetails => {
+            consoleWrite(`ACTION: ${actionDetails.action}`);
+        });
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "pause"})');
+        // Playback shouldn't have been interrupted if a handler for the pause action was defined and it did nothing.
+        testExpected("video.paused", false);
+
+        navigator.mediaSession.setActionHandler("pause", null);
+        run('internals.sendMediaSessionAction(navigator.mediaSession, {action: "pause"})');
+        testExpected("video.paused", true);
+
+        endTest();
+    }
+    </script>
+</head>
+<body _onload_="runTest()">
+    <video controls preload='auto'></video>
+</body>
+</html>

Modified: trunk/LayoutTests/platform/mac/media/video-best-element-for-playback-controls-purpose-expected.txt (275786 => 275787)


--- trunk/LayoutTests/platform/mac/media/video-best-element-for-playback-controls-purpose-expected.txt	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/LayoutTests/platform/mac/media/video-best-element-for-playback-controls-purpose-expected.txt	2021-04-10 00:45:37 UTC (rev 275787)
@@ -1,31 +1,31 @@
 
 Unloaded video elements should not be considered main content.
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == 'null') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == 'null') OK
 
 Large, autoplay videos with video and audio should be considered main content.
 RUN(video = createVideo({autoplay: true, type: "audio+video", size: "large"}))
 EVENT(playing)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == '[object HTMLVideoElement]') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == '[object HTMLVideoElement]') OK
 
 Small, autoplay videos with video and audio should be considered main content.
 RUN(video = createVideo({autoplay: true, type: "audio+video", size: "small"}))
 EVENT(playing)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == 'null') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == 'null') OK
 
 Muted autoplay videos should not be considered main content.
 RUN(video = createVideo({autoplay: true, muted: true, type: "audio+video", size: "large"}))
 EVENT(playing)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == 'null') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == 'null') OK
 
 Video-only autoplay videos should not be considered main content.
 RUN(video = createVideo({autoplay: true, type: "video", size: "large"}))
 EVENT(playing)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == 'null') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == 'null') OK
 
 Non-playing videos should not be considered main content.
 RUN(video = createVideo({type: "audio+video", size: "large"}))
 EVENT(canplaythrough)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == 'null') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == 'null') OK
 
 Large, autoplay videos outside fullscreen element should not be considered main content
 RUN(video = createVideo({autoplay: true, muted: true, type: "audio+video", size: "large"}))
@@ -32,7 +32,7 @@
 EVENT(playing)
 RUN(document.querySelector("#fullscreen").webkitRequestFullscreen())
 EVENT(webkitfullscreenchange)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == 'null') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == 'null') OK
 RUN(document.webkitExitFullscreen())
 EVENT(webkitfullscreenchange)
 
@@ -41,7 +41,7 @@
 EVENT(playing)
 RUN(document.querySelector("#target").webkitRequestFullscreen())
 EVENT(webkitfullscreenchange)
-EXPECTED (internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager") == '[object HTMLVideoElement]') OK
+EXPECTED (internals.bestMediaElementForRemoteControls("ControlsManager") == '[object HTMLVideoElement]') OK
 RUN(document.webkitExitFullscreen())
 EVENT(webkitfullscreenchange)
 

Modified: trunk/LayoutTests/platform/mac/media/video-best-element-for-playback-controls-purpose.html (275786 => 275787)


--- trunk/LayoutTests/platform/mac/media/video-best-element-for-playback-controls-purpose.html	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/LayoutTests/platform/mac/media/video-best-element-for-playback-controls-purpose.html	2021-04-10 00:45:37 UTC (rev 275787)
@@ -63,7 +63,7 @@
         function() {
             consoleWrite('Unloaded video elements should not be considered main content.');
             video = createVideo();
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', null)
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', null)
         },
 
         async function() {
@@ -70,7 +70,7 @@
             consoleWrite('Large, autoplay videos with video and audio should be considered main content.')
             run('video = createVideo({autoplay: true, type: "audio+video", size: "large"})');
             await waitFor(video, 'playing');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', video)
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', video)
         },
 
         async function() {
@@ -77,7 +77,7 @@
             consoleWrite('Small, autoplay videos with video and audio should be considered main content.')
             run('video = createVideo({autoplay: true, type: "audio+video", size: "small"})');
             await waitFor(video, 'playing');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', null)
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', null)
         },
 
         async function() {
@@ -84,7 +84,7 @@
             consoleWrite('Muted autoplay videos should not be considered main content.')
             run('video = createVideo({autoplay: true, muted: true, type: "audio+video", size: "large"})');
             await waitFor(video, 'playing');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', null)
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', null)
         },
 
         async function() {
@@ -91,7 +91,7 @@
             consoleWrite('Video-only autoplay videos should not be considered main content.')
             run('video = createVideo({autoplay: true, type: "video", size: "large"})');
             await waitFor(video, 'playing');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', null)
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', null)
         },
 
         async function() {
@@ -98,7 +98,7 @@
             consoleWrite('Non-playing videos should not be considered main content.')
             run('video = createVideo({type: "audio+video", size: "large"})');
             await waitFor(video, 'canplaythrough');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', null)
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', null)
         },
 
         async function() {
@@ -106,7 +106,7 @@
             run('video = createVideo({autoplay: true, muted: true, type: "audio+video", size: "large"})');
             await waitFor(video, 'playing');
             await enterFullscreen('#fullscreen');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', null);
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', null);
             await exitFullscreen();
         },
 
@@ -115,7 +115,7 @@
             run('video = createVideo({autoplay: true, type: "audio+video", size: "large"})');
             await waitFor(video, 'playing');
             await enterFullscreen('#target');
-            testExpected('internals.bestMediaElementForShowingPlaybackControlsManager("ControlsManager")', video);
+            testExpected('internals.bestMediaElementForRemoteControls("ControlsManager")', video);
             await exitFullscreen();
         },
     ];

Modified: trunk/Source/WebCore/ChangeLog (275786 => 275787)


--- trunk/Source/WebCore/ChangeLog	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/ChangeLog	2021-04-10 00:45:37 UTC (rev 275787)
@@ -1,3 +1,56 @@
+2021-04-09  Jean-Yves Avenard  <[email protected]>
+
+        Media Session action should default to the MediaElement's default when no MediaSession handler are set
+        https://bugs.webkit.org/show_bug.cgi?id=224278
+        <rdar://problem/76339841>
+
+        Reviewed by Youenn Fablet .
+
+        When a media session doesn't explicitly define an action handler, we use the media element
+        default action of the same type.
+        Media Session doesn't track a particular media element, instead it loosely defines a guessed playback state
+        that tracks if some element in the current document is playing and not muted or otherwise paused.
+        (see https://w3c.github.io/mediasession/#playback-state-model)
+        We therefore need to determine what is currently the most suitable media element available in this document.
+        Unlike the Media Controller and the Now Playing policy that will only ever select a media
+        if it is currently playing and not muted, for the Media Session we may have to interact with paused media elements.
+        For this we add a new PlaybackControlsPurpose named MediaSession defining new search criterias.
+        A media element will be up for selection if it's playable (according to autoplay policy). From then,
+        the best element will be selected accoring to its visibility, its size, if it's beeing played previously and
+        the last time it was interacted with.
+
+        Test: media/media-session/default-actionHandlers.html
+
+        * Modules/mediasession/MediaSession.cpp:
+        (WebCore::platformCommandForMediaSessionAction): New convenience method to convert one datatype into another.
+        (WebCore::MediaSession::callActionHandler): Use user defined handler if present or run the related action on
+        the most suitable media element
+        (WebCore::MediaSession::activeMediaElement const): Determine the currently active media element in the current
+        Media Session's document.
+        * Modules/mediasession/MediaSession.h:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::mediaElementSessionInfoForSession): Add new hasEverNotifiedAboutPlaying member.
+        (WebCore::preferMediaControlsForCandidateSessionOverOtherCandidateSession): Amend sorting algorithm to cater
+        for the MediaSession criterias described above.
+        (WebCore::mediaSessionMayBeConfusedWithMainContent): When using MediaSession purpose, there is no possible
+        ambiguity, amend as such.
+        (WebCore::HTMLMediaElement::bestMediaElementForRemoteControls): Renamed from bestMediaElementForShowingPlaybackControlsManager
+        method to better match how the method is actually used.
+        * html/HTMLMediaElement.h:
+        * html/MediaElementSession.cpp:
+        (WebCore::MediaElementSession::canShowControlsManager const): Amend for new Media Session criterias.
+        (WebCore::MediaElementSession::didReceiveRemoteControlCommand): Always let MediaSession manage the actions handling.
+        * html/MediaElementSession.h:
+        * page/Page.cpp: 
+        (WebCore::Page::playbackControlsManagerUpdateTimerFired): amend following method rename.
+        * platform/audio/cocoa/MediaSessionManagerCocoa.mm:
+        (WebCore::MediaSessionManagerCocoa::nowPlayingEligibleSession): amend following method rename.
+        * testing/Internals.cpp:
+        (WebCore::Internals::bestMediaElementForRemoteControls): Renamed from bestMediaElementForShowingPlaybackControlsManager as above.
+        (WebCore::Internals::sendMediaSessionAction): amend following method rename.
+        * testing/Internals.h: Rename method
+        * testing/Internals.idl: Rename method
+
 2021-04-09  Fujii Hironori  <[email protected]>
 
         [Cairo][GPUP] GraphicsContextGLOpenGL::paintToCanvas can't paint into a remote canvas

Modified: trunk/Source/WebCore/Modules/mediasession/MediaSession.cpp (275786 => 275787)


--- trunk/Source/WebCore/Modules/mediasession/MediaSession.cpp	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/Modules/mediasession/MediaSession.cpp	2021-04-10 00:45:37 UTC (rev 275787)
@@ -30,6 +30,7 @@
 
 #include "DOMWindow.h"
 #include "EventNames.h"
+#include "HTMLMediaElement.h"
 #include "JSMediaPositionState.h"
 #include "JSMediaSessionAction.h"
 #include "JSMediaSessionPlaybackState.h"
@@ -76,6 +77,53 @@
     return PlatformMediaSession::NoCommand;
 }
 
+static Optional<std::pair<PlatformMediaSession::RemoteControlCommandType, PlatformMediaSession::RemoteCommandArgument>> platformCommandForMediaSessionAction(const MediaSessionActionDetails& actionDetails)
+{
+    PlatformMediaSession::RemoteControlCommandType command = PlatformMediaSession::NoCommand;
+    PlatformMediaSession::RemoteCommandArgument argument;
+
+    switch (actionDetails.action) {
+    case MediaSessionAction::Play:
+        command = PlatformMediaSession::PlayCommand;
+        break;
+    case MediaSessionAction::Pause:
+        command = PlatformMediaSession::PauseCommand;
+        break;
+    case MediaSessionAction::Seekbackward:
+        command = PlatformMediaSession::SkipBackwardCommand;
+        argument.time = actionDetails.seekOffset;
+        break;
+    case MediaSessionAction::Seekforward:
+        command = PlatformMediaSession::SkipForwardCommand;
+        argument.time = actionDetails.seekOffset;
+        break;
+    case MediaSessionAction::Previoustrack:
+        command = PlatformMediaSession::PreviousTrackCommand;
+        break;
+    case MediaSessionAction::Nexttrack:
+        command = PlatformMediaSession::NextTrackCommand;
+        break;
+    case MediaSessionAction::Skipad:
+        // Not supported at present.
+        break;
+    case MediaSessionAction::Stop:
+        command = PlatformMediaSession::StopCommand;
+        break;
+    case MediaSessionAction::Seekto:
+        command = PlatformMediaSession::SeekToPlaybackPositionCommand;
+        argument.time = actionDetails.seekTime;
+        argument.fastSeek = actionDetails.fastSeek;
+        break;
+    case MediaSessionAction::Settrack:
+        // Not supported at present.
+        break;
+    }
+    if (command == PlatformMediaSession::NoCommand)
+        return { };
+
+    return std::make_pair(command, argument);
+}
+
 Ref<MediaSession> MediaSession::create(Navigator& navigator)
 {
     return adoptRef(*new MediaSession(navigator));
@@ -199,22 +247,23 @@
     notifyActionHandlerObservers();
 }
 
-bool MediaSession::hasActionHandler(MediaSessionAction action) const
+bool MediaSession::callActionHandler(const MediaSessionActionDetails& actionDetails)
 {
-    return m_actionHandlers.contains(action);
-}
+    if (auto handler = m_actionHandlers.get(actionDetails.action)) {
+        handler->handleEvent(actionDetails);
+        return true;
+    }
+    auto element = activeMediaElement();
+    if (!element)
+        return false;
 
-RefPtr<MediaSessionActionHandler> MediaSession::handlerForAction(MediaSessionAction action) const
-{
-    return m_actionHandlers.get(action);
+    auto platformCommand = platformCommandForMediaSessionAction(actionDetails);
+    if (!platformCommand)
+        return false;
+    element->didReceiveRemoteControlCommand(platformCommand->first, platformCommand->second);
+    return true;
 }
 
-void MediaSession::callActionHandler(const MediaSessionActionDetails& actionDetails)
-{
-    if (auto handler = m_actionHandlers.get(actionDetails.action))
-        handler->handleEvent(actionDetails);
-}
-
 ExceptionOr<void> MediaSession::setPositionState(Optional<MediaPositionState>&& state)
 {
     if (state)
@@ -314,6 +363,15 @@
     });
 }
 
+RefPtr<HTMLMediaElement> MediaSession::activeMediaElement() const
+{
+    auto* doc = document();
+    if (!doc)
+        return nullptr;
+
+    return HTMLMediaElement::bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose::MediaSession, doc);
+}
+
 #if ENABLE(MEDIA_SESSION_COORDINATOR)
 void MediaSession::notifyReadyStateObservers()
 {

Modified: trunk/Source/WebCore/Modules/mediasession/MediaSession.h (275786 => 275787)


--- trunk/Source/WebCore/Modules/mediasession/MediaSession.h	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/Modules/mediasession/MediaSession.h	2021-04-10 00:45:37 UTC (rev 275787)
@@ -45,6 +45,7 @@
 namespace WebCore {
 
 class Document;
+class HTMLMediaElement;
 class MediaMetadata;
 class MediaSessionCoordinator;
 class Navigator;
@@ -84,12 +85,9 @@
     ExceptionOr<void> setPlaylist(ScriptExecutionContext&, Vector<RefPtr<MediaMetadata>>&&);
 #endif
 
-    bool hasActionHandler(MediaSessionAction) const;
-    WEBCORE_EXPORT RefPtr<MediaSessionActionHandler> handlerForAction(MediaSessionAction) const;
     bool hasActiveActionHandlers() const { return !m_actionHandlers.isEmpty(); }
+    WEBCORE_EXPORT bool callActionHandler(const MediaSessionActionDetails&);
 
-    void callActionHandler(const MediaSessionActionDetails&);
-
     const Logger& logger() const { return *m_logger.get(); }
 
     // EventTarget
@@ -134,6 +132,8 @@
     const char* activeDOMObjectName() const final { return "MediaSession"; }
     bool virtualHasPendingActivity() const final;
 
+    RefPtr<HTMLMediaElement> activeMediaElement() const;
+
     WeakPtr<Navigator> m_navigator;
     RefPtr<MediaMetadata> m_metadata;
     MediaSessionPlaybackState m_playbackState { MediaSessionPlaybackState::None };

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (275786 => 275787)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2021-04-10 00:45:37 UTC (rev 275787)
@@ -325,6 +325,7 @@
     bool isVisibleInViewportOrFullscreen : 1;
     bool isLargeEnoughForMainContent : 1;
     bool isPlayingAudio : 1;
+    bool hasEverNotifiedAboutPlaying : 1;
 };
 
 static MediaElementSessionInfo mediaElementSessionInfoForSession(const MediaElementSession& session, MediaElementSession::PlaybackControlsPurpose purpose)
@@ -337,7 +338,8 @@
         session.canShowControlsManager(purpose),
         element.isFullscreen() || element.isVisibleInViewport(),
         session.isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls),
-        element.isPlaying() && element.hasAudio() && !element.muted()
+        element.isPlaying() && element.hasAudio() && !element.muted(),
+        element.hasEverNotifiedAboutPlaying()
     };
 }
 
@@ -346,14 +348,21 @@
     MediaElementSession::PlaybackControlsPurpose purpose = session.purpose;
     ASSERT(purpose == otherSession.purpose);
 
-    // For the controls manager, prioritize visible media over offscreen media.
-    if (purpose == MediaElementSession::PlaybackControlsPurpose::ControlsManager && session.isVisibleInViewportOrFullscreen != otherSession.isVisibleInViewportOrFullscreen)
+    // For the controls manager and MediaSession, prioritize visible media over offscreen media.
+    if ((purpose == MediaElementSession::PlaybackControlsPurpose::ControlsManager || purpose == MediaElementSession::PlaybackControlsPurpose::MediaSession)
+        && session.isVisibleInViewportOrFullscreen != otherSession.isVisibleInViewportOrFullscreen)
         return session.isVisibleInViewportOrFullscreen;
 
-    // For Now Playing, prioritize elements that would normally satisfy main content.
-    if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying && session.isLargeEnoughForMainContent != otherSession.isLargeEnoughForMainContent)
+    // For Now Playing and MediaSession, prioritize elements that would normally satisfy main content.
+    if ((purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying || purpose == MediaElementSession::PlaybackControlsPurpose::MediaSession)
+        && session.isLargeEnoughForMainContent != otherSession.isLargeEnoughForMainContent)
         return session.isLargeEnoughForMainContent;
 
+    // For MediaSession, prioritize elements that have been played before.
+    if (purpose == MediaElementSession::PlaybackControlsPurpose::MediaSession
+        && session.hasEverNotifiedAboutPlaying != otherSession.hasEverNotifiedAboutPlaying)
+        return session.hasEverNotifiedAboutPlaying;
+
     // As a tiebreaker, prioritize elements that the user recently interacted with.
     return session.timeOfLastUserInteraction > otherSession.timeOfLastUserInteraction;
 }
@@ -360,6 +369,9 @@
 
 static bool mediaSessionMayBeConfusedWithMainContent(const MediaElementSessionInfo& session, MediaElementSession::PlaybackControlsPurpose purpose)
 {
+    if (purpose == MediaElementSession::PlaybackControlsPurpose::MediaSession)
+        return false;
+
     if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying)
         return session.isPlayingAudio;
 
@@ -581,12 +593,13 @@
         ThreadableBlobRegistry::unregisterBlobURL(m_blobURLForReading);
 }
 
-RefPtr<HTMLMediaElement> HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose purpose)
+RefPtr<HTMLMediaElement> HTMLMediaElement::bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose purpose, const Document* document)
 {
     Vector<MediaElementSessionInfo> candidateSessions;
     bool atLeastOneNonCandidateMayBeConfusedForMainContent = false;
-    PlatformMediaSessionManager::sharedManager().forEachMatchingSession([](auto& session) {
-        return is<MediaElementSession>(session);
+    PlatformMediaSessionManager::sharedManager().forEachMatchingSession([document](auto& session) {
+        return is<MediaElementSession>(session)
+            && (!document || &downcast<MediaElementSession>(session).element().document() == document);
     }, [&](auto& session) {
         auto mediaElementSessionInfo = mediaElementSessionInfoForSession(downcast<MediaElementSession>(session), purpose);
         if (mediaElementSessionInfo.canShowControlsManager)

Modified: trunk/Source/WebCore/html/HTMLMediaElement.h (275786 => 275787)


--- trunk/Source/WebCore/html/HTMLMediaElement.h	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/html/HTMLMediaElement.h	2021-04-10 00:45:37 UTC (rev 275787)
@@ -46,6 +46,7 @@
 #include "VisibilityChangeClient.h"
 #include <wtf/Function.h>
 #include <wtf/LoggerHelper.h>
+#include <wtf/Optional.h>
 #include <wtf/WeakPtr.h>
 
 #if USE(AUDIO_SESSION) && PLATFORM(MAC)
@@ -155,7 +156,7 @@
 
     static HashSet<HTMLMediaElement*>& allMediaElements();
 
-    WEBCORE_EXPORT static RefPtr<HTMLMediaElement> bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose);
+    WEBCORE_EXPORT static RefPtr<HTMLMediaElement> bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose, const Document* = nullptr);
 
     WEBCORE_EXPORT void rewind(double timeDelta);
     WEBCORE_EXPORT void returnToRealtime() override;
@@ -573,6 +574,8 @@
     WEBCORE_EXPORT void setOverridePreferredDynamicRangeMode(DynamicRangeMode);
     void setPreferredDynamicRangeMode(DynamicRangeMode);
 
+    void didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType, const PlatformMediaSession::RemoteCommandArgument&) override;
+
 protected:
     HTMLMediaElement(const QualifiedName&, Document&, bool createdByParser);
     virtual void finishInitialization();
@@ -866,7 +869,6 @@
     void resumeAutoplaying() override;
     void mayResumePlayback(bool shouldResume) override;
     bool canReceiveRemoteControlCommands() const override { return true; }
-    void didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType, const PlatformMediaSession::RemoteCommandArgument&) override;
     bool shouldOverrideBackgroundPlaybackRestriction(PlatformMediaSession::InterruptionType) const override;
     bool shouldOverrideBackgroundLoadingRestriction() const override;
     bool canProduceAudio() const final;

Modified: trunk/Source/WebCore/html/MediaElementSession.cpp (275786 => 275787)


--- trunk/Source/WebCore/html/MediaElementSession.cpp	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/html/MediaElementSession.cpp	2021-04-10 00:45:37 UTC (rev 275787)
@@ -525,7 +525,7 @@
         return true;
     }
 
-    if (client().presentationType() == MediaType::Audio && purpose == PlaybackControlsPurpose::ControlsManager) {
+    if (client().presentationType() == MediaType::Audio && (purpose == PlaybackControlsPurpose::ControlsManager || purpose == PlaybackControlsPurpose::MediaSession)) {
         if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || m_element.document().processingUserGestureForMedia()) {
             INFO_LOG(LOGIDENTIFIER, "returning TRUE: audio element with user gesture");
             return true;
@@ -565,7 +565,7 @@
         return false;
     }
 
-    if (!m_element.hasEverNotifiedAboutPlaying()) {
+    if (purpose != PlaybackControlsPurpose::MediaSession && !m_element.hasEverNotifiedAboutPlaying()) {
         INFO_LOG(LOGIDENTIFIER, "returning FALSE: hasn't fired playing notification");
         return false;
     }
@@ -597,7 +597,7 @@
         }
     }
 
-    if (purpose == PlaybackControlsPurpose::NowPlaying) {
+    if (purpose == PlaybackControlsPurpose::NowPlaying || purpose == PlaybackControlsPurpose::MediaSession) {
         INFO_LOG(LOGIDENTIFIER, "returning TRUE: potentially plays audio");
         return true;
     }
@@ -1135,10 +1135,7 @@
         return;
     }
 
-    if (auto handler = session->handlerForAction(actionDetails.action))
-        handler->handleEvent(actionDetails);
-    else
-        ALWAYS_LOG(LOGIDENTIFIER, "Ignoring command, no action handler registered for ", actionDetails.action);
+    session->callActionHandler(actionDetails);
 }
 #endif
 

Modified: trunk/Source/WebCore/html/MediaElementSession.h (275786 => 275787)


--- trunk/Source/WebCore/html/MediaElementSession.h	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/html/MediaElementSession.h	2021-04-10 00:45:37 UTC (rev 275787)
@@ -158,7 +158,7 @@
     bool wantsToObserveViewportVisibilityForMediaControls() const;
     bool wantsToObserveViewportVisibilityForAutoplay() const;
 
-    enum class PlaybackControlsPurpose { ControlsManager, NowPlaying };
+    enum class PlaybackControlsPurpose { ControlsManager, NowPlaying, MediaSession };
     bool canShowControlsManager(PlaybackControlsPurpose) const;
     bool isLargeEnoughForMainContent(MediaSessionMainContentPurpose) const;
     bool isMainContentForPurposesOfAutoplayEvents() const;

Modified: trunk/Source/WebCore/page/Page.cpp (275786 => 275787)


--- trunk/Source/WebCore/page/Page.cpp	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/page/Page.cpp	2021-04-10 00:45:37 UTC (rev 275787)
@@ -2135,7 +2135,7 @@
 
 void Page::playbackControlsManagerUpdateTimerFired()
 {
-    if (auto bestMediaElement = HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose::ControlsManager))
+    if (auto bestMediaElement = HTMLMediaElement::bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose::ControlsManager))
         chrome().client().setUpPlaybackControlsManager(*bestMediaElement);
     else
         chrome().client().clearPlaybackControlsManager();

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


--- trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm	2021-04-10 00:45:37 UTC (rev 275787)
@@ -339,7 +339,7 @@
 PlatformMediaSession* MediaSessionManagerCocoa::nowPlayingEligibleSession()
 {
     // FIXME: Fix this layering violation.
-    if (auto element = HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose::NowPlaying))
+    if (auto element = HTMLMediaElement::bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose::NowPlaying))
         return &element->mediaSession();
 
     return nullptr;

Modified: trunk/Source/WebCore/testing/Internals.cpp (275786 => 275787)


--- trunk/Source/WebCore/testing/Internals.cpp	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/testing/Internals.cpp	2021-04-10 00:45:37 UTC (rev 275787)
@@ -4423,9 +4423,9 @@
 }
 
 #if ENABLE(VIDEO)
-RefPtr<HTMLMediaElement> Internals::bestMediaElementForShowingPlaybackControlsManager(Internals::PlaybackControlsPurpose purpose)
+RefPtr<HTMLMediaElement> Internals::bestMediaElementForRemoteControls(Internals::PlaybackControlsPurpose purpose)
 {
-    return HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(purpose);
+    return HTMLMediaElement::bestMediaElementForRemoteControls(purpose);
 }
 
 Internals::MediaSessionState Internals::mediaSessionState(HTMLMediaElement& element)
@@ -6121,10 +6121,9 @@
 
 ExceptionOr<void> Internals::sendMediaSessionAction(MediaSession& session, const MediaSessionActionDetails& actionDetails)
 {
-    if (auto handler = session.handlerForAction(actionDetails.action)) {
-        handler->handleEvent(actionDetails);
+    if (session.callActionHandler(actionDetails))
         return { };
-    }
+
     return Exception { InvalidStateError };
 }
 

Modified: trunk/Source/WebCore/testing/Internals.h (275786 => 275787)


--- trunk/Source/WebCore/testing/Internals.h	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/testing/Internals.h	2021-04-10 00:45:37 UTC (rev 275787)
@@ -926,7 +926,7 @@
 
 #if ENABLE(VIDEO)
     using PlaybackControlsPurpose = MediaElementSession::PlaybackControlsPurpose;
-    RefPtr<HTMLMediaElement> bestMediaElementForShowingPlaybackControlsManager(PlaybackControlsPurpose);
+    RefPtr<HTMLMediaElement> bestMediaElementForRemoteControls(PlaybackControlsPurpose);
 
     using MediaSessionState = PlatformMediaSession::State;
     MediaSessionState mediaSessionState(HTMLMediaElement&);

Modified: trunk/Source/WebCore/testing/Internals.idl (275786 => 275787)


--- trunk/Source/WebCore/testing/Internals.idl	2021-04-10 00:03:58 UTC (rev 275786)
+++ trunk/Source/WebCore/testing/Internals.idl	2021-04-10 00:45:37 UTC (rev 275787)
@@ -905,7 +905,7 @@
 
     [Conditional=VIDEO] readonly attribute NowPlayingState nowPlayingState;
 
-    [Conditional=VIDEO] HTMLMediaElement bestMediaElementForShowingPlaybackControlsManager(PlaybackControlsPurpose purpose);
+    [Conditional=VIDEO] HTMLMediaElement bestMediaElementForRemoteControls(PlaybackControlsPurpose purpose);
     [Conditional=VIDEO] MediaSessionState mediaSessionState(HTMLMediaElement element);
 
     [Conditional=VIDEO] MediaUsageState mediaUsageState(HTMLMediaElement element);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to