Implement the Remote Playback API.
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Oct 2019 21:54:07 +0000 (21:54 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 29 Oct 2019 21:54:07 +0000 (21:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162971

Reviewed by Youenn Fablet.

Source/JavaScriptCore:

Add RemotePlayback as a common identifier, needed for bindings due to "EnabledAtRuntime=RemotePlayback".

* runtime/CommonIdentifiers.h:

Source/WebCore:

Tests: media/remoteplayback-cancel-invalid.html
       media/remoteplayback-prompt.html
       media/remoteplayback-target-availability.html
       media/remoteplayback-watch-disableremoteplayback.html
       imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability.html
       imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws.html
       imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws.html
       imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws.html
       imported/w3c/web-platform-tests/remote-playback/idlharness.window.html
       imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback.html

The Remote Playback API <https://w3c.github.io/remote-playback> allows page authors
to control the presentation of <audio> and <video> elements to remote playback devices
such as the Apple TV or Chromecast. WebKit already exposes a non-standardized API to
initiate remote playback, and the Remote Playback API is largely congruent with the
webkitShowPlaybackTargetPicker() API et. al.

One addition provided by the Remote Playback API is an event in the case the user
dismisses the picker UI without making a choice, so this concept needs to be plumbed
through from the ChromeClient to the PlatformMediaSession.

* DerivedSources.make:
* Modules/mediasession/WebMediaSessionManager.cpp:
(WebCore::WebMediaSessionManager::mockMediaPlaybackTargetPickerDismissPopup):
(WebCore::WebMediaSessionManager::playbackTargetPickerWasDismissed):
(WebCore::WebMediaSessionManager::configurePlaybackTargetClients):
* Modules/mediasession/WebMediaSessionManager.h:
* Modules/mediasession/WebMediaSessionManagerClient.h:
* Modules/remoteplayback/HTMLMediaElementRemotePlayback.h: Added.
(WebCore::HTMLMediaElementRemotePlayback::remote):
(WebCore::HTMLMediaElementRemotePlayback::hasAttributeWithoutSynchronization):
(WebCore::HTMLMediaElementRemotePlayback::setBooleanAttribute):
* Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl: Added.
* Modules/remoteplayback/RemotePlayback.cpp: Added.
(WebCore::RemotePlayback::create):
(WebCore::RemotePlayback::RemotePlayback):
(WebCore::RemotePlayback::~RemotePlayback):
(WebCore::RemotePlayback::watchAvailability):
(WebCore::RemotePlayback::cancelWatchAvailability):
(WebCore::RemotePlayback::prompt):
(WebCore::RemotePlayback::shouldPlayToRemoteTargetChanged):
(WebCore::RemotePlayback::setState):
(WebCore::RemotePlayback::establishConnection):
(WebCore::RemotePlayback::disconnect):
(WebCore::RemotePlayback::updateAvailability):
(WebCore::RemotePlayback::playbackTargetPickerWasDismissed):
(WebCore::RemotePlayback::isPlayingToRemoteTargetChanged):
(WebCore::RemotePlayback::hasAvailabilityCallbacks):
(WebCore::RemotePlayback::availabilityChanged):
(WebCore::RemotePlayback::invalidate):
(WebCore::RemotePlayback::activeDOMObjectName):
(WebCore::RemotePlayback::canSuspendForDocumentSuspension):
(WebCore::RemotePlayback::stop):
* Modules/remoteplayback/RemotePlayback.h: Added.
(WebCore::RemotePlayback::state):
* Modules/remoteplayback/RemotePlayback.idl: Added.
* Modules/remoteplayback/RemotePlaybackAvailabilityCallback.h: Added.
(WebCore::RemotePlaybackAvailabilityCallback::~RemotePlaybackAvailabilityCallback):
* Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl: Added.
* WebCore.xcodeproj/project.pbxproj:
* bindings/generic/RuntimeEnabledFeatures.h:
(WebCore::RuntimeEnabledFeatures::setRemotePlaybackEnabled):
(WebCore::RuntimeEnabledFeatures::remotePlaybackEnabled):
* dom/Document.cpp:
(WebCore::Document::playbackTargetPickerWasDismissed):
* dom/Document.h:
* dom/EventNames.h:
* dom/EventTargetFactory.in:
* html/HTMLAttributeNames.in:
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::HTMLMediaElement):
(WebCore::HTMLMediaElement::~HTMLMediaElement):
(WebCore::HTMLMediaElement::clearMediaPlayer):
(WebCore::HTMLMediaElement::wirelessRoutesAvailableDidChange):
(WebCore::HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged):
(WebCore::HTMLMediaElement::addEventListener):
(WebCore::HTMLMediaElement::removeEventListener):
(WebCore::HTMLMediaElement::setWirelessPlaybackTarget):
(WebCore::HTMLMediaElement::playbackTargetPickerWasDismissed):
(WebCore::HTMLMediaElement::remoteHasAvailabilityCallbacksChanged):
(WebCore::HTMLMediaElement::createMediaPlayer):
* html/HTMLMediaElement.h:
(WebCore::HTMLMediaElement::remote):
* html/MediaElementSession.cpp:
(WebCore::MediaElementSession::wirelessVideoPlaybackDisabled):
(WebCore::MediaElementSession::playbackTargetPickerWasDismissed):
* html/MediaElementSession.h:
* page/Page.cpp:
(WebCore::Page::mockMediaPlaybackTargetPickerDismissPopup):
(WebCore::Page::playbackTargetPickerWasDismissed):
* page/Page.h:
* page/Settings.in:
* platform/audio/PlatformMediaSession.h:
(WebCore::PlatformMediaSessionClient::playbackTargetPickerWasDismissed):
* platform/graphics/MediaPlaybackTargetClient.h:
* platform/graphics/MediaPlaybackTargetPicker.h:
(WebCore::MediaPlaybackTargetPicker::playbackTargetPickerWasDismissed):
* platform/graphics/avfoundation/objc/MediaPlaybackTargetPickerMac.mm:
(WebCore::MediaPlaybackTargetPickerMac::showPlaybackTargetPicker):
* platform/mock/MediaPlaybackTargetPickerMock.cpp:
(WebCore::MediaPlaybackTargetPickerMock::dismissPopup):
* platform/mock/MediaPlaybackTargetPickerMock.h:
* testing/Internals.cpp:
(WebCore::Internals::mockMediaPlaybackTargetPickerDismissPopup):
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit:

Add a preference to enable the Remote Playback API. Pass the playbackTargetPickerWasDismissed()
notification on to Page.

* Shared/WebPreferencesDefinitions.h:
* UIProcess/API/C/WKPreferences.cpp:
(WKPreferencesGetRemotePlaybackEnabled):
(WKPreferencesSetRemotePlaybackEnabled):
* UIProcess/API/C/WKPreferencesRef.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::playbackTargetPickerWasDismissed):
* UIProcess/WebPageProxy.h:
* WebProcess/WebCoreSupport/WebChromeClient.cpp:
(WebKit::WebChromeClient::mockMediaPlaybackTargetPickerDismissPopup):
* WebProcess/WebCoreSupport/WebChromeClient.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::updatePreferences):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/mac/WebPageMac.mm:
(WebKit::WebPage::playbackTargetPickerWasDismissed):

Tools:

Enable RemotePlayback for DumpRenderTree and WebKitTestRunner.

* DumpRenderTree/mac/DumpRenderTree.mm:
(resetWebPreferencesToConsistentValues):
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::resetPreferencesToConsistentValues):

LayoutTests:

* media/remoteplayback-cancel-invalid-expected.txt: Added.
* media/remoteplayback-cancel-invalid.html: Added.
* media/remoteplayback-prompt-expected.txt: Added.
* media/remoteplayback-prompt.html: Added.
* media/remoteplayback-target-availability-expected.txt: Added.
* media/remoteplayback-target-availability.html: Added.
* media/remoteplayback-watch-disableremoteplayback-expected.txt: Added.
* media/remoteplayback-watch-disableremoteplayback.html: Added.
* platform/ios-wk2/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@251737 268f45cc-cd09-0410-ab3c-d52691b4dbfc

95 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/resources/import-expectations.json
LayoutTests/imported/w3c/web-platform-tests/remote-playback/META.yml [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/README.md [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/w3c-import.log [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback.html [new file with mode: 0644]
LayoutTests/media/remoteplayback-cancel-invalid-expected.txt [new file with mode: 0644]
LayoutTests/media/remoteplayback-cancel-invalid.html [new file with mode: 0644]
LayoutTests/media/remoteplayback-prompt-expected.txt [new file with mode: 0644]
LayoutTests/media/remoteplayback-prompt.html [new file with mode: 0644]
LayoutTests/media/remoteplayback-target-availability-expected.txt [new file with mode: 0644]
LayoutTests/media/remoteplayback-target-availability.html [new file with mode: 0644]
LayoutTests/media/remoteplayback-watch-disableremoteplayback-expected.txt [new file with mode: 0644]
LayoutTests/media/remoteplayback-watch-disableremoteplayback.html [new file with mode: 0644]
LayoutTests/platform/ios-wk2/TestExpectations
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/WebCore/ChangeLog
Source/WebCore/DerivedSources-input.xcfilelist
Source/WebCore/DerivedSources-output.xcfilelist
Source/WebCore/DerivedSources.make
Source/WebCore/Modules/mediasession/WebMediaSessionManager.cpp
Source/WebCore/Modules/mediasession/WebMediaSessionManager.h
Source/WebCore/Modules/mediasession/WebMediaSessionManagerClient.h
Source/WebCore/Modules/remoteplayback/HTMLMediaElementRemotePlayback.h [new file with mode: 0644]
Source/WebCore/Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl [new file with mode: 0644]
Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp [new file with mode: 0644]
Source/WebCore/Modules/remoteplayback/RemotePlayback.h [new file with mode: 0644]
Source/WebCore/Modules/remoteplayback/RemotePlayback.idl [new file with mode: 0644]
Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.h [new file with mode: 0644]
Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl [new file with mode: 0644]
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/WebCoreBuiltinNames.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/EventNames.h
Source/WebCore/dom/EventTargetFactory.in
Source/WebCore/html/HTMLAttributeNames.in
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/html/MediaElementSession.cpp
Source/WebCore/html/MediaElementSession.h
Source/WebCore/page/ChromeClient.h
Source/WebCore/page/Page.cpp
Source/WebCore/page/Page.h
Source/WebCore/page/Settings.yaml
Source/WebCore/platform/audio/PlatformMediaSession.h
Source/WebCore/platform/graphics/MediaPlaybackTargetClient.h
Source/WebCore/platform/graphics/MediaPlaybackTargetPicker.cpp
Source/WebCore/platform/graphics/MediaPlaybackTargetPicker.h
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlaybackTargetPickerMac.mm
Source/WebCore/platform/mock/MediaPlaybackTargetPickerMock.cpp
Source/WebCore/platform/mock/MediaPlaybackTargetPickerMock.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/UIProcess/API/C/WKPreferences.cpp
Source/WebKit/UIProcess/API/C/WKPreferencesRef.h
Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm
Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm
Source/WebKitLegacy/mac/WebCoreSupport/WebChromeClient.h
Source/WebKitLegacy/mac/WebCoreSupport/WebChromeClient.mm
Source/WebKitLegacy/mac/WebView/WebMediaPlaybackTargetPicker.h
Source/WebKitLegacy/mac/WebView/WebMediaPlaybackTargetPicker.mm
Source/WebKitLegacy/mac/WebView/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/mac/WebView/WebPreferences.mm
Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h
Source/WebKitLegacy/mac/WebView/WebView.mm
Source/WebKitLegacy/mac/WebView/WebViewInternal.h
Tools/ChangeLog
Tools/DumpRenderTree/mac/DumpRenderTree.mm
Tools/WebKitTestRunner/TestController.cpp

index 71e941c..9ee949a 100644 (file)
@@ -1,3 +1,20 @@
+2019-10-07  Jer Noble  <jer.noble@apple.com>
+
+        Implement the Remote Playback API.
+        https://bugs.webkit.org/show_bug.cgi?id=162971
+
+        Reviewed by Youenn Fablet.
+
+        * media/remoteplayback-cancel-invalid-expected.txt: Added.
+        * media/remoteplayback-cancel-invalid.html: Added.
+        * media/remoteplayback-prompt-expected.txt: Added.
+        * media/remoteplayback-prompt.html: Added.
+        * media/remoteplayback-target-availability-expected.txt: Added.
+        * media/remoteplayback-target-availability.html: Added.
+        * media/remoteplayback-watch-disableremoteplayback-expected.txt: Added.
+        * media/remoteplayback-watch-disableremoteplayback.html: Added.
+        * platform/ios-wk2/TestExpectations:
+
 2019-10-29  Truitt Savell  <tsavell@apple.com>
 
         imported/w3c/web-platform-tests/css/css-sizing/dynamic-available-size-iframe.html is flakey
index d9fee32..3e47834 100644 (file)
     "web-platform-tests/quirks-mode": "skip", 
     "web-platform-tests/quirks/unitless-length": "import", 
     "web-platform-tests/referrer-policy": "import", 
-    "web-platform-tests/remote-playback": "skip", 
+    "web-platform-tests/remote-playback": "import", 
     "web-platform-tests/requestidlecallback": "import", 
     "web-platform-tests/resize-observer": "skip", 
     "web-platform-tests/resize-observer/": "import", 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/META.yml b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/META.yml
new file mode 100644 (file)
index 0000000..c23f3bd
--- /dev/null
@@ -0,0 +1,3 @@
+spec: https://w3c.github.io/remote-playback/
+suggested_reviewers:
+  - mounirlamouri
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/README.md b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/README.md
new file mode 100644 (file)
index 0000000..b934227
--- /dev/null
@@ -0,0 +1,14 @@
+# Remote Playback API specification Tests
+
+The Remote Playback API can be found here:
+
+GitHub repository: https://github.com/w3c/remote-playback/
+
+File an issue: https://github.com/w3c/remote-playback/issues/new
+
+## Hardware/network dependency
+
+The Remote Playback API requires to communicate with a device over the network.
+Some behavior would require a real devices to be implemented. In order to keep
+these tests automated, only behaviors that can be run without user gesture or
+specific configurations are available here.
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability-expected.txt
new file mode 100644 (file)
index 0000000..55178af
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS Test that calling cancelWatchAvailability() with an id does remove the callback. 
+PASS Test that calling cancelWatchAvailability() without an id removes all the callbacks. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability.html b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability.html
new file mode 100644 (file)
index 0000000..e4b6330
--- /dev/null
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<title>Tests various ways to call cancelWatchAvailability()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script>
+async_test(t => {
+  var v = document.createElement('video');
+  v.src = getVideoURI('movie_5');
+
+  v.remote.watchAvailability(function() {})
+    .then(t.step_func(id => {
+      v.remote.cancelWatchAvailability(id).then(t.step_func(function() {
+        v.remote.cancelWatchAvailability(id).then(
+          t.unreached_func(), t.step_func_done(e => {
+            assert_equals(e.name, 'NotFoundError');
+          })
+        );
+      }), t.unreached_func());
+    }), t.unreached_func());
+}, 'Test that calling cancelWatchAvailability() with an id does remove the callback.');
+
+async_test(t => {
+  var v = document.createElement('video');
+  v.src = getVideoURI('movie_5');
+
+  Promise.all([
+      v.remote.watchAvailability(function() {}),
+      v.remote.watchAvailability(function() {})
+  ]).then(t.step_func(ids => {
+    v.remote.cancelWatchAvailability().then(t.step_func(function() {
+      v.remote.cancelWatchAvailability(ids[0]).then(t.unreached_func(), t.step_func(function(e) {
+        assert_equals(e.name, 'NotFoundError');
+        v.remote.cancelWatchAvailability(ids[1])
+          .then(t.unreached_func(), t.step_func_done(function(e) {
+            assert_equals(e.name, 'NotFoundError');
+          }));
+      }));
+    }), t.unreached_func());
+  }), t.unreached_func());
+}, 'Test that calling cancelWatchAvailability() without an id removes all the callbacks.');
+</script>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws-expected.txt
new file mode 100644 (file)
index 0000000..674fd86
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Test that calling cancelWatchAvailability() when disableRemotePlayback attribute is set throws an exception. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws.html b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws.html
new file mode 100644 (file)
index 0000000..fdcc734
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<title>Test that calling cancelWatchAvailability() when disableRemotePlayback attribute is set throws an exception</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script>
+async_test(t => {
+  var v = document.createElement('video');
+  v.src = getVideoURI('movie_5');
+
+  v.remote.watchAvailability(function() {})
+    .then(id => {
+      v.disableRemotePlayback = true;
+      v.remote.cancelWatchAvailability(id).then(
+        t.unreached_func(),
+        t.step_func(e => {
+          assert_equals(e.name, 'InvalidStateError');
+          v.remote.cancelWatchAvailability().then(
+            t.unreached_func(),
+            t.step_func_done(e => {
+              assert_equals(e.name, 'InvalidStateError');
+            })
+          );
+        }));
+    }, t.unreached_func());
+}, 'Test that calling cancelWatchAvailability() when disableRemotePlayback attribute is set throws an exception.');
+</script>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws-expected.txt
new file mode 100644 (file)
index 0000000..c13c1e6
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Test that calling prompt() when disableRemotePlayback attribute is set throws an exception. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws.html b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws.html
new file mode 100644 (file)
index 0000000..7ddc2ad
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<title>Test that calling prompt() when disableRemotePlayback attribute is set throws an exception</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script>
+promise_test(t => {
+  var v = document.createElement('video');
+  v.src = getVideoURI('movie_5');
+  v.disableRemotePlayback = true;
+
+  return promise_rejects(t, 'InvalidStateError', v.remote.prompt());
+}, 'Test that calling prompt() when disableRemotePlayback attribute is set throws an exception.');
+</script>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws-expected.txt
new file mode 100644 (file)
index 0000000..8ab63be
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Test that calling watchAvailability() when disableRemotePlayback attribute is set throws an exception. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws.html b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws.html
new file mode 100644 (file)
index 0000000..3c21f01
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<title>Test that calling watchAvailability() when disableRemotePlayback attribute is set throws an exception</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script>
+promise_test(t => {
+  var v = document.createElement('video');
+  v.src = getVideoURI('movie_5');
+  v.disableRemotePlayback = true;
+
+  return promise_rejects(t, 'InvalidStateError',
+                         v.remote.watchAvailability(function() {}));
+}, 'Test that calling watchAvailability() when disableRemotePlayback attribute is set throws an exception.');
+</script>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window-expected.txt
new file mode 100644 (file)
index 0000000..be588c9
--- /dev/null
@@ -0,0 +1,30 @@
+
+PASS idl_test setup 
+PASS Partial interface HTMLMediaElement: original interface defined 
+PASS RemotePlayback interface: existence and properties of interface object 
+PASS RemotePlayback interface object length 
+PASS RemotePlayback interface object name 
+PASS RemotePlayback interface: existence and properties of interface prototype object 
+PASS RemotePlayback interface: existence and properties of interface prototype object's "constructor" property 
+PASS RemotePlayback interface: existence and properties of interface prototype object's @@unscopables property 
+PASS RemotePlayback interface: operation watchAvailability(RemotePlaybackAvailabilityCallback) 
+PASS RemotePlayback interface: operation cancelWatchAvailability(long) 
+PASS RemotePlayback interface: attribute state 
+PASS RemotePlayback interface: attribute onconnecting 
+PASS RemotePlayback interface: attribute onconnect 
+PASS RemotePlayback interface: attribute ondisconnect 
+PASS RemotePlayback interface: operation prompt() 
+PASS RemotePlayback must be primary interface of media.remote 
+PASS Stringification of media.remote 
+PASS RemotePlayback interface: media.remote must inherit property "watchAvailability(RemotePlaybackAvailabilityCallback)" with the proper type 
+PASS RemotePlayback interface: calling watchAvailability(RemotePlaybackAvailabilityCallback) on media.remote with too few arguments must throw TypeError 
+PASS RemotePlayback interface: media.remote must inherit property "cancelWatchAvailability(long)" with the proper type 
+PASS RemotePlayback interface: calling cancelWatchAvailability(long) on media.remote with too few arguments must throw TypeError 
+PASS RemotePlayback interface: media.remote must inherit property "state" with the proper type 
+PASS RemotePlayback interface: media.remote must inherit property "onconnecting" with the proper type 
+PASS RemotePlayback interface: media.remote must inherit property "onconnect" with the proper type 
+PASS RemotePlayback interface: media.remote must inherit property "ondisconnect" with the proper type 
+PASS RemotePlayback interface: media.remote must inherit property "prompt()" with the proper type 
+PASS HTMLMediaElement interface: attribute remote 
+PASS HTMLMediaElement interface: attribute disableRemotePlayback 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.html b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.html
new file mode 100644 (file)
index 0000000..2382913
--- /dev/null
@@ -0,0 +1 @@
+<!-- This file is required for WebKit test infrastructure to run the templated test -->
\ No newline at end of file
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.js b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.js
new file mode 100644 (file)
index 0000000..73bc998
--- /dev/null
@@ -0,0 +1,28 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=/common/media.js
+
+'use strict';
+
+// https://w3c.github.io/remoteplayback/
+
+idl_test(
+  ['remote-playback'],
+  ['html', 'dom'],
+  idl_array => {
+    try {
+      const media = document.createElement('video');
+      media.src = getVideoURI('movie_5');
+      media.width = media.height = 10;
+      document.body.appendChild(media);
+      self.media = media;
+    } catch (e) {
+      // Will be surfaced when media is undefined below.
+    }
+
+    idl_array.add_objects({
+      HTMLVideoElement: ['media'],
+      RemotePlayback: ['media.remote']
+    });
+  }
+);
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/w3c-import.log
new file mode 100644 (file)
index 0000000..06c055e
--- /dev/null
@@ -0,0 +1,24 @@
+The tests in this directory were imported from the W3C repository.
+Do NOT modify these tests directly in WebKit.
+Instead, create a pull request on the WPT github:
+       https://github.com/web-platform-tests/wpt
+
+Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
+
+Do NOT modify or remove this file.
+
+------------------------------------------------------------------------
+Properties requiring vendor prefixes:
+None
+Property values requiring vendor prefixes:
+None
+------------------------------------------------------------------------
+List of files:
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/META.yml
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/README.md
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability.html
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws.html
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws.html
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws.html
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/idlharness.window.js
+/LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback.html
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback-expected.txt
new file mode 100644 (file)
index 0000000..1526a54
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Test that the callback is called once watchAvailability() resolves. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback.html b/LayoutTests/imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback.html
new file mode 100644 (file)
index 0000000..851558b
--- /dev/null
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<title>Test that the callback is called once watchAvailability() resolves.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script>
+async_test(t => {
+  var v = document.createElement('video');
+  v.src = getVideoURI('movie_5');
+
+  var promiseResolved = false;
+
+  function callback(available) {
+    assert_true(promiseResolved);
+  }
+
+  v.remote.watchAvailability(t.step_func_done(callback)).then(
+    t.step_func(() => { promiseResolved = true; }), t.unreached_func());
+}, 'Test that the callback is called once watchAvailability() resolves.');
+</script>
+</html>
diff --git a/LayoutTests/media/remoteplayback-cancel-invalid-expected.txt b/LayoutTests/media/remoteplayback-cancel-invalid-expected.txt
new file mode 100644 (file)
index 0000000..82ca37c
--- /dev/null
@@ -0,0 +1,8 @@
+
+Test that passing an invalid id to cancelWatchAvailability will generate an error.
+
+** create initial callback
+** pass invalid id to cancelWatchAvailability
+EXPECTED ERROR: NotFoundError: The object can not be found here.
+END OF TEST
+
diff --git a/LayoutTests/media/remoteplayback-cancel-invalid.html b/LayoutTests/media/remoteplayback-cancel-invalid.html
new file mode 100644 (file)
index 0000000..d23e0dd
--- /dev/null
@@ -0,0 +1,41 @@
+<html>
+    <head>
+        <script src='media-file.js'></script>
+        <script src='video-test.js'></script>
+        <script>
+            var video;
+
+            function start()
+            {
+                findMediaElement();
+
+                if (window.internals) {
+                    internals.setMockMediaPlaybackTargetPickerEnabled(true);
+                    internals.settings.setAllowsAirPlayForMediaPlayback(true);
+                }
+
+                consoleWrite('** create initial callback');
+                video.remote.watchAvailability(callback).then(cancelInvalid);
+            }
+
+            function callback(available) { };
+
+            function cancelInvalid(id)
+            {
+                consoleWrite('** pass invalid id to cancelWatchAvailability');
+                video.remote.cancelWatchAvailability(id + 1).catch(handleError);
+            }
+
+            function handleError(exception)
+            {
+                consoleWrite(`EXPECTED ERROR: ${ exception }`);
+                endTest();
+            }
+        </script>
+    </head>
+
+    <body onload='start()'>
+        <video controls></video>
+        <p>Test that passing an invalid id to cancelWatchAvailability will generate an error.</p>
+    </body>
+</html>
diff --git a/LayoutTests/media/remoteplayback-prompt-expected.txt b/LayoutTests/media/remoteplayback-prompt-expected.txt
new file mode 100644 (file)
index 0000000..2c6a53d
--- /dev/null
@@ -0,0 +1,24 @@
+
+Test the operation of prompt().
+
+
+** prompt() when disableRemotePlayback is set should fail
+RUN(video.disableRemotePlayback = true)
+EXPECTED ERROR: InvalidStateError: The object is in an invalid state.
+RUN(video.disableRemotePlayback = false)
+
+** prompt() outside of a user action should fail
+EXPECTED ERROR: NotSupportedError: The operation is not supported.
+
+** prompt() when no targets are available should fail
+EXPECTED ERROR: NotSupportedError: The operation is not supported.
+
+** Simulate a device becoming available
+RUN(video.src = findMediaFile("video", "content/test"))
+EVENT(canplaythrough)
+
+** Simulate a device becoming selected
+EVENT(connecting)
+EVENT(connect)
+END OF TEST
+
diff --git a/LayoutTests/media/remoteplayback-prompt.html b/LayoutTests/media/remoteplayback-prompt.html
new file mode 100644 (file)
index 0000000..6326016
--- /dev/null
@@ -0,0 +1,79 @@
+<html>
+    <head>
+        <script src='media-file.js'></script>
+        <script src='video-test.js'></script>
+        <script>
+            var video;
+            var callbackId;
+
+            var nextStep;
+
+            function start()
+            {
+                findMediaElement();
+
+                if (window.internals) {
+                    internals.setMockMediaPlaybackTargetPickerEnabled(true);
+                    internals.settings.setAllowsAirPlayForMediaPlayback(true);
+                }
+
+                consoleWrite('<br>** prompt() when disableRemotePlayback is set should fail');
+                run('video.disableRemotePlayback = true');
+                video.remote.prompt().then(failTest).catch(handleDisabledError);
+            }
+
+            function handleDisabledError(error)
+            {
+                consoleWrite(`EXPECTED ERROR: ${ error }`);
+                run('video.disableRemotePlayback = false');
+                consoleWrite('<br>** prompt() outside of a user action should fail');
+                video.remote.prompt().then(failTest).catch(handleUserGestureError);
+            }
+
+            function handleUserGestureError(error)
+            {
+                consoleWrite(`EXPECTED ERROR: ${ error }`);
+                consoleWrite('<br>** prompt() when no targets are available should fail');
+                runWithKeyDown(() => { video.remote.prompt().then(failTest).catch(handleNoTargetError); });
+            }
+
+            function handleNoTargetError(error)
+            {
+                consoleWrite(`EXPECTED ERROR: ${ error }`);
+                consoleWrite('<br>** Simulate a device becoming available')
+
+                video.remote.watchAvailability(availabilityChanged).catch(failTest);
+                if (window.internals)
+                    internals.setMockMediaPlaybackTargetPickerState('', 'DeviceAvailable');
+            }
+
+            function availabilityChanged(available)
+            {
+                if (!available)
+                    return;
+
+                if (video.src)
+                    return;
+
+                run('video.src = findMediaFile("video", "content/test")');
+                waitForEvent('canplaythrough', canplaythrough);
+            }
+
+            function canplaythrough()
+            {
+                consoleWrite('<br>** Simulate a device becoming selected');
+                runWithKeyDown(() => { video.remote.prompt().catch(failTest); });
+                if (window.internals)
+                    internals.setMockMediaPlaybackTargetPickerState('Sleepy TV', 'DeviceAvailable');
+                waitForEventOn(video.remote, 'connecting');
+                waitForEventOn(video.remote, 'connect', endTest);
+            }
+
+        </script>
+    </head>
+
+    <body onload='start()'>
+        <video controls></video>
+        <p>Test the operation of prompt().</p>
+    </body>
+</html>
diff --git a/LayoutTests/media/remoteplayback-target-availability-expected.txt b/LayoutTests/media/remoteplayback-target-availability-expected.txt
new file mode 100644 (file)
index 0000000..93a10aa
--- /dev/null
@@ -0,0 +1,17 @@
+
+Test that 'RemotePlaybackAvailabilityCallback' callback is called when playback devices come and go.
+
+
+** test initial callback when device is unavailable
+CALLBACK( available=false )
+
+** simulate device becoming available
+CALLBACK( available=true )
+
+** test initial callback when device is available
+CALLBACK( available=true )
+
+** simulate device becoming unavailable
+CALLBACK( available=false )
+END OF TEST
+
diff --git a/LayoutTests/media/remoteplayback-target-availability.html b/LayoutTests/media/remoteplayback-target-availability.html
new file mode 100644 (file)
index 0000000..ee4e2de
--- /dev/null
@@ -0,0 +1,71 @@
+<html>
+    <head>
+        <script src='media-file.js'></script>
+        <script src='video-test.js'></script>
+        <script>
+            var video;
+            var callbackId;
+
+            var nextStep;
+
+            function start()
+            {
+                findMediaElement();
+
+                if (window.internals) {
+                    internals.setMockMediaPlaybackTargetPickerEnabled(true);
+                    internals.settings.setAllowsAirPlayForMediaPlayback(true);
+                }
+
+                consoleWrite('<br>** test initial callback when device is unavailable');
+
+                video.remote.watchAvailability(step).then(setCallbackId).catch(failTest);
+                nextStep = initial;
+            }
+
+            function setCallbackId(id)
+            {
+                callbackId = id;
+            }
+
+            function step(available)
+            {
+                consoleWrite(`CALLBACK( available=${available} )`);
+
+                if (nextStep)
+                    nextStep(available);
+            }
+
+            function initial(available)
+            {
+                consoleWrite('<br>** simulate device becoming available');
+                if (window.internals)
+                    internals.setMockMediaPlaybackTargetPickerState('Sleepy TV', 'DeviceAvailable');
+                nextStep = haveTarget;
+            }
+
+            function haveTarget(available)
+            {
+                consoleWrite('<br>** test initial callback when device is available');
+
+                video.remote.cancelWatchAvailability(callbackId);
+                video.remote.watchAvailability(step).then(setCallbackId).catch(failTest);
+                nextStep = initialWithTarget;
+            }
+
+            function initialWithTarget(available)
+            {
+                consoleWrite('<br>** simulate device becoming unavailable');
+                if (window.internals)
+                    internals.setMockMediaPlaybackTargetPickerState('Sleepy TV', 'DeviceUnavailable');
+                nextStep = endTest;
+
+            }
+        </script>
+    </head>
+
+    <body onload='start()'>
+        <video controls></video>
+        <p>Test that 'RemotePlaybackAvailabilityCallback' callback is called when playback devices come and go.</p>
+    </body>
+</html>
diff --git a/LayoutTests/media/remoteplayback-watch-disableremoteplayback-expected.txt b/LayoutTests/media/remoteplayback-watch-disableremoteplayback-expected.txt
new file mode 100644 (file)
index 0000000..f810458
--- /dev/null
@@ -0,0 +1,14 @@
+
+Test that trying to watchAvailability on a video with the "disableremoteplayback" attribute set will generate an error.
+
+** create initial callback
+RUN(video.disableRemotePlayback = true)
+EXPECTED ERROR: InvalidStateError: The object is in an invalid state.
+** try again without disableRemotePlayback
+RUN(video.disableRemotePlayback = false)
+SUCCEEDED
+** set disableRemotePlayback and try to cancel callback
+RUN(video.disableRemotePlayback = true)
+EXPECTED ERROR: InvalidStateError: The object is in an invalid state.
+END OF TEST
+
diff --git a/LayoutTests/media/remoteplayback-watch-disableremoteplayback.html b/LayoutTests/media/remoteplayback-watch-disableremoteplayback.html
new file mode 100644 (file)
index 0000000..9bfc087
--- /dev/null
@@ -0,0 +1,52 @@
+<html>
+    <head>
+        <script src='media-file.js'></script>
+        <script src='video-test.js'></script>
+        <script>
+            var video;
+
+            function start()
+            {
+                findMediaElement();
+
+                if (window.internals) {
+                    internals.setMockMediaPlaybackTargetPickerEnabled(true);
+                    internals.settings.setAllowsAirPlayForMediaPlayback(true);
+                }
+
+                consoleWrite('** create initial callback');
+                run('video.disableRemotePlayback = true');
+                video.remote.watchAvailability(callback).then(failTest).catch(handleInitialError);
+            }
+
+            function callback(available) { };
+
+            function handleInitialError(exception)
+            {
+                consoleWrite(`EXPECTED ERROR: ${ exception }`);
+                consoleWrite('** try again without disableRemotePlayback');
+                run('video.disableRemotePlayback = false');
+                video.remote.watchAvailability(callback).then(succeeded).catch(failTest);
+            }
+
+            function succeeded(id)
+            {
+                consoleWrite('SUCCEEDED');
+                consoleWrite('** set disableRemotePlayback and try to cancel callback');
+                run('video.disableRemotePlayback = true');
+                video.remote.cancelWatchAvailability(id).then(failTest).catch(handleError);
+            }
+
+            function handleError(exception)
+            {
+                consoleWrite(`EXPECTED ERROR: ${ exception }`);
+                endTest();
+            }
+        </script>
+    </head>
+
+    <body onload='start()'>
+        <video controls></video>
+        <p>Test that trying to watchAvailability on a video with the "disableremoteplayback" attribute set will generate an error.</p>
+    </body>
+</html>
index dc41e8e..c40b443 100644 (file)
@@ -1362,3 +1362,6 @@ webkit.org/b/203371 fast/forms/contenteditable-font-optical-size.html [ Pass Fai
 # <rdar://problem/56714139> (r251242) [ iOS ]: fast/scrolling/ios/touch-scroll-visibility-hidden.html is a Flaky Timeout (203577)
 webkit.org/b/203577 fast/scrolling/ios/touch-scroll-visibility-hidden.html [ Pass Timeout ]
 
+# Timeout running prompt() because mock implementation is absent.
+webkit.org/b/203572 media/remoteplayback-prompt.html [ Skip ]
+webkit.org/b/203572 media/remoteplayback-target-availability.html [ Skip ]
index 8301e48..6bed242 100644 (file)
@@ -1,3 +1,14 @@
+2019-10-07  Jer Noble  <jer.noble@apple.com>
+
+        Implement the Remote Playback API.
+        https://bugs.webkit.org/show_bug.cgi?id=162971
+
+        Reviewed by Youenn Fablet.
+
+        Add RemotePlayback as a common identifier, needed for bindings due to "EnabledAtRuntime=RemotePlayback".
+
+        * runtime/CommonIdentifiers.h:
+
 2019-10-29  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Add fast path for String#localeCompare
index 3b47c90..07cc7ac 100644 (file)
@@ -49,6 +49,7 @@
     macro(Promise) \
     macro(Reflect) \
     macro(RegExp) \
+    macro(RemotePlayback) \
     macro(Set) \
     macro(SharedArrayBuffer) \
     macro(String) \
index 1e68802..2eff484 100644 (file)
@@ -1,3 +1,117 @@
+2019-10-07  Jer Noble  <jer.noble@apple.com>
+
+        Implement the Remote Playback API.
+        https://bugs.webkit.org/show_bug.cgi?id=162971
+
+        Reviewed by Youenn Fablet.
+
+        Tests: media/remoteplayback-cancel-invalid.html
+               media/remoteplayback-prompt.html
+               media/remoteplayback-target-availability.html
+               media/remoteplayback-watch-disableremoteplayback.html
+               imported/w3c/web-platform-tests/remote-playback/cancel-watch-availability.html
+               imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-cancel-watch-availability-throws.html
+               imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-prompt-throws.html
+               imported/w3c/web-platform-tests/remote-playback/disable-remote-playback-watch-availability-throws.html
+               imported/w3c/web-platform-tests/remote-playback/idlharness.window.html
+               imported/w3c/web-platform-tests/remote-playback/watch-availability-initial-callback.html
+
+        The Remote Playback API <https://w3c.github.io/remote-playback> allows page authors
+        to control the presentation of <audio> and <video> elements to remote playback devices
+        such as the Apple TV or Chromecast. WebKit already exposes a non-standardized API to
+        initiate remote playback, and the Remote Playback API is largely congruent with the
+        webkitShowPlaybackTargetPicker() API et. al.
+
+        One addition provided by the Remote Playback API is an event in the case the user
+        dismisses the picker UI without making a choice, so this concept needs to be plumbed
+        through from the ChromeClient to the PlatformMediaSession.
+
+        * DerivedSources.make:
+        * Modules/mediasession/WebMediaSessionManager.cpp:
+        (WebCore::WebMediaSessionManager::mockMediaPlaybackTargetPickerDismissPopup):
+        (WebCore::WebMediaSessionManager::playbackTargetPickerWasDismissed):
+        (WebCore::WebMediaSessionManager::configurePlaybackTargetClients):
+        * Modules/mediasession/WebMediaSessionManager.h:
+        * Modules/mediasession/WebMediaSessionManagerClient.h:
+        * Modules/remoteplayback/HTMLMediaElementRemotePlayback.h: Added.
+        (WebCore::HTMLMediaElementRemotePlayback::remote):
+        (WebCore::HTMLMediaElementRemotePlayback::hasAttributeWithoutSynchronization):
+        (WebCore::HTMLMediaElementRemotePlayback::setBooleanAttribute):
+        * Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl: Added.
+        * Modules/remoteplayback/RemotePlayback.cpp: Added.
+        (WebCore::RemotePlayback::create):
+        (WebCore::RemotePlayback::RemotePlayback):
+        (WebCore::RemotePlayback::~RemotePlayback):
+        (WebCore::RemotePlayback::watchAvailability):
+        (WebCore::RemotePlayback::cancelWatchAvailability):
+        (WebCore::RemotePlayback::prompt):
+        (WebCore::RemotePlayback::shouldPlayToRemoteTargetChanged):
+        (WebCore::RemotePlayback::setState):
+        (WebCore::RemotePlayback::establishConnection):
+        (WebCore::RemotePlayback::disconnect):
+        (WebCore::RemotePlayback::updateAvailability):
+        (WebCore::RemotePlayback::playbackTargetPickerWasDismissed):
+        (WebCore::RemotePlayback::isPlayingToRemoteTargetChanged):
+        (WebCore::RemotePlayback::hasAvailabilityCallbacks):
+        (WebCore::RemotePlayback::availabilityChanged):
+        (WebCore::RemotePlayback::invalidate):
+        (WebCore::RemotePlayback::activeDOMObjectName):
+        (WebCore::RemotePlayback::canSuspendForDocumentSuspension):
+        (WebCore::RemotePlayback::stop):
+        * Modules/remoteplayback/RemotePlayback.h: Added.
+        (WebCore::RemotePlayback::state):
+        * Modules/remoteplayback/RemotePlayback.idl: Added.
+        * Modules/remoteplayback/RemotePlaybackAvailabilityCallback.h: Added.
+        (WebCore::RemotePlaybackAvailabilityCallback::~RemotePlaybackAvailabilityCallback):
+        * Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl: Added.
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/generic/RuntimeEnabledFeatures.h:
+        (WebCore::RuntimeEnabledFeatures::setRemotePlaybackEnabled):
+        (WebCore::RuntimeEnabledFeatures::remotePlaybackEnabled):
+        * dom/Document.cpp:
+        (WebCore::Document::playbackTargetPickerWasDismissed):
+        * dom/Document.h:
+        * dom/EventNames.h:
+        * dom/EventTargetFactory.in:
+        * html/HTMLAttributeNames.in:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::HTMLMediaElement):
+        (WebCore::HTMLMediaElement::~HTMLMediaElement):
+        (WebCore::HTMLMediaElement::clearMediaPlayer):
+        (WebCore::HTMLMediaElement::wirelessRoutesAvailableDidChange):
+        (WebCore::HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged):
+        (WebCore::HTMLMediaElement::addEventListener):
+        (WebCore::HTMLMediaElement::removeEventListener):
+        (WebCore::HTMLMediaElement::setWirelessPlaybackTarget):
+        (WebCore::HTMLMediaElement::playbackTargetPickerWasDismissed):
+        (WebCore::HTMLMediaElement::remoteHasAvailabilityCallbacksChanged):
+        (WebCore::HTMLMediaElement::createMediaPlayer):
+        * html/HTMLMediaElement.h:
+        (WebCore::HTMLMediaElement::remote):
+        * html/MediaElementSession.cpp:
+        (WebCore::MediaElementSession::wirelessVideoPlaybackDisabled):
+        (WebCore::MediaElementSession::playbackTargetPickerWasDismissed):
+        * html/MediaElementSession.h:
+        * page/Page.cpp:
+        (WebCore::Page::mockMediaPlaybackTargetPickerDismissPopup):
+        (WebCore::Page::playbackTargetPickerWasDismissed):
+        * page/Page.h:
+        * page/Settings.in:
+        * platform/audio/PlatformMediaSession.h:
+        (WebCore::PlatformMediaSessionClient::playbackTargetPickerWasDismissed):
+        * platform/graphics/MediaPlaybackTargetClient.h:
+        * platform/graphics/MediaPlaybackTargetPicker.h:
+        (WebCore::MediaPlaybackTargetPicker::playbackTargetPickerWasDismissed):
+        * platform/graphics/avfoundation/objc/MediaPlaybackTargetPickerMac.mm:
+        (WebCore::MediaPlaybackTargetPickerMac::showPlaybackTargetPicker):
+        * platform/mock/MediaPlaybackTargetPickerMock.cpp:
+        (WebCore::MediaPlaybackTargetPickerMock::dismissPopup):
+        * platform/mock/MediaPlaybackTargetPickerMock.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::mockMediaPlaybackTargetPickerDismissPopup):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2019-10-28  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Add enterkeyhint support
index c28d074..b31a250 100644 (file)
@@ -251,6 +251,9 @@ $(PROJECT_DIR)/Modules/quota/StorageQuota.idl
 $(PROJECT_DIR)/Modules/quota/StorageQuotaCallback.idl
 $(PROJECT_DIR)/Modules/quota/StorageUsageCallback.idl
 $(PROJECT_DIR)/Modules/quota/WorkerNavigatorStorageQuota.idl
+$(PROJECT_DIR)/Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl
+$(PROJECT_DIR)/Modules/remoteplayback/RemotePlayback.idl
+$(PROJECT_DIR)/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl
 $(PROJECT_DIR)/Modules/speech/DOMWindowSpeechSynthesis.idl
 $(PROJECT_DIR)/Modules/speech/SpeechSynthesis.idl
 $(PROJECT_DIR)/Modules/speech/SpeechSynthesisEvent.idl
index 66e9fa7..50ce923 100644 (file)
@@ -785,6 +785,8 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElement.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElement.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementMediaSession.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementMediaSession.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementRemotePlayback.cpp
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementRemotePlayback.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMenuElement.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMenuElement.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMenuItemElement.cpp
@@ -1385,6 +1387,10 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSReadableStreamSource.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSReadableStreamSource.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRemoteDOMWindow.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRemoteDOMWindow.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRemotePlayback.cpp
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRemotePlayback.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRemotePlaybackAvailabilityCallback.cpp
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRemotePlaybackAvailabilityCallback.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRequestAnimationFrameCallback.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSRequestAnimationFrameCallback.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSResizeObserver.cpp
index 5d33d85..18b9802 100644 (file)
@@ -54,6 +54,7 @@ VPATH = \
     $(WebCore)/Modules/pictureinpicture \
     $(WebCore)/Modules/plugins \
     $(WebCore)/Modules/quota \
+       $(WebCore)/Modules/remoteplayback \
     $(WebCore)/Modules/speech \
     $(WebCore)/Modules/streams \
     $(WebCore)/Modules/webaudio \
@@ -321,6 +322,9 @@ JS_BINDING_IDLS = \
     $(WebCore)/Modules/quota/StorageQuotaCallback.idl \
     $(WebCore)/Modules/quota/StorageUsageCallback.idl \
     $(WebCore)/Modules/quota/WorkerNavigatorStorageQuota.idl \
+       $(WebCore)/Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl \
+       $(WebCore)/Modules/remoteplayback/RemotePlayback.idl \
+       $(WebCore)/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl \
     $(WebCore)/Modules/speech/DOMWindowSpeechSynthesis.idl \
     $(WebCore)/Modules/speech/SpeechSynthesis.idl \
     $(WebCore)/Modules/speech/SpeechSynthesisEvent.idl \
index b97434c..1a3e1c0 100644 (file)
@@ -112,6 +112,13 @@ void WebMediaSessionManager::setMockMediaPlaybackTargetPickerState(const String&
     mockPicker().setState(name, state);
 }
 
+void WebMediaSessionManager::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    LOG(Media, "WebMediaSessionManager::mockMediaPlaybackTargetPickerDismissPopup");
+
+    mockPicker().dismissPopup();
+}
+
 MediaPlaybackTargetPickerMock& WebMediaSessionManager::mockPicker()
 {
     if (!m_pickerOverride)
@@ -269,6 +276,12 @@ void WebMediaSessionManager::externalOutputDeviceAvailableDidChange(bool availab
         state->client.externalOutputDeviceAvailableDidChange(state->contextId, available);
 }
 
+void WebMediaSessionManager::playbackTargetPickerWasDismissed()
+{
+    m_playbackTargetPickerDismissed = true;
+    scheduleDelayedTask(TargetClientsConfigurationTask);
+}
+
 void WebMediaSessionManager::configureNewClients()
 {
     for (auto& state : m_clientState) {
@@ -299,7 +312,7 @@ void WebMediaSessionManager::configurePlaybackTargetClients()
 
         LOG(Media, "WebMediaSessionManager::configurePlaybackTargetClients %zu - client (%p + %llu) requestedPicker = %i, flags = %s", i, &state->client, state->contextId, state->requestedPicker, mediaProducerStateString(state->flags).utf8().data());
 
-        if (m_targetChanged && state->requestedPicker)
+        if ((m_targetChanged || m_playbackTargetPickerDismissed) && state->requestedPicker)
             indexOfClientThatRequestedPicker = i;
 
         if (indexOfClientWillPlayToTarget == notFound && flagsAreSet(state->flags, MediaProducer::IsPlayingToExternalDevice))
@@ -327,8 +340,11 @@ void WebMediaSessionManager::configurePlaybackTargetClients()
         if (i != indexOfClientWillPlayToTarget || !haveActiveRoute)
             state->client.setShouldPlayToPlaybackTarget(state->contextId, false);
 
+        if (state->requestedPicker && m_playbackTargetPickerDismissed)
+            state->client.playbackTargetPickerWasDismissed(state->contextId);
+
         state->configurationRequired = false;
-        if (m_targetChanged)
+        if ((m_targetChanged || m_playbackTargetPickerDismissed))
             state->requestedPicker = false;
     }
 
index 33019a5..1c93652 100644 (file)
@@ -49,6 +49,7 @@ public:
 
     WEBCORE_EXPORT void setMockMediaPlaybackTargetPickerEnabled(bool);
     WEBCORE_EXPORT void setMockMediaPlaybackTargetPickerState(const String&, MediaPlaybackTargetContext::State);
+    WEBCORE_EXPORT void mockMediaPlaybackTargetPickerDismissPopup();
 
     WEBCORE_EXPORT uint64_t addPlaybackTargetPickerClient(WebMediaSessionManagerClient&, uint64_t);
     WEBCORE_EXPORT void removePlaybackTargetPickerClient(WebMediaSessionManagerClient&, uint64_t);
@@ -71,6 +72,7 @@ private:
     // MediaPlaybackTargetPicker::Client
     void setPlaybackTarget(Ref<WebCore::MediaPlaybackTarget>&&) override;
     void externalOutputDeviceAvailableDidChange(bool) override;
+    void playbackTargetPickerWasDismissed() override;
 
     size_t find(WebMediaSessionManagerClient*, uint64_t);
 
@@ -104,6 +106,7 @@ private:
     Seconds m_currentWatchdogInterval;
     bool m_externalOutputDeviceAvailable { false };
     bool m_targetChanged { false };
+    bool m_playbackTargetPickerDismissed { false };
     bool m_mockPickerEnabled { false };
 };
 
index b78f97f..eb298ca 100644 (file)
@@ -40,6 +40,7 @@ public:
     virtual void setPlaybackTarget(uint64_t, Ref<MediaPlaybackTarget>&&) = 0;
     virtual void externalOutputDeviceAvailableDidChange(uint64_t, bool) = 0;
     virtual void setShouldPlayToPlaybackTarget(uint64_t, bool) = 0;
+    virtual void playbackTargetPickerWasDismissed(uint64_t) = 0;
 };
 
 } // namespace WebCore
diff --git a/Source/WebCore/Modules/remoteplayback/HTMLMediaElementRemotePlayback.h b/Source/WebCore/Modules/remoteplayback/HTMLMediaElementRemotePlayback.h
new file mode 100644 (file)
index 0000000..78919b3
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET) && ENABLE(VIDEO)
+
+namespace WebCore {
+
+class HTMLMediaElement;
+class RemotePlayback;
+
+class HTMLMediaElementRemotePlayback {
+public:
+    static RemotePlayback& remote(HTMLMediaElement& element) { return element.remote(); }
+    static bool hasAttributeWithoutSynchronization(HTMLMediaElement& element, const QualifiedName& name) { return element.hasAttributeWithoutSynchronization(name); }
+    static void setBooleanAttribute(HTMLMediaElement& element, const QualifiedName& name, bool value) { element.setBooleanAttribute(name, value); }
+};
+
+}
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET) && ENABLE(VIDEO)
diff --git a/Source/WebCore/Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl b/Source/WebCore/Modules/remoteplayback/HTMLMediaElementRemotePlayback.idl
new file mode 100644 (file)
index 0000000..59e91bb
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+[
+    Conditional=VIDEO&WIRELESS_PLAYBACK_TARGET,
+    EnabledBySetting=RemotePlayback,
+]
+partial interface HTMLMediaElement
+{
+    readonly attribute RemotePlayback remote;
+    [Reflect] attribute boolean disableRemotePlayback;
+};
diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp b/Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp
new file mode 100644 (file)
index 0000000..42e8473
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "RemotePlayback.h"
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+
+#include "Event.h"
+#include "EventNames.h"
+#include "HTMLMediaElement.h"
+#include "JSDOMPromiseDeferred.h"
+#include "Logging.h"
+#include "MediaElementSession.h"
+#include "MediaPlaybackTarget.h"
+#include "RemotePlaybackAvailabilityCallback.h"
+#include <wtf/IsoMallocInlines.h>
+
+namespace WebCore {
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(RemotePlayback);
+
+Ref<RemotePlayback> RemotePlayback::create(HTMLMediaElement& element)
+{
+    return adoptRef(*new RemotePlayback(element));
+}
+
+RemotePlayback::RemotePlayback(HTMLMediaElement& element)
+    : WebCore::ActiveDOMObject(element.scriptExecutionContext())
+    , m_mediaElement(makeWeakPtr(element))
+    , m_eventQueue(MainThreadGenericEventQueue::create(*this))
+{
+    suspendIfNeeded();
+}
+
+RemotePlayback::~RemotePlayback()
+{
+}
+
+void RemotePlayback::watchAvailability(Ref<RemotePlaybackAvailabilityCallback>&& callback, Ref<DeferredPromise>&& promise)
+{
+    // 6.2.1.3 Getting the remote playback devices availability information
+    // https://w3c.github.io/remote-playback/#monitoring-the-list-of-available-remote-playback-devices
+    // W3C Editor's Draft 15 July 2016
+
+    // 1. Let promise be a new promise->
+    // 2. Return promise, and run the following steps below:
+    
+    m_taskQueue.enqueueTask([this, callback = WTFMove(callback), promise = WTFMove(promise)] () mutable {
+        // 3. If the disableRemotePlayback attribute is present for the media element, reject the promise with
+        //    InvalidStateError and abort all the remaining steps.
+        if (!m_mediaElement
+            || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)
+            || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) {
+            WTFLogAlways("RemotePlayback::watchAvailability()::task - promise rejected");
+            promise->reject(InvalidStateError);
+            return;
+        }
+
+        // 4. If the user agent is unable to monitor the list of available remote playback devices for the entire
+        //    lifetime of the browsing context (for instance, because the user has disabled this feature), then run
+        //    the following steps in parallel:
+        // 5. If the user agent is unable to continuously monitor the list of available remote playback devices but
+        //    can do it for a short period of time when initiating remote playback, then:
+        // NOTE: Unimplemented; all current ports can support continuous device monitoring
+
+        // 6. Let callbackId be a number unique to the media element that will identify the callback.
+        int32_t callbackId = ++m_nextId;
+
+        // 7. Create a tuple (callbackId, callback) and add it to the set of availability callbacks for this media element.
+        ASSERT(!m_callbackMap.contains(callbackId));
+        m_callbackMap.add(callbackId, WTFMove(callback));
+
+        // 8. Fulfill promise with the callbackId and run the following steps in parallel:
+        promise->whenSettled([this, protectedThis = makeRefPtr(this), callbackId] {
+            // 8.1 Queue a task to invoke the callback with the current availability for the media element.
+            m_taskQueue.enqueueTask([this, callbackId] {
+                auto foundCallback = m_callbackMap.find(callbackId);
+                if (foundCallback == m_callbackMap.end())
+                    return;
+
+                if (updateAvailability() == UpdateResults::Unchanged)
+                    foundCallback->value->handleEvent(m_available);
+            });
+
+            // 8.2 Run the algorithm to monitor the list of available remote playback devices.
+            m_mediaElement->remoteHasAvailabilityCallbacksChanged();
+        });
+        promise->resolve<IDLLong>(callbackId);
+    });
+}
+
+void RemotePlayback::cancelWatchAvailability(Optional<int32_t> id, Ref<DeferredPromise>&& promise)
+{
+    // 6.2.1.5 Stop observing remote playback devices availability
+    // https://w3c.github.io/remote-playback/#stop-observing-remote-playback-devices-availability
+    // W3C Editor's Draft 15 July 2016
+
+    // 1. Let promise be a new promise->
+    // 2. Return promise, and run the following steps below:
+
+    m_taskQueue.enqueueTask([this, id = WTFMove(id), promise = WTFMove(promise)] {
+        // 3. If the disableRemotePlayback attribute is present for the media element, reject promise with
+        //    InvalidStateError and abort all the remaining steps.
+        if (!m_mediaElement
+            || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)
+            || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) {
+            promise->reject(InvalidStateError);
+            return;
+        }
+
+        // 4. If the parameter id is undefined, clear the set of availability callbacks.
+        if (!id)
+            m_callbackMap.clear();
+        else {
+            // 5. Otherwise, if id matches the callbackId for any entry in the set of availability callbacks,
+            //    remove the entry from the set.
+            if (auto it = m_callbackMap.find(id.value()) != m_callbackMap.end())
+                m_callbackMap.remove(it);
+            // 6. Otherwise, reject promise with NotFoundError and abort all the remaining steps.
+            else {
+                promise->reject(NotFoundError);
+                return;
+            }
+        }
+        // 7. If the set of availability callbacks is now empty and there is no pending request to initiate remote
+        //    playback, cancel any pending task to monitor the list of available remote playback devices for power
+        //    saving purposes.
+        m_mediaElement->remoteHasAvailabilityCallbacksChanged();
+
+        // 8. Fulfill promise.
+        promise->resolve();
+    });
+}
+
+void RemotePlayback::prompt(Ref<DeferredPromise>&& promise)
+{
+    // 6.2.2 Prompt user for changing remote playback statee
+    // https://w3c.github.io/remote-playback/#stop-observing-remote-playback-devices-availability
+    // W3C Editor's Draft 15 July 2016
+
+    // 1. Let promise be a new promise->
+    // 2. Return promise, and run the following steps below:
+
+    m_taskQueue.enqueueTask([this, promise = WTFMove(promise), processingUserGesture = UserGestureIndicator::processingUserGesture()] () mutable {
+        // 3. If the disableRemotePlayback attribute is present for the media element, reject the promise with
+        //    InvalidStateError and abort all the remaining steps.
+        if (!m_mediaElement
+            || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)
+            || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) {
+            promise->reject(InvalidStateError);
+            return;
+        }
+
+        // 4. If there is already an unsettled promise from a previous call to prompt for the same media element
+        //     or even for the same browsing context, the user agent may reject promise with an OperationError
+        //     exception and abort all remaining steps.
+        // NOTE: consider implementing
+
+        // 5. OPTIONALLY, if the user agent knows a priori that showing the UI for this particular media element
+        //    is not feasible, reject promise with a NotSupportedError and abort all remaining steps.
+#if !PLATFORM(IOS)
+        if (m_mediaElement->readyState() < HTMLMediaElementEnums::HAVE_METADATA) {
+            promise->reject(NotSupportedError);
+            return;
+        }
+#endif
+
+        // 6. If the algorithm isn't allowed to show a popup, reject promise with an InvalidAccessError exception
+        //    and abort these steps.
+        if (!processingUserGesture) {
+            promise->reject(InvalidAccessError);
+            return;
+        }
+
+        // 7. If the user agent needs to show the list of available remote playback devices and is not monitoring
+        //    the list of available remote playback devices, run the steps to monitor the list of available remote
+        //    playback devices in parallel.
+        // NOTE: Monitoring enabled by adding to m_promptPromises and calling remoteHasAvailabilityCallbacksChanged().
+        //       Meanwhile, just update availability for step 9.
+        updateAvailability();
+
+        // 8. If the list of available remote playback devices is empty and will remain so before the request for
+        //    user permission is completed, reject promise with a NotFoundError exception and abort all remaining steps.
+        // NOTE: consider implementing (no network?)
+
+        // 9. If the state is disconnected and availability for the media element is false, reject promise with a
+        //    NotSupportedError exception and abort all remaining steps.
+        if (m_state == State::Disconnected && !m_available) {
+            promise->reject(NotSupportedError);
+            return;
+        }
+
+        m_promptPromises.append(WTFMove(promise));
+        m_mediaElement->remoteHasAvailabilityCallbacksChanged();
+        m_mediaElement->webkitShowPlaybackTargetPicker();
+
+        // NOTE: Steps 10-12 are implemented in the following methods:
+    });
+}
+
+void RemotePlayback::shouldPlayToRemoteTargetChanged(bool shouldPlayToRemoteTarget)
+{
+    // 6.2.2 Prompt user for changing remote playback state [Ctd]
+    // https://w3c.github.io/remote-playback/#prompt-user-for-changing-remote-playback-statee
+    // W3C Editor's Draft 15 July 2016
+
+    LOG(Media, "RemotePlayback::shouldPlayToRemoteTargetChanged(%p), shouldPlay(%d), promise count(%lu)", this, shouldPlayToRemoteTarget, m_promptPromises.size());
+
+    // 10. If the user picked a remote playback device device to initiate remote playback with, the user agent
+    //     must run the following steps:
+    if (shouldPlayToRemoteTarget) {
+        // 10.1 Set the state of the remote object to connecting.
+        // 10.3 Queue a task to fire a simple event with the name connecting at the remote property of the media element.
+        //      The event must not bubble, must not be cancelable, and has no default action.
+        setState(State::Connecting);
+    }
+
+    for (auto& promise : std::exchange(m_promptPromises, { })) {
+        // 10.2 Fulfill promise.
+        // 10.4 Establish a connection with the remote playback device device for the media element.
+        // NOTE: Implemented in establishConnection().
+
+        // 11. Otherwise, if the user chose to disconnect from the remote playback device device, the user agent
+        //     must run the following steps:
+        // 11.1. Fulfill promise.
+        // 11.2. Run the disconnect from remote playback device algorithm for the device.
+        // NOTE: Implemented in disconnect().
+
+        promise->resolve();
+    }
+
+    if (shouldPlayToRemoteTarget)
+        establishConnection();
+    else
+        disconnect();
+
+    m_mediaElement->remoteHasAvailabilityCallbacksChanged();
+}
+
+void RemotePlayback::setState(State state)
+{
+    if (m_state == state)
+        return;
+
+    m_state = state;
+
+    switch (m_state) {
+    case State::Connected:
+        m_eventQueue->enqueueEvent(Event::create(eventNames().connectEvent, Event::CanBubble::No, Event::IsCancelable::No));
+        break;
+    case State::Connecting:
+        m_eventQueue->enqueueEvent(Event::create(eventNames().connectingEvent, Event::CanBubble::No, Event::IsCancelable::No));
+        break;
+    case State::Disconnected:
+        m_eventQueue->enqueueEvent(Event::create(eventNames().disconnectEvent, Event::CanBubble::No, Event::IsCancelable::No));
+        break;
+    }
+}
+
+void RemotePlayback::establishConnection()
+{
+    // 6.2.4 Establishing a connection with a remote playback device
+    // https://w3c.github.io/remote-playback/#establishing-a-connection-with-a-remote-playback-device
+    // W3C Editor's Draft 15 July 2016
+
+    // 1. If the state of remote is not equal to connecting, abort all the remaining steps.
+    if (m_state != State::Connecting)
+        return;
+
+    // 2. Request connection of remote to device. The implementation of this step is specific to the user agent.
+    // NOTE: Handled in MediaPlayer.
+
+    // NOTE: Continued in isPlayingToRemoteTargetChanged()
+}
+
+void RemotePlayback::disconnect()
+{
+    // 6.2.6 Disconnecting from remote playback device
+    // https://w3c.github.io/remote-playback/#dfn-disconnect-from-remote-playback-device
+    // W3C Editor's Draft 15 July 2016
+
+    // 1. If the state of remote is disconnected, abort all remaining steps.
+    if (m_state == State::Disconnected)
+        return;
+
+    // 2. Queue a task to run the following steps:
+    m_taskQueue.enqueueTask([this] {
+        // 2.1 Request disconnection of remote from the device. Implementation is user agent specific.
+        // NOTE: Implemented by MediaPlayer::setWirelessPlaybackTarget()
+        // 2.2 Change the remote's state to disconnected.
+        // 2.3 Fire an event with the name disconnect at remote.
+        setState(State::Disconnected);
+
+        // 2.4 Synchronize the current media element state with the local playback state. Implementation is
+        //     specific to user agent.
+        // NOTE: Handled by the MediaPlayer
+    });
+}
+
+RemotePlayback::UpdateResults RemotePlayback::updateAvailability()
+{
+    bool available = m_mediaElement ? m_mediaElement->mediaSession().hasWirelessPlaybackTargets() : false;
+    if (available == m_available)
+        return UpdateResults::Unchanged;
+
+    availabilityChanged(available);
+    return UpdateResults::Changed;
+}
+
+void RemotePlayback::playbackTargetPickerWasDismissed()
+{
+    // 6.2.2 Prompt user for changing remote playback state [Ctd]
+    // https://w3c.github.io/remote-playback/#stop-observing-remote-playback-devices-availability
+    // W3C Editor's Draft 15 July 2016
+
+    // 12. Otherwise, the user is considered to deny permission to use the device, so reject promise with NotAllowedError
+    // exception and hide the UI shown by the user agent
+    ASSERT(!m_promptPromises.isEmpty());
+
+    for (auto& promise : std::exchange(m_promptPromises, { }))
+        promise->reject(NotAllowedError);
+    m_mediaElement->remoteHasAvailabilityCallbacksChanged();
+}
+
+void RemotePlayback::isPlayingToRemoteTargetChanged(bool isPlayingToTarget)
+{
+    // 6.2.4 Establishing a connection with a remote playback device [Ctd]
+    // https://w3c.github.io/remote-playback/#establishing-a-connection-with-a-remote-playback-device
+    // W3C Editor's Draft 15 July 2016
+
+    // 3. If connection completes successfully, queue a task to run the following steps:
+    if (isPlayingToTarget) {
+        // 3.1. Set the state of remote to connected.
+        // 3.2. Fire a simple event named connect at remote.
+        setState(State::Connected);
+
+        // 3.3 Synchronize the current media element state with the remote playback state. Implementation is
+        //     specific to user agent.
+        // NOTE: Implemented by MediaPlayer.
+        return;
+    }
+
+    // 4. If connection fails, queue a task to run the following steps:
+    // 4.1. Set the remote playback state of remote to disconnected.
+    // 4.2. Fire a simple event named disconnect at remote.
+    setState(State::Disconnected);
+}
+
+bool RemotePlayback::hasAvailabilityCallbacks() const
+{
+    return !m_callbackMap.isEmpty() || !m_promptPromises.isEmpty();
+}
+
+void RemotePlayback::availabilityChanged(bool available)
+{
+    if (available == m_available)
+        return;
+    m_available = available;
+
+    m_taskQueue.enqueueTask([this] {
+        // Protect m_callbackMap against mutation while it's being iterated over.
+        Vector<Ref<RemotePlaybackAvailabilityCallback>> callbacks;
+        callbacks.reserveInitialCapacity(m_callbackMap.size());
+
+        // Can't use copyValuesToVector() here because Ref<> has a deleted assignment operator.
+        for (auto& callback : m_callbackMap.values())
+            callbacks.uncheckedAppend(callback.copyRef());
+        for (auto& callback : callbacks)
+            callback->handleEvent(m_available);
+    });
+}
+
+void RemotePlayback::invalidate()
+{
+    m_mediaElement = nullptr;
+}
+
+const char* RemotePlayback::activeDOMObjectName() const
+{
+    return "RemotePlayback";
+}
+
+void RemotePlayback::stop()
+{
+    m_taskQueue.close();
+    m_eventQueue->close();
+}
+
+}
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlayback.h b/Source/WebCore/Modules/remoteplayback/RemotePlayback.h
new file mode 100644 (file)
index 0000000..285c946
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+
+#include "ActiveDOMObject.h"
+#include "EventTarget.h"
+#include "GenericEventQueue.h"
+#include <wtf/HashMap.h>
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class DeferredPromise;
+class HTMLMediaElement;
+class MediaPlaybackTarget;
+class RemotePlaybackAvailabilityCallback;
+
+class RemotePlayback final : public RefCounted<RemotePlayback>, public ActiveDOMObject, public EventTargetWithInlineData {
+    WTF_MAKE_ISO_ALLOCATED(RemotePlayback);
+public:
+    static Ref<RemotePlayback> create(HTMLMediaElement&);
+    ~RemotePlayback();
+
+    void watchAvailability(Ref<RemotePlaybackAvailabilityCallback>&&, Ref<DeferredPromise>&&);
+    void cancelWatchAvailability(Optional<int32_t> id, Ref<DeferredPromise>&&);
+    void prompt(Ref<DeferredPromise>&&);
+
+    bool hasAvailabilityCallbacks() const;
+    void availabilityChanged(bool);
+    void playbackTargetPickerWasDismissed();
+    void shouldPlayToRemoteTargetChanged(bool);
+    void isPlayingToRemoteTargetChanged(bool);
+
+    enum class State {
+        Connecting,
+        Connected,
+        Disconnected,
+    };
+    State state() const { return m_state; }
+
+    void invalidate();
+
+    using RefCounted::ref;
+    using RefCounted::deref;
+
+private:
+    explicit RemotePlayback(HTMLMediaElement&);
+
+    void refEventTarget() final { ref(); }
+    void derefEventTarget() final { deref(); }
+
+    void setState(State);
+    void establishConnection();
+    void disconnect();
+
+    enum class UpdateResults {
+        Unchanged,
+        Changed,
+    };
+    UpdateResults updateAvailability();
+
+    // ActiveDOMObject.
+    const char* activeDOMObjectName() const final;
+    void stop() final;
+
+    // EventTargetWithInlineData.
+    EventTargetInterface eventTargetInterface() const final { return RemotePlaybackEventTargetInterfaceType; }
+    ScriptExecutionContext* scriptExecutionContext() const final { return ActiveDOMObject::scriptExecutionContext(); }
+
+    WeakPtr<HTMLMediaElement> m_mediaElement;
+    uint32_t m_nextId { 0 };
+
+    using CallbackMap = HashMap<int32_t, Ref<RemotePlaybackAvailabilityCallback>>;
+    CallbackMap m_callbackMap;
+
+    using PromiseVector = Vector<Ref<DeferredPromise>>;
+    PromiseVector m_availabilityPromises;
+    PromiseVector m_cancelAvailabilityPromises;
+    PromiseVector m_promptPromises;
+    State m_state { State::Disconnected };
+    bool m_available { false };
+
+    UniqueRef<MainThreadGenericEventQueue> m_eventQueue;
+    GenericTaskQueue<Timer> m_taskQueue;
+};
+
+}
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
+
diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlayback.idl b/Source/WebCore/Modules/remoteplayback/RemotePlayback.idl
new file mode 100644 (file)
index 0000000..f3b544a
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+enum RemotePlaybackState {
+    "connecting",
+    "connected",
+    "disconnected"
+};
+
+[
+    ActiveDOMObject,
+    Conditional=WIRELESS_PLAYBACK_TARGET,
+    EnabledBySetting=RemotePlayback,
+]
+interface RemotePlayback : EventTarget {
+    readonly attribute RemotePlaybackState state;
+
+    attribute EventHandler onconnecting;
+    attribute EventHandler onconnect;
+    attribute EventHandler ondisconnect;
+
+    Promise<long> watchAvailability(RemotePlaybackAvailabilityCallback callback);
+    Promise<void> cancelWatchAvailability(optional long id);
+    Promise<void> prompt();
+};
diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.h b/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.h
new file mode 100644 (file)
index 0000000..636698c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+
+#include "ActiveDOMCallback.h"
+#include "CallbackResult.h"
+#include <wtf/Forward.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class RemotePlaybackAvailabilityCallback : public RefCounted<RemotePlaybackAvailabilityCallback>, public ActiveDOMCallback {
+public:
+    using ActiveDOMCallback::ActiveDOMCallback;
+
+    virtual CallbackResult<bool> handleEvent(bool) = 0;
+};
+
+}
+
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl b/Source/WebCore/Modules/remoteplayback/RemotePlaybackAvailabilityCallback.idl
new file mode 100644 (file)
index 0000000..25e9cee
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+[
+    Conditional=WIRELESS_PLAYBACK_TARGET,
+    EnabledBySetting=RemotePlayback,
+] callback RemotePlaybackAvailabilityCallback = boolean (boolean available);
index e8a74e9..4e209fe 100644 (file)
@@ -209,6 +209,8 @@ Modules/pictureinpicture/EnterPictureInPictureEvent.cpp
 Modules/pictureinpicture/HTMLVideoElementPictureInPicture.cpp
 Modules/pictureinpicture/PictureInPictureWindow.cpp
 
+Modules/remoteplayback/RemotePlayback.cpp
+
 Modules/speech/DOMWindowSpeechSynthesis.cpp
 Modules/speech/SpeechSynthesis.cpp
 Modules/speech/SpeechSynthesisEvent.cpp
@@ -3622,3 +3624,10 @@ JSUserMessageHandlersNamespace.cpp
 JSWebKitNamespace.cpp
 
 #endif
+
+#if ENABLE_WIRELESS_PLAYBACK_TARGET
+
+JSRemotePlayback.cpp
+JSRemotePlaybackAvailabilityCallback.cpp
+
+#endif
index 74cf3b1..3994c5f 100644 (file)
                CDC224281F756966005F077B /* CDMInstanceFairPlayStreamingAVFObjC.mm in Sources */ = {isa = PBXBuildFile; fileRef = CD78A2EC1F75648600DE371B /* CDMInstanceFairPlayStreamingAVFObjC.mm */; };
                CDC26B40160A8CC60026757B /* LegacyMockCDM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDC26B3C160A62B00026757B /* LegacyMockCDM.cpp */; };
                CDC26B41160A8CCE0026757B /* LegacyMockCDM.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC26B3D160A62B00026757B /* LegacyMockCDM.h */; };
+               CDC312E922FCD7C0001204EC /* RemotePlayback.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC312E122FCD0B0001204EC /* RemotePlayback.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               CDC312EA22FCD7C6001204EC /* HTMLMediaElementRemotePlayback.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC312E522FCD0B0001204EC /* HTMLMediaElementRemotePlayback.h */; };
+               CDC312EB22FCD7C9001204EC /* RemotePlaybackAvailabilityCallback.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC312E722FCD0B0001204EC /* RemotePlaybackAvailabilityCallback.h */; };
                CDC48AC92149CF2A0024FD59 /* MediaEngineConfigurationFactoryCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC48AC72149CF2A0024FD59 /* MediaEngineConfigurationFactoryCocoa.h */; };
                CDC675221EAEA9B700727C84 /* AVAudioSessionCaptureDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDC675201EAEA9B700727C84 /* AVAudioSessionCaptureDeviceManager.mm */; };
                CDC675231EAEA9B700727C84 /* AVAudioSessionCaptureDeviceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC675211EAEA9B700727C84 /* AVAudioSessionCaptureDeviceManager.h */; };
                CD8B5A48180E138B008B8E65 /* TextTrackMediaSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextTrackMediaSource.h; sourceTree = "<group>"; };
                CD8B5A4A180E17A3008B8E65 /* AudioTrackMediaSource.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = AudioTrackMediaSource.idl; sourceTree = "<group>"; };
                CD8B5A4B180E17C0008B8E65 /* AudioTrackMediaSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioTrackMediaSource.h; sourceTree = "<group>"; };
+               CD8C6C2622FDDA8F00A720AB /* JSRemotePlayback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSRemotePlayback.cpp; sourceTree = "<group>"; };
+               CD8C6C2722FDDA9000A720AB /* JSRemotePlaybackAvailabilityCallback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSRemotePlaybackAvailabilityCallback.cpp; sourceTree = "<group>"; };
+               CD8C6C2822FDDA9000A720AB /* JSRemotePlaybackAvailabilityCallback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSRemotePlaybackAvailabilityCallback.h; sourceTree = "<group>"; };
+               CD8C6C2922FDDA9100A720AB /* JSRemotePlayback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSRemotePlayback.h; sourceTree = "<group>"; };
                CD92F50F2260FFEE00F87BB3 /* DocumentFullscreen.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = DocumentFullscreen.idl; sourceTree = "<group>"; };
                CD92F5102260FFEE00F87BB3 /* DocumentFullscreen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DocumentFullscreen.h; sourceTree = "<group>"; };
                CD92F5162261038200F87BB3 /* FullscreenManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FullscreenManager.h; sourceTree = "<group>"; };
                CDC1DD4117CC2C48008CB55D /* mediaControlsApple.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = mediaControlsApple.css; sourceTree = "<group>"; };
                CDC26B3C160A62B00026757B /* LegacyMockCDM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LegacyMockCDM.cpp; sourceTree = "<group>"; };
                CDC26B3D160A62B00026757B /* LegacyMockCDM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LegacyMockCDM.h; sourceTree = "<group>"; };
+               CDC312E122FCD0B0001204EC /* RemotePlayback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemotePlayback.h; sourceTree = "<group>"; };
+               CDC312E222FCD0B0001204EC /* HTMLMediaElementRemotePlayback.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = HTMLMediaElementRemotePlayback.idl; sourceTree = "<group>"; };
+               CDC312E322FCD0B0001204EC /* RemotePlayback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = RemotePlayback.cpp; sourceTree = "<group>"; };
+               CDC312E422FCD0B0001204EC /* RemotePlaybackAvailabilityCallback.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = RemotePlaybackAvailabilityCallback.idl; sourceTree = "<group>"; };
+               CDC312E522FCD0B0001204EC /* HTMLMediaElementRemotePlayback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLMediaElementRemotePlayback.h; sourceTree = "<group>"; };
+               CDC312E622FCD0B0001204EC /* RemotePlayback.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = RemotePlayback.idl; sourceTree = "<group>"; };
+               CDC312E722FCD0B0001204EC /* RemotePlaybackAvailabilityCallback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemotePlaybackAvailabilityCallback.h; sourceTree = "<group>"; };
                CDC48AC72149CF2A0024FD59 /* MediaEngineConfigurationFactoryCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaEngineConfigurationFactoryCocoa.h; sourceTree = "<group>"; };
                CDC48AC82149CF2A0024FD59 /* MediaEngineConfigurationFactoryCocoa.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaEngineConfigurationFactoryCocoa.cpp; sourceTree = "<group>"; };
                CDC675201EAEA9B700727C84 /* AVAudioSessionCaptureDeviceManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AVAudioSessionCaptureDeviceManager.mm; sourceTree = "<group>"; };
                                A9D247F90D757E4100FDF959 /* Plugins */,
                                89F60B17157F6A020075E157 /* Quota */,
                                BC9854460CD3DA5F00069BC1 /* Ranges */,
+                               CD8C6C2422FDDA6400A720AB /* RemotePlayback */,
                                5182C24C1F3142090059BA7C /* ServiceWorkers */,
                                AA7FEE9B16A491A1004C0C33 /* Speech */,
                                A83B79150CCB0078000B0825 /* Storage */,
                                A1F76B0E1F44C0CF0014C318 /* paymentrequest */,
                                1DEF06A2233C32DB00EE228D /* pictureinpicture */,
                                072AE1DE183C0513000A5988 /* plugins */,
+                               CDC312E022FCD0B0001204EC /* remoteplayback */,
                                89F60B08157F68350075E157 /* quota */,
                                AA2A5AB716A485A400975A25 /* speech */,
                                41A023EA1A39DB7900F722CF /* streams */,
                        path = iso;
                        sourceTree = "<group>";
                };
+               CD8C6C2422FDDA6400A720AB /* RemotePlayback */ = {
+                       isa = PBXGroup;
+                       children = (
+                               CD8C6C2622FDDA8F00A720AB /* JSRemotePlayback.cpp */,
+                               CD8C6C2922FDDA9100A720AB /* JSRemotePlayback.h */,
+                               CD8C6C2722FDDA9000A720AB /* JSRemotePlaybackAvailabilityCallback.cpp */,
+                               CD8C6C2822FDDA9000A720AB /* JSRemotePlaybackAvailabilityCallback.h */,
+                       );
+                       name = RemotePlayback;
+                       sourceTree = "<group>";
+               };
                CD94A5CB1F71CB4600F525C5 /* encryptedmedia */ = {
                        isa = PBXGroup;
                        children = (
                        path = mediacapabilities;
                        sourceTree = "<group>";
                };
+               CDC312E022FCD0B0001204EC /* remoteplayback */ = {
+                       isa = PBXGroup;
+                       children = (
+                               CDC312E522FCD0B0001204EC /* HTMLMediaElementRemotePlayback.h */,
+                               CDC312E222FCD0B0001204EC /* HTMLMediaElementRemotePlayback.idl */,
+                               CDC312E322FCD0B0001204EC /* RemotePlayback.cpp */,
+                               CDC312E122FCD0B0001204EC /* RemotePlayback.h */,
+                               CDC312E622FCD0B0001204EC /* RemotePlayback.idl */,
+                               CDC312E722FCD0B0001204EC /* RemotePlaybackAvailabilityCallback.h */,
+                               CDC312E422FCD0B0001204EC /* RemotePlaybackAvailabilityCallback.idl */,
+                       );
+                       path = remoteplayback;
+                       sourceTree = "<group>";
+               };
                CDC6751F1EAEA99600727C84 /* ios */ = {
                        isa = PBXGroup;
                        children = (
                                E1C415DA0F655D6F0092D2FB /* CrossOriginPreflightResultCache.h in Headers */,
                                6B1F48112298A37E00DE8B82 /* CrossSiteNavigationDataTransfer.h in Headers */,
                                E169803D1133542D00894115 /* CRuntimeObject.h in Headers */,
+                               CDC312E922FCD7C0001204EC /* RemotePlayback.h in Headers */,
                                975CA28B130365F800E99AD9 /* Crypto.h in Headers */,
                                7C9ACA981F3CCDAD00F3AA09 /* CryptoAesKeyAlgorithm.h in Headers */,
                                E172AF6E180F24C600FBADB9 /* CryptoAlgorithm.h in Headers */,
                                A8EA79FA0A1916DF00A8EF5F /* HTMLDirectoryElement.h in Headers */,
                                A8EA7CB70A192B9C00A8EF5F /* HTMLDivElement.h in Headers */,
                                A8EA79F70A1916DF00A8EF5F /* HTMLDListElement.h in Headers */,
+                               CDC312EB22FCD7C9001204EC /* RemotePlaybackAvailabilityCallback.h in Headers */,
                                93F198E508245E59001E9ABC /* HTMLDocument.h in Headers */,
                                977B3867122883E900B81FF8 /* HTMLDocumentParser.h in Headers */,
                                93F198E608245E59001E9ABC /* HTMLElement.h in Headers */,
                                FD31603612B0267600C1A359 /* ScriptProcessorNode.h in Headers */,
                                8A413AE01207BBA50082016E /* ScriptRunner.h in Headers */,
                                934CC10A0EDB223900A658F2 /* ScriptSourceCode.h in Headers */,
+                               CDC312EA22FCD7C6001204EC /* HTMLMediaElementRemotePlayback.h in Headers */,
                                41C760B10EDE03D300C1655F /* ScriptState.h in Headers */,
                                228C284510D82500009D0D0E /* ScriptWrappable.h in Headers */,
                                1400D7A817136EA70077CE05 /* ScriptWrappableInlines.h in Headers */,
index 7712d21..b007dd7 100644 (file)
@@ -204,6 +204,7 @@ namespace WebCore {
     macro(ReadableStreamBYOBRequest) \
     macro(ReadableStreamDefaultController) \
     macro(ReadableStreamDefaultReader) \
+    macro(RemotePlayback) \
     macro(Request) \
     macro(Response) \
     macro(ScreenLuminance) \
index 371ea8e..a46b8db 100644 (file)
@@ -7196,6 +7196,14 @@ void Document::setShouldPlayToPlaybackTarget(uint64_t clientId, bool shouldPlay)
     it->value->setShouldPlayToPlaybackTarget(shouldPlay);
 }
 
+void Document::playbackTargetPickerWasDismissed(uint64_t clientId)
+{
+    auto it = m_idToClientMap.find(clientId);
+    if (it == m_idToClientMap.end())
+        return;
+
+    it->value->playbackTargetPickerWasDismissed();
+}
 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
 
 #if ENABLE(MEDIA_SESSION)
index 6bfdfd3..b961518 100644 (file)
@@ -1376,6 +1376,7 @@ public:
     void setPlaybackTarget(uint64_t, Ref<MediaPlaybackTarget>&&);
     void playbackTargetAvailabilityDidChange(uint64_t, bool);
     void setShouldPlayToPlaybackTarget(uint64_t, bool);
+    void playbackTargetPickerWasDismissed(uint64_t);
 #endif
 
     ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicyToPropagate() const;
index 73c4f1e..d4960b9 100644 (file)
@@ -107,6 +107,7 @@ namespace WebCore {
     macro(devicemotion) \
     macro(deviceorientation) \
     macro(dischargingtimechange) \
+    macro(disconnect) \
     macro(downloading) \
     macro(drag) \
     macro(dragend) \
index 60508cf..d49b1b0 100644 (file)
@@ -34,6 +34,7 @@ PaymentRequest conditional=PAYMENT_REQUEST
 PaymentResponse conditional=PAYMENT_REQUEST
 Performance
 PictureInPictureWindow conditional=PICTURE_IN_PICTURE_API
+RemotePlayback conditional=WIRELESS_PLAYBACK_TARGET
 RTCDataChannel conditional=WEB_RTC
 RTCDTMFSender conditional=WEB_RTC
 RTCPeerConnection conditional=WEB_RTC
index 5759644..dcd8378 100644 (file)
@@ -117,6 +117,7 @@ direction
 dirname
 disabled
 disablepictureinpicture
+disableremoteplayback
 disposition
 download
 draggable
index 65fce46..fcc9ca9 100644 (file)
 #endif
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
+#include "RemotePlayback.h"
 #include "WebKitPlaybackTargetAvailabilityEvent.h"
 #endif
 
@@ -465,6 +466,9 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
     , m_haveVisibleTextTrack(false)
     , m_processingPreferenceChange(false)
 #endif
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+    , m_remote(RemotePlayback::create(*this))
+#endif
 #if !RELEASE_LOG_DISABLED
     , m_logger(&document.logger())
     , m_logIdentifier(uniqueLogIdentifier())
@@ -591,7 +595,7 @@ HTMLMediaElement::~HTMLMediaElement()
 #endif
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
-    if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
+    if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent) || m_remote->hasAvailabilityCallbacks()) {
         m_hasPlaybackTargetAvailabilityListeners = false;
         m_mediaSession->setHasPlaybackTargetAvailabilityListeners(false);
         updateMediaState();
@@ -5633,7 +5637,7 @@ void HTMLMediaElement::clearMediaPlayer()
 #endif
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
-    if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
+    if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent) || m_remote->hasAvailabilityCallbacks()) {
         m_hasPlaybackTargetAvailabilityListeners = false;
         m_mediaSession->setHasPlaybackTargetAvailabilityListeners(false);
 
@@ -5872,6 +5876,9 @@ void HTMLMediaElement::webkitShowPlaybackTargetPicker()
 
 void HTMLMediaElement::wirelessRoutesAvailableDidChange()
 {
+    bool hasTargets = m_mediaSession->hasWirelessPlaybackTargets();
+    m_remote->availabilityChanged(hasTargets);
+
     enqueuePlaybackTargetAvailabilityChangedEvent();
 }
 
@@ -5886,7 +5893,7 @@ void HTMLMediaElement::setIsPlayingToWirelessTarget(bool isPlayingToWirelessTarg
         if (isPlayingToWirelessTarget == m_isPlayingToWirelessTarget)
             return;
         m_isPlayingToWirelessTarget = m_player && m_player->isCurrentPlaybackTargetWireless();
-
+        m_remote->isPlayingToRemoteTargetChanged(m_isPlayingToWirelessTarget);
         ALWAYS_LOG(LOGIDENTIFIER, m_isPlayingToWirelessTarget);
         configureMediaControls();
         m_mediaSession->isPlayingToWirelessPlaybackTargetChanged(m_isPlayingToWirelessTarget);
@@ -5916,7 +5923,8 @@ bool HTMLMediaElement::addEventListener(const AtomString& eventType, Ref<EventLi
     if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
         return Node::addEventListener(eventType, WTFMove(listener), options);
 
-    bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
+    bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent) && !m_remote->hasAvailabilityCallbacks();
+
     if (!Node::addEventListener(eventType, WTFMove(listener), options))
         return false;
 
@@ -5939,7 +5947,7 @@ bool HTMLMediaElement::removeEventListener(const AtomString& eventType, EventLis
     if (!Node::removeEventListener(eventType, listener, options))
         return false;
 
-    bool didRemoveLastAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
+    bool didRemoveLastAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent) && !m_remote->hasAvailabilityCallbacks();
     INFO_LOG(LOGIDENTIFIER, "removed last listener = ", didRemoveLastAvailabilityChangedListener);
     if (didRemoveLastAvailabilityChangedListener) {
         m_hasPlaybackTargetAvailabilityListeners = false;
@@ -5963,8 +5971,11 @@ void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent()
 void HTMLMediaElement::setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
 {
     ALWAYS_LOG(LOGIDENTIFIER);
+    bool hasActiveRoute = device->hasActiveRoute();
     if (m_player)
         m_player->setWirelessPlaybackTarget(WTFMove(device));
+
+    m_remote->shouldPlayToRemoteTargetChanged(hasActiveRoute);
 }
 
 void HTMLMediaElement::setShouldPlayToPlaybackTarget(bool shouldPlay)
@@ -5975,6 +5986,22 @@ void HTMLMediaElement::setShouldPlayToPlaybackTarget(bool shouldPlay)
         m_player->setShouldPlayToPlaybackTarget(shouldPlay);
 }
 
+void HTMLMediaElement::playbackTargetPickerWasDismissed()
+{
+    m_remote->playbackTargetPickerWasDismissed();
+}
+
+void HTMLMediaElement::remoteHasAvailabilityCallbacksChanged()
+{
+    bool hasListeners = hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent) || m_remote->hasAvailabilityCallbacks();
+    if (m_hasPlaybackTargetAvailabilityListeners == hasListeners)
+        return;
+
+    INFO_LOG(LOGIDENTIFIER, "hasListeners: ", hasListeners);
+    m_hasPlaybackTargetAvailabilityListeners = hasListeners;
+    m_mediaSession->setHasPlaybackTargetAvailabilityListeners(hasListeners);
+    scheduleUpdateMediaState();
+}
 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
 
 bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const
@@ -6706,7 +6733,7 @@ void HTMLMediaElement::createMediaPlayer()
 #endif
 
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
-    if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
+    if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent) || m_remote->hasAvailabilityCallbacks()) {
         m_hasPlaybackTargetAvailabilityListeners = true;
         m_mediaSession->setHasPlaybackTargetAvailabilityListeners(true);
         enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event.
index cf0ee9a..7e6cdd1 100644 (file)
@@ -107,6 +107,10 @@ class WebKitMediaKeys;
 
 template<typename> class DOMPromiseDeferred;
 
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+class RemotePlayback;
+#endif
+
 #if ENABLE(VIDEO_TRACK)
 using CueIntervalTree = PODIntervalTree<MediaTime, TextTrackCue*>;
 using CueInterval = CueIntervalTree::IntervalType;
@@ -413,6 +417,7 @@ public:
     void wirelessRoutesAvailableDidChange() override;
     void setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&&) override;
     void setShouldPlayToPlaybackTarget(bool) override;
+    void playbackTargetPickerWasDismissed() override;
 #endif
     bool isPlayingToWirelessPlaybackTarget() const override { return m_isPlayingToWirelessTarget; };
     void setIsPlayingToWirelessTarget(bool);
@@ -582,6 +587,11 @@ public:
 
     enum class AutoplayEventPlaybackState { None, PreventedAutoplay, StartedWithUserGesture, StartedWithoutUserGesture };
 
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+    RemotePlayback& remote() { return m_remote; }
+    void remoteHasAvailabilityCallbacksChanged();
+#endif
+
 protected:
     HTMLMediaElement(const QualifiedName&, Document&, bool createdByParser);
     virtual void finishInitialization();
@@ -1182,6 +1192,10 @@ private:
     GenericTaskQueue<Timer> m_encryptedMediaQueue;
 #endif
 
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+    Ref<RemotePlayback> m_remote;
+#endif
+
     std::unique_ptr<MediaElementSession> m_mediaSession;
     size_t m_reportedExtraMemoryCost { 0 };
 
index 2a4c8f5..53f53bf 100644 (file)
@@ -600,6 +600,11 @@ bool MediaElementSession::wirelessVideoPlaybackDisabled() const
     }
 #endif
 
+    if (m_element.document().settings().remotePlaybackEnabled() && m_element.hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) {
+        LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of RemotePlayback attribute");
+        return true;
+    }
+
     auto player = m_element.player();
     if (!player)
         return true;
@@ -678,6 +683,12 @@ void MediaElementSession::setShouldPlayToPlaybackTarget(bool shouldPlay)
     client().setShouldPlayToPlaybackTarget(shouldPlay);
 }
 
+void MediaElementSession::playbackTargetPickerWasDismissed()
+{
+    INFO_LOG(LOGIDENTIFIER);
+    client().playbackTargetPickerWasDismissed();
+}
+
 void MediaElementSession::mediaStateDidChange(MediaProducer::MediaStateFlags state)
 {
     m_element.document().playbackTargetPickerClientStateDidChange(*this, state);
index 440e5b2..f5b7ba1 100644 (file)
@@ -172,6 +172,7 @@ private:
     void setPlaybackTarget(Ref<MediaPlaybackTarget>&&) override;
     void externalOutputDeviceAvailableDidChange(bool) override;
     void setShouldPlayToPlaybackTarget(bool) override;
+    void playbackTargetPickerWasDismissed() override;
 #endif
 #if PLATFORM(IOS_FAMILY)
     bool requiresPlaybackTargetRouteMonitoring() const override;
index 3505d44..6920a6d 100644 (file)
@@ -473,6 +473,7 @@ public:
     virtual void playbackTargetPickerClientStateDidChange(uint64_t /*contextId*/, MediaProducer::MediaStateFlags) { }
     virtual void setMockMediaPlaybackTargetPickerEnabled(bool)  { }
     virtual void setMockMediaPlaybackTargetPickerState(const String&, MediaPlaybackTargetContext::State) { }
+    virtual void mockMediaPlaybackTargetPickerDismissPopup() { }
 #endif
 
     virtual void imageOrMediaDocumentSizeChanged(const IntSize&) { }
index 229e1d2..7ba0eaa 100644 (file)
@@ -2552,6 +2552,11 @@ void Page::setMockMediaPlaybackTargetPickerState(const String& name, MediaPlayba
     chrome().client().setMockMediaPlaybackTargetPickerState(name, state);
 }
 
+void Page::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    chrome().client().mockMediaPlaybackTargetPickerDismissPopup();
+}
+
 void Page::setPlaybackTarget(uint64_t contextId, Ref<MediaPlaybackTarget>&& target)
 {
     for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
@@ -2578,6 +2583,15 @@ void Page::setShouldPlayToPlaybackTarget(uint64_t clientId, bool shouldPlay)
         frame->document()->setShouldPlayToPlaybackTarget(clientId, shouldPlay);
     }
 }
+
+void Page::playbackTargetPickerWasDismissed(uint64_t clientId)
+{
+    for (auto* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
+        if (!frame->document())
+            continue;
+        frame->document()->playbackTargetPickerWasDismissed(clientId);
+    }
+}
 #endif
 
 WheelEventTestMonitor& Page::ensureWheelEventTestMonitor()
index 3462741..ea2b7d2 100644 (file)
@@ -638,10 +638,12 @@ public:
     void playbackTargetPickerClientStateDidChange(uint64_t, MediaProducer::MediaStateFlags);
     WEBCORE_EXPORT void setMockMediaPlaybackTargetPickerEnabled(bool);
     WEBCORE_EXPORT void setMockMediaPlaybackTargetPickerState(const String&, MediaPlaybackTargetContext::State);
+    WEBCORE_EXPORT void mockMediaPlaybackTargetPickerDismissPopup();
 
     WEBCORE_EXPORT void setPlaybackTarget(uint64_t, Ref<MediaPlaybackTarget>&&);
     WEBCORE_EXPORT void playbackTargetAvailabilityDidChange(uint64_t, bool);
     WEBCORE_EXPORT void setShouldPlayToPlaybackTarget(uint64_t, bool);
+    WEBCORE_EXPORT void playbackTargetPickerWasDismissed(uint64_t);
 #endif
 
     RefPtr<WheelEventTestMonitor> wheelEventTestMonitor() const { return m_wheelEventTestMonitor; }
index 917483e..5e35fac 100644 (file)
@@ -493,6 +493,10 @@ allowsAirPlayForMediaPlayback:
   initial: true
   conditional: WIRELESS_PLAYBACK_TARGET
 
+remotePlaybackEnabled:
+  initial: true
+  conditional: WIRELESS_PLAYBACK_TARGET
+
 shouldConvertPositionStyleOnCopy:
   initial: false
 
index 224f717..8eb3a59 100644 (file)
@@ -166,6 +166,7 @@ public:
     void setPlaybackTarget(Ref<MediaPlaybackTarget>&&) override { }
     void externalOutputDeviceAvailableDidChange(bool) override { }
     void setShouldPlayToPlaybackTarget(bool) override { }
+    void playbackTargetPickerWasDismissed() override { }
 #endif
 
 #if PLATFORM(IOS_FAMILY)
@@ -250,6 +251,7 @@ public:
     virtual void setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&&) { }
     virtual bool isPlayingToWirelessPlaybackTarget() const { return false; }
     virtual void setShouldPlayToPlaybackTarget(bool) { }
+    virtual void playbackTargetPickerWasDismissed() { }
 
     virtual bool isPlayingOnSecondScreen() const { return false; }
 
index e29c9e6..604b3e4 100644 (file)
@@ -41,6 +41,7 @@ public:
     virtual void setPlaybackTarget(Ref<MediaPlaybackTarget>&&) = 0;
     virtual void externalOutputDeviceAvailableDidChange(bool) = 0;
     virtual void setShouldPlayToPlaybackTarget(bool) = 0;
+    virtual void playbackTargetPickerWasDismissed() = 0;
 };
 
 } // namespace WebCore
index 3246c4f..a2228cf 100644 (file)
@@ -59,6 +59,9 @@ void MediaPlaybackTargetPicker::pendingActionTimerFired()
 
     if (pendingActions & OutputDeviceAvailabilityChanged)
         m_client->externalOutputDeviceAvailableDidChange(externalOutputDeviceAvailable());
+
+    if (pendingActions & PlaybackTargetPickerWasDismissed)
+        m_client->playbackTargetPickerWasDismissed();
 }
 
 void MediaPlaybackTargetPicker::addPendingAction(PendingActionFlags action)
index 0eca0b9..497484a 100644 (file)
@@ -45,6 +45,7 @@ public:
     public:
         virtual void setPlaybackTarget(Ref<MediaPlaybackTarget>&&) = 0;
         virtual void externalOutputDeviceAvailableDidChange(bool) = 0;
+        virtual void playbackTargetPickerWasDismissed() = 0;
 
         void invalidate();
     };
@@ -58,6 +59,7 @@ public:
 
     void availableDevicesDidChange() { addPendingAction(OutputDeviceAvailabilityChanged); }
     void currentDeviceDidChange() { addPendingAction(CurrentDeviceDidChange); }
+    void playbackTargetPickerWasDismissed() { addPendingAction(PlaybackTargetPickerWasDismissed); }
 
 protected:
     explicit MediaPlaybackTargetPicker(Client&);
@@ -65,6 +67,7 @@ protected:
     enum ActionType {
         OutputDeviceAvailabilityChanged = 1 << 0,
         CurrentDeviceDidChange = 1 << 1,
+        PlaybackTargetPickerWasDismissed = 1 << 2,
     };
     typedef unsigned PendingActionFlags;
 
index 9d03f98..f0b5c34 100644 (file)
@@ -105,19 +105,22 @@ AVOutputDeviceMenuController *MediaPlaybackTargetPickerMac::devicePicker()
     return m_outputDeviceMenuController.get();
 }
 
-void MediaPlaybackTargetPickerMac::showPlaybackTargetPicker(const FloatRect& location, bool checkActiveRoute, bool useDarkAppearance)
+void MediaPlaybackTargetPickerMac::showPlaybackTargetPicker(const FloatRect& location, bool hasActiveRoute, bool useDarkAppearance)
 {
     if (!client() || m_showingMenu)
         return;
 
-    LOG(Media, "MediaPlaybackTargetPickerMac::showPlaybackTargetPicker - checkActiveRoute = %i", (int)checkActiveRoute);
+    LOG(Media, "MediaPlaybackTargetPickerMac::showPlaybackTargetPicker - hasActiveRoute = %i", (int)hasActiveRoute);
 
     m_showingMenu = true;
 
-    if ([devicePicker() showMenuForRect:location appearanceName:(useDarkAppearance ? NSAppearanceNameVibrantDark : NSAppearanceNameVibrantLight) allowReselectionOfSelectedOutputDevice:!checkActiveRoute]) {
-        if (!checkActiveRoute)
-            currentDeviceDidChange();
-    }
+    bool targetSelected = [devicePicker() showMenuForRect:location appearanceName:(useDarkAppearance ? NSAppearanceNameVibrantDark : NSAppearanceNameVibrantLight) allowReselectionOfSelectedOutputDevice:!hasActiveRoute];
+
+    if (targetSelected != hasActiveRoute)
+        currentDeviceDidChange();
+    else if (!targetSelected && !hasActiveRoute)
+        playbackTargetPickerWasDismissed();
+
     m_showingMenu = false;
 }
 
index a4ff609..092c9c0 100644 (file)
@@ -120,6 +120,15 @@ void MediaPlaybackTargetPickerMock::setState(const String& deviceName, MediaPlay
     }
 }
 
+void MediaPlaybackTargetPickerMock::dismissPopup()
+{
+    if (!m_showingMenu)
+        return;
+
+    m_showingMenu = false;
+    currentDeviceDidChange();
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)
index d4769d6..a6a1606 100644 (file)
@@ -48,6 +48,7 @@ public:
     void invalidatePlaybackTargets() override;
 
     void setState(const String&, MediaPlaybackTargetContext::State);
+    void dismissPopup();
 
 private:
     bool externalOutputDeviceAvailable() override;
index fa5bf61..07ed39f 100644 (file)
@@ -4185,6 +4185,14 @@ ExceptionOr<void> Internals::setMockMediaPlaybackTargetPickerState(const String&
     return { };
 }
 
+void Internals::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    auto* page = contextDocument()->frame()->page();
+    ASSERT(page);
+
+    page->mockMediaPlaybackTargetPickerDismissPopup();
+}
+
 #endif
 
 ExceptionOr<Ref<MockPageOverlay>> Internals::installMockPageOverlay(PageOverlayType type)
index fd378e6..c1992fd 100644 (file)
@@ -631,6 +631,7 @@ public:
 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
     void setMockMediaPlaybackTargetPickerEnabled(bool);
     ExceptionOr<void> setMockMediaPlaybackTargetPickerState(const String& deviceName, const String& deviceState);
+    void mockMediaPlaybackTargetPickerDismissPopup();
 #endif
 
 #if ENABLE(WEB_AUDIO)
index b23f70c..baf627e 100644 (file)
@@ -619,6 +619,7 @@ enum CompositingPolicy {
     [Conditional=VIDEO, MayThrowException] void postRemoteControlCommand(DOMString command, optional unrestricted float argument = 0);
     [Conditional=WIRELESS_PLAYBACK_TARGET] void setMockMediaPlaybackTargetPickerEnabled(boolean enabled);
     [Conditional=WIRELESS_PLAYBACK_TARGET, MayThrowException] void setMockMediaPlaybackTargetPickerState(DOMString deviceName, DOMString deviceState);
+    [Conditional=WIRELESS_PLAYBACK_TARGET] void mockMediaPlaybackTargetPickerDismissPopup();
     [Conditional=MEDIA_STREAM] void setMockMediaCaptureDevicesEnabled(boolean enabled);
     [Conditional=MEDIA_STREAM] void setCustomPrivateRecorderCreator();
 
index d7d1fad..19da265 100644 (file)
@@ -1,3 +1,31 @@
+2019-10-07  Jer Noble  <jer.noble@apple.com>
+
+        Implement the Remote Playback API.
+        https://bugs.webkit.org/show_bug.cgi?id=162971
+
+        Reviewed by Youenn Fablet.
+
+        Add a preference to enable the Remote Playback API. Pass the playbackTargetPickerWasDismissed()
+        notification on to Page.
+
+        * Shared/WebPreferencesDefinitions.h:
+        * UIProcess/API/C/WKPreferences.cpp:
+        (WKPreferencesGetRemotePlaybackEnabled):
+        (WKPreferencesSetRemotePlaybackEnabled):
+        * UIProcess/API/C/WKPreferencesRef.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::playbackTargetPickerWasDismissed):
+        * UIProcess/WebPageProxy.h:
+        * WebProcess/WebCoreSupport/WebChromeClient.cpp:
+        (WebKit::WebChromeClient::mockMediaPlaybackTargetPickerDismissPopup):
+        * WebProcess/WebCoreSupport/WebChromeClient.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::updatePreferences):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/mac/WebPageMac.mm:
+        (WebKit::WebPage::playbackTargetPickerWasDismissed):
+
 2019-10-29  Per Arne Vollan  <pvollan@apple.com>
 
         REGRESSION: WebContent getting killed due to syscall filter violation
index bfcf1bc..999cd7a 100644 (file)
@@ -1806,6 +1806,14 @@ PictureInPictureAPIEnabled:
   category: experimental
   condition: ENABLE(PICTURE_IN_PICTURE_API)
 
+RemotePlaybackEnabled:
+  category: experimental
+  condition: ENABLE(WIRELESS_PLAYBACK_TARGET)
+  type: bool
+  defaultValue: false
+  humanReadableName: "Remote Playback API"
+  humanReadableDescription: "Enable Remote Playback API"
+
 # Deprecated
 
 ICECandidateFilteringEnabled:
index 88802a2..71a6ed2 100644 (file)
@@ -2168,3 +2168,13 @@ bool WKPreferencesGetLazyImageLoadingEnabled(WKPreferencesRef preferencesRef)
 {
     return toImpl(preferencesRef)->lazyImageLoadingEnabled();
 }
+
+bool WKPreferencesGetRemotePlaybackEnabled(WKPreferencesRef preferencesRef)
+{
+    return WebKit::toImpl(preferencesRef)->remotePlaybackEnabled();
+}
+
+void WKPreferencesSetRemotePlaybackEnabled(WKPreferencesRef preferencesRef, bool enabled)
+{
+    WebKit::toImpl(preferencesRef)->setRemotePlaybackEnabled(enabled);
+}
index c0d78ef..08e182c 100644 (file)
@@ -338,6 +338,10 @@ WK_EXPORT bool WKPreferencesGetCaptureAudioInUIProcessEnabled(WKPreferencesRef p
 WK_EXPORT void WKPreferencesSetCaptureVideoInUIProcessEnabled(WKPreferencesRef preferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetCaptureVideoInUIProcessEnabled(WKPreferencesRef preferencesRef);
 
+// Defaults to false.
+WK_EXPORT bool WKPreferencesGetRemotePlaybackEnabled(WKPreferencesRef preferencesRef);
+WK_EXPORT void WKPreferencesSetRemotePlaybackEnabled(WKPreferencesRef preferencesRef, bool enabled);
+
 #ifdef __cplusplus
 }
 #endif
index 00c7b71..80cea66 100644 (file)
@@ -862,6 +862,16 @@ static WebCore::EditableLinkBehavior toEditableLinkBehavior(_WKEditableLinkBehav
     _preferences->setSecureContextChecksEnabled(enabled);
 }
 
+- (BOOL)_remotePlaybackEnabled
+{
+    return _preferences->remotePlaybackEnabled();
+}
+
+- (void)_setRemotePlaybackEnabled:(BOOL)enabled
+{
+    _preferences->setRemotePlaybackEnabled(enabled);
+}
+
 #if PLATFORM(MAC)
 - (void)_setJavaEnabledForLocalFiles:(BOOL)enabled
 {
index 26fca7f..dd3cf15 100644 (file)
@@ -157,6 +157,7 @@ typedef NS_ENUM(NSInteger, _WKEditableLinkBehavior) {
 @property (nonatomic, setter=_setItpDebugModeEnabled:) BOOL _itpDebugModeEnabled WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setMediaSourceEnabled:) BOOL _mediaSourceEnabled WK_API_AVAILABLE(macos(10.13.4), ios(WK_IOS_TBA));
 @property (nonatomic, setter=_setSecureContextChecksEnabled:) BOOL _secureContextChecksEnabled WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+@property (nonatomic, setter=_setRemotePlaybackEnabled:) BOOL _remotePlaybackEnabled 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));
index 272fcaa..193f1c7 100644 (file)
@@ -8569,6 +8569,11 @@ void WebPageProxy::setMockMediaPlaybackTargetPickerState(const String& name, Web
     pageClient().mediaSessionManager().setMockMediaPlaybackTargetPickerState(name, state);
 }
 
+void WebPageProxy::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    pageClient().mediaSessionManager().mockMediaPlaybackTargetPickerDismissPopup();
+}
+
 void WebPageProxy::setPlaybackTarget(uint64_t contextId, Ref<MediaPlaybackTarget>&& target)
 {
     if (!hasRunningProcess())
@@ -8592,6 +8597,14 @@ void WebPageProxy::setShouldPlayToPlaybackTarget(uint64_t contextId, bool should
 
     m_process->send(Messages::WebPage::SetShouldPlayToPlaybackTarget(contextId, shouldPlay), m_webPageID);
 }
+
+void WebPageProxy::playbackTargetPickerWasDismissed(uint64_t contextId)
+{
+    if (!hasRunningProcess())
+        return;
+
+    m_process->send(Messages::WebPage::PlaybackTargetPickerWasDismissed(contextId), m_webPageID);
+}
 #endif
 
 void WebPageProxy::didExceedInactiveMemoryLimitWhileActive()
index a71d28f..f684fcd 100644 (file)
@@ -1425,11 +1425,13 @@ public:
     void playbackTargetPickerClientStateDidChange(uint64_t, WebCore::MediaProducer::MediaStateFlags);
     void setMockMediaPlaybackTargetPickerEnabled(bool);
     void setMockMediaPlaybackTargetPickerState(const String&, WebCore::MediaPlaybackTargetContext::State);
+    void mockMediaPlaybackTargetPickerDismissPopup();
 
     // WebMediaSessionManagerClient
     void setPlaybackTarget(uint64_t, Ref<WebCore::MediaPlaybackTarget>&&) override;
     void externalOutputDeviceAvailableDidChange(uint64_t, bool) override;
     void setShouldPlayToPlaybackTarget(uint64_t, bool) override;
+    void playbackTargetPickerWasDismissed(uint64_t) override;
 #endif
 
     void didChangeBackgroundColor();
index 6c62307..a6ce38d 100644 (file)
@@ -503,6 +503,7 @@ messages -> WebPageProxy {
     PlaybackTargetPickerClientStateDidChange(uint64_t contextId, unsigned mediaState)
     SetMockMediaPlaybackTargetPickerEnabled(bool enabled)
     SetMockMediaPlaybackTargetPickerState(String name, unsigned pickerState)
+    MockMediaPlaybackTargetPickerDismissPopup()
 #endif
 
 #if ENABLE(POINTER_LOCK)
index 4e62959..88a1620 100644 (file)
@@ -1300,6 +1300,10 @@ void WebChromeClient::setMockMediaPlaybackTargetPickerState(const String& name,
     m_page.send(Messages::WebPageProxy::SetMockMediaPlaybackTargetPickerState(name, state));
 }
 
+void WebChromeClient::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    m_page.send(Messages::WebPageProxy::MockMediaPlaybackTargetPickerDismissPopup());
+}
 #endif
 
 void WebChromeClient::imageOrMediaDocumentSizeChanged(const IntSize& newSize)
index 99918c7..b399507 100644 (file)
@@ -50,10 +50,10 @@ public:
 private:
     ~WebChromeClient();
 
-    void didInsertMenuElement(WebCore::HTMLMenuElement&);
-    void didRemoveMenuElement(WebCore::HTMLMenuElement&);
-    void didInsertMenuItemElement(WebCore::HTMLMenuItemElement&);
-    void didRemoveMenuItemElement(WebCore::HTMLMenuItemElement&);
+    void didInsertMenuElement(WebCore::HTMLMenuElement&) final;
+    void didRemoveMenuElement(WebCore::HTMLMenuElement&) final;
+    void didInsertMenuItemElement(WebCore::HTMLMenuItemElement&) final;
+    void didRemoveMenuItemElement(WebCore::HTMLMenuItemElement&) final;
 
     void chromeDestroyed() final;
     
@@ -355,6 +355,7 @@ private:
     void playbackTargetPickerClientStateDidChange(uint64_t, WebCore::MediaProducer::MediaStateFlags) final;
     void setMockMediaPlaybackTargetPickerEnabled(bool) final;
     void setMockMediaPlaybackTargetPickerState(const String&, WebCore::MediaPlaybackTargetContext::State) final;
+    void mockMediaPlaybackTargetPickerDismissPopup() final;
 #endif
 
     void imageOrMediaDocumentSizeChanged(const WebCore::IntSize&) final;
index 5c2a62a..4a12c5c 100644 (file)
@@ -1578,6 +1578,7 @@ private:
     void playbackTargetSelected(uint64_t, const WebCore::MediaPlaybackTargetContext& outputDevice) const;
     void playbackTargetAvailabilityDidChange(uint64_t, bool);
     void setShouldPlayToPlaybackTarget(uint64_t, bool);
+    void playbackTargetPickerWasDismissed(uint64_t);
 #endif
 
     void clearWheelEventTestMonitor();
index dc25d90..adbcf3d 100644 (file)
@@ -507,6 +507,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType
     PlaybackTargetSelected(uint64_t contextId, WebCore::MediaPlaybackTargetContext target)
     PlaybackTargetAvailabilityDidChange(uint64_t contextId, bool available)
     SetShouldPlayToPlaybackTarget(uint64_t contextId, bool shouldPlay)
+    PlaybackTargetPickerWasDismissed(uint64_t contextId);
 #endif
 
 #if ENABLE(POINTER_LOCK)
index 25f3f77..d720052 100644 (file)
@@ -1046,6 +1046,11 @@ void WebPage::setShouldPlayToPlaybackTarget(uint64_t contextId, bool shouldPlay)
 {
     m_page->setShouldPlayToPlaybackTarget(contextId, shouldPlay);
 }
+
+void WebPage::playbackTargetPickerWasDismissed(uint64_t contextId)
+{
+    m_page->playbackTargetPickerWasDismissed(contextId);
+}
 #endif
 
 } // namespace WebKit
index c0fbda9..3a993e8 100644 (file)
@@ -227,6 +227,7 @@ private:
     void playbackTargetPickerClientStateDidChange(uint64_t /*contextId*/, WebCore::MediaProducer::MediaStateFlags) final;
     void setMockMediaPlaybackTargetPickerEnabled(bool) final;
     void setMockMediaPlaybackTargetPickerState(const String&, WebCore::MediaPlaybackTargetContext::State) final;
+    void mockMediaPlaybackTargetPickerDismissPopup() override;
 #endif
 
     String signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL&) const final;
index e1a8989..a20caae 100644 (file)
@@ -1118,6 +1118,10 @@ void WebChromeClient::setMockMediaPlaybackTargetPickerState(const String& name,
     [m_webView _setMockMediaPlaybackTargetPickerName:name state:state];
 }
 
+void WebChromeClient::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    [m_webView _mockMediaPlaybackTargetPickerDismissPopup];
+}
 #endif
 
 String WebChromeClient::signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL& url) const
index a072e81..bd79756 100644 (file)
@@ -50,11 +50,13 @@ public:
     void playbackTargetPickerClientStateDidChange(uint64_t, WebCore::MediaProducer::MediaStateFlags);
     void setMockMediaPlaybackTargetPickerEnabled(bool);
     void setMockMediaPlaybackTargetPickerState(const String&, WebCore::MediaPlaybackTargetContext::State);
+    void mockMediaPlaybackTargetPickerDismissPopup();
 
     // WebMediaSessionManagerClient
     void setPlaybackTarget(uint64_t, Ref<WebCore::MediaPlaybackTarget>&&) override;
     void externalOutputDeviceAvailableDidChange(uint64_t, bool) override;
     void setShouldPlayToPlaybackTarget(uint64_t, bool) override;
+    void playbackTargetPickerWasDismissed(uint64_t) override;
 
     void invalidate();
 
index 6024029..1965b98 100644 (file)
@@ -71,6 +71,11 @@ void WebMediaPlaybackTargetPicker::setMockMediaPlaybackTargetPickerState(const S
     WebCore::WebMediaSessionManager::shared().setMockMediaPlaybackTargetPickerState(name, state);
 }
 
+void WebMediaPlaybackTargetPicker::mockMediaPlaybackTargetPickerDismissPopup()
+{
+    WebCore::WebMediaSessionManager::shared().mockMediaPlaybackTargetPickerDismissPopup();
+}
+
 void WebMediaPlaybackTargetPicker::setPlaybackTarget(uint64_t contextId, Ref<WebCore::MediaPlaybackTarget>&& target)
 {
     if (!m_page)
@@ -95,6 +100,12 @@ void WebMediaPlaybackTargetPicker::setShouldPlayToPlaybackTarget(uint64_t contex
     m_page->setShouldPlayToPlaybackTarget(contextId, shouldPlay);
 }
 
+void WebMediaPlaybackTargetPicker::playbackTargetPickerWasDismissed(uint64_t contextId)
+{
+    if (m_page)
+        m_page->playbackTargetPickerWasDismissed(contextId);
+}
+
 void WebMediaPlaybackTargetPicker::invalidate()
 {
     m_page = nullptr;
index b3da2d6..e695d83 100644 (file)
 #define WebKitVisualViewportAPIEnabledPreferenceKey @"WebKitVisualViewportAPIEnabled"
 #define WebKitCSSOMViewScrollingAPIEnabledPreferenceKey @"WebKitCSSOMViewScrollingAPIEnabled"
 #define WebKitModernMediaControlsEnabledPreferenceKey @"WebKitModernMediaControlsEnabled"
+#define WebKitRemotePlaybackEnabledPreferenceKey @"WebKitRemotePlaybackEnabled"
 #define WebKitSubtleCryptoEnabledPreferenceKey @"WebKitSubtleCryptoEnabled"
 #define WebKitMediaDevicesEnabledPreferenceKey @"WebKitMediaDevicesEnabled"
 #define WebKitMediaStreamEnabledPreferenceKey @"WebKitMediaStreamEnabled"
index 811b3a5..49e43db 100644 (file)
@@ -3554,6 +3554,15 @@ static NSString *classIBCreatorID = nil;
     [self _setBoolValue:flag forKey:WebKitCSSShadowPartsEnabledPreferenceKey];
 }
 
+- (BOOL)remotePlaybackEnabled
+{
+    return [self _boolValueForKey:WebKitRemotePlaybackEnabledPreferenceKey];
+}
+
+- (void)setRemotePlaybackEnabled:(BOOL)remotePlaybackEnabled
+{
+    [self _setBoolValue:remotePlaybackEnabled forKey:WebKitRemotePlaybackEnabledPreferenceKey];
+}
 @end
 
 @implementation WebPreferences (WebInternal)
index dad23c1..ab1f1a1 100644 (file)
@@ -632,6 +632,7 @@ extern NSString *WebPreferencesCacheModelChangedInternalNotification WEBKIT_DEPR
 @property (nonatomic) BOOL mediaDataLoadsAutomatically;
 @property (nonatomic) BOOL attachmentElementEnabled;
 @property (nonatomic) BOOL allowsInlineMediaPlaybackAfterFullscreen;
+@property (nonatomic) BOOL remotePlaybackEnabled;
 @property (nonatomic) BOOL intersectionObserverEnabled;
 @property (nonatomic) BOOL menuItemElementEnabled;
 @property (nonatomic) BOOL keygenElementEnabled;
index f8d41a3..7d64476 100644 (file)
@@ -3208,6 +3208,10 @@ static bool needsSelfRetainWhileLoadingQuirk()
 
     settings.setAllowMediaContentTypesRequiringHardwareSupportAsFallback(preferences.allowMediaContentTypesRequiringHardwareSupportAsFallback);
 
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+    settings.setRemotePlaybackEnabled(preferences.remotePlaybackEnabled);
+#endif
+
     NSTimeInterval timeout = [preferences incrementalRenderingSuppressionTimeoutInSeconds];
     if (timeout > 0)
         settings.setIncrementalRenderingSuppressionTimeoutInSeconds(timeout);
@@ -9604,6 +9608,11 @@ bool LayerFlushController::flushLayers()
 {
     [self _devicePicker]->setMockMediaPlaybackTargetPickerState(name, state);
 }
+
+- (void)_mockMediaPlaybackTargetPickerDismissPopup
+{
+    [self _devicePicker]->mockMediaPlaybackTargetPickerDismissPopup();
+}
 #endif
 
 #if HAVE(TOUCH_BAR)
index 84f6f89..85e23f8 100644 (file)
@@ -312,6 +312,7 @@ OBJC_CLASS NSTextAlternatives;
 - (void)_playbackTargetPickerClientStateDidChange:(uint64_t)contextId state:(WebCore::MediaProducer::MediaStateFlags)state;
 - (void)_setMockMediaPlaybackTargetPickerEnabled:(bool)enabled;
 - (void)_setMockMediaPlaybackTargetPickerName:(NSString *)name state:(WebCore::MediaPlaybackTargetContext::State)state;
+- (void)_mockMediaPlaybackTargetPickerDismissPopup;
 #endif
 
 - (void)prepareForMouseUp;
index e869e7c..dffcde8 100644 (file)
@@ -1,3 +1,17 @@
+2019-10-07  Jer Noble  <jer.noble@apple.com>
+
+        Implement the Remote Playback API.
+        https://bugs.webkit.org/show_bug.cgi?id=162971
+
+        Reviewed by Youenn Fablet.
+
+        Enable RemotePlayback for DumpRenderTree and WebKitTestRunner.
+
+        * DumpRenderTree/mac/DumpRenderTree.mm:
+        (resetWebPreferencesToConsistentValues):
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::resetPreferencesToConsistentValues):
+
 2019-10-29  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Add fast path for String#localeCompare
index 1880bbe..24c32f8 100644 (file)
@@ -989,6 +989,7 @@ static void resetWebPreferencesToConsistentValues()
 
     [preferences setHiddenPageDOMTimerThrottlingEnabled:NO];
     [preferences setHiddenPageCSSAnimationSuspensionEnabled:NO];
+    [preferences setRemotePlaybackEnabled:YES];
 
     [preferences setMediaDevicesEnabled:YES];
 
index 7545fbf..9810c88 100644 (file)
@@ -889,6 +889,7 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio
     WKPreferencesSetMediaPreloadingEnabled(preferences, true);
     WKPreferencesSetMediaPlaybackAllowsInline(preferences, true);
     WKPreferencesSetInlineMediaPlaybackRequiresPlaysInlineAttribute(preferences, false);
+    WKPreferencesSetRemotePlaybackEnabled(preferences, true);
     WKPreferencesSetBeaconAPIEnabled(preferences, true);
     WKPreferencesSetDirectoryUploadEnabled(preferences, true);