Title: [267472] trunk
Revision
267472
Author
[email protected]
Date
2020-09-23 07:04:20 -0700 (Wed, 23 Sep 2020)

Log Message

Add support for HTMLMediaElement.setSinkId
https://bugs.webkit.org/show_bug.cgi?id=216696

Reviewed by Eric Carlson.

LayoutTests/imported/w3c:

* resources/import-expectations.json:
* web-platform-tests/audio-output/META.yml: Added.
* web-platform-tests/audio-output/idlharness.https.window-expected.txt: Added.
* web-platform-tests/audio-output/idlharness.https.window.html: Added.
* web-platform-tests/audio-output/idlharness.https.window.js: Added.
* web-platform-tests/audio-output/setSinkId.https-expected.txt: Added.
* web-platform-tests/audio-output/setSinkId.https.html: Added.
* web-platform-tests/audio-output/w3c-import.log: Added.

Source/WebCore:

Implement setSinkId and sinkId as per https://w3c.github.io/mediacapture-output/#htmlmediaelement-extensions.
Introduce a setting to expose these methods and to enable/disable user gesture requirement.
Add interfaces to change device output for specific media players.
Add support for HLS, MSE and MediaStreamTrack renderers on MacOS.
In case of setting the empty string, the default output device is used

Tests: http/wpt/audio-output/setSinkId.https.html
       imported/w3c/web-platform-tests/audio-output/idlharness.https.window.html
       imported/w3c/web-platform-tests/audio-output/setSinkId.https.html

* CMakeLists.txt:
* DerivedSources.make:
* Modules/mediastream/MediaDevices.cpp:
(WebCore::MediaDevices::exposeDevices):
* Modules/mediastream/MediaDevices.h:
* WebCore.xcodeproj/project.pbxproj:
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::audioOutputDevice const):
(WebCore::HTMLMediaElement::setAudioOutputDevice):
(WebCore::HTMLMediaElement::audioOutputDeviceId const):
* html/HTMLMediaElement.h:
(WebCore::HTMLMediaElement::audioOutputHashedDeviceId const):
* html/HTMLMediaElementAudioOutput.idl: Added.
* page/Settings.yaml:
* platform/graphics/MediaPlayer.cpp:
(WebCore::MediaPlayer::audioOutputDeviceChanged):
* platform/graphics/MediaPlayer.h:
(WebCore::MediaPlayerClient::audioOutputDeviceId const):
(WebCore::MediaPlayerClient::audioOutputDeviceIdOverride const):
(WebCore::MediaPlayer::audioOutputDeviceId const):
(WebCore::MediaPlayer::audioOutputDeviceIdOverride const):
* platform/graphics/MediaPlayerPrivate.h:
(WebCore::MediaPlayerPrivateInterface::audioOutputDeviceChanged):
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayer):
(WebCore::MediaPlayerPrivateAVFoundationObjC::audioOutputDeviceChanged):
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::audioOutputDeviceChanged):
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaStreamAVFObjC::updateTracks):
(WebCore::MediaPlayerPrivateMediaStreamAVFObjC::audioOutputDeviceChanged):
* platform/mediastream/AudioMediaStreamTrackRenderer.h:
(WebCore::AudioMediaStreamTrackRenderer::setAudioOutputDevice):
* platform/mediastream/AudioTrackPrivateMediaStream.cpp:
(WebCore::AudioTrackPrivateMediaStream::setAudioOutputDevice):
* platform/mediastream/AudioTrackPrivateMediaStream.h:
* platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.cpp:
(WebCore::AudioMediaStreamTrackRendererCocoa::setAudioOutputDevice):
(WebCore::AudioMediaStreamTrackRendererCocoa::pushSamples):
* platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.h:
* platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.cpp:
(WebCore::AudioMediaStreamTrackRendererUnit::setAudioOutputDevice):
(WebCore::AudioMediaStreamTrackRendererUnit::createAudioUnitIfNeeded):
* platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.h:

Source/WebKit:

Add internal flag to enable/disable user gesture requirement for setting audio output device,
and for per media element setting of audio output device.

* Shared/WebPreferencesExperimental.yaml:
* Shared/WebPreferencesInternals.yaml:

Source/WTF:

Add HAVE_AUDIO_OUTPUT_DEVICE_UNIQUE_ID.
* wtf/PlatformHave.h:

Tools:

* WebKitTestRunner/TestController.cpp:
(WTR::TestController::resetPreferencesToConsistentValues):

LayoutTests:

Skip new tests on WK1 since mediaDevices is not implemented in WK1.

* http/wpt/audio-output/setSinkId.https-expected.txt: Added.
* http/wpt/audio-output/setSinkId.https.html: Added.
* platform/mac-wk1/TestExpectations:
* platform/win/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (267471 => 267472)


--- trunk/LayoutTests/ChangeLog	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/LayoutTests/ChangeLog	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1,3 +1,17 @@
+2020-09-23  Youenn Fablet  <[email protected]>
+
+        Add support for HTMLMediaElement.setSinkId
+        https://bugs.webkit.org/show_bug.cgi?id=216696
+
+        Reviewed by Eric Carlson.
+
+        Skip new tests on WK1 since mediaDevices is not implemented in WK1.
+
+        * http/wpt/audio-output/setSinkId.https-expected.txt: Added.
+        * http/wpt/audio-output/setSinkId.https.html: Added.
+        * platform/mac-wk1/TestExpectations:
+        * platform/win/TestExpectations:
+
 2020-09-23  Philippe Normand  <[email protected]>
 
         REGRESSION(r267383): fast/mediastream/getUserMedia-webaudio.html is failing

Added: trunk/LayoutTests/http/wpt/audio-output/setSinkId.https-expected.txt (0 => 267472)


--- trunk/LayoutTests/http/wpt/audio-output/setSinkId.https-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/audio-output/setSinkId.https-expected.txt	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,4 @@
+
+PASS setSinkId requires user gesture 
+PASS setSinkId and sinkId 
+

Added: trunk/LayoutTests/http/wpt/audio-output/setSinkId.https.html (0 => 267472)


--- trunk/LayoutTests/http/wpt/audio-output/setSinkId.https.html	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/audio-output/setSinkId.https.html	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,36 @@
+<!doctype html>
+<head>
+<title>Test setSinkId behavior </title>
+<link rel="author" title="Dominique Hazael-Massieux" href=""
+<link rel="help" href=""
+</head>
+<script src=""
+<script src=""
+<script>
+"use strict";
+
+const audio = new Audio();
+
+promise_test(t => {
+    if (!window.internals)
+        return;
+    internals.settings.setSpeakerSelectionRequiresUserGesture(true);
+    t.add_cleanup(() => { internals.settings.setSpeakerSelectionRequiresUserGesture(false); });
+
+    return promise_rejects_dom(t, "NotAllowedError", audio.setSinkId(""));
+}, "setSinkId requires user gesture");
+
+
+promise_test(async t => {
+    await navigator.mediaDevices.getUserMedia({ audio : true });
+    const list = await navigator.mediaDevices.enumerateDevices();
+    const outputDevicesList = list.filter(({kind}) => kind == "audiooutput");
+    assert_not_equals(outputDevicesList.length, 0, "media device list includes at least one audio output device");
+
+    let promise = audio.setSinkId(outputDevicesList[0].deviceId);
+
+    assert_equals(audio.sinkId, "");
+    await promise;
+    assert_equals(audio.sinkId, outputDevicesList[0].deviceId);
+}, "setSinkId and sinkId");
+</script>

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (267471 => 267472)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1,3 +1,19 @@
+2020-09-23  Youenn Fablet  <[email protected]>
+
+        Add support for HTMLMediaElement.setSinkId
+        https://bugs.webkit.org/show_bug.cgi?id=216696
+
+        Reviewed by Eric Carlson.
+
+        * resources/import-expectations.json:
+        * web-platform-tests/audio-output/META.yml: Added.
+        * web-platform-tests/audio-output/idlharness.https.window-expected.txt: Added.
+        * web-platform-tests/audio-output/idlharness.https.window.html: Added.
+        * web-platform-tests/audio-output/idlharness.https.window.js: Added.
+        * web-platform-tests/audio-output/setSinkId.https-expected.txt: Added.
+        * web-platform-tests/audio-output/setSinkId.https.html: Added.
+        * web-platform-tests/audio-output/w3c-import.log: Added.
+
 2020-09-22  Chris Dumez  <[email protected]>
 
         Slightly improve AudioBufferSourceNode resampling

Modified: trunk/LayoutTests/imported/w3c/resources/import-expectations.json (267471 => 267472)


--- trunk/LayoutTests/imported/w3c/resources/import-expectations.json	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/LayoutTests/imported/w3c/resources/import-expectations.json	2020-09-23 14:04:20 UTC (rev 267472)
@@ -49,7 +49,7 @@
     "web-platform-tests/apng": "skip", 
     "web-platform-tests/app-uri": "skip", 
     "web-platform-tests/assumptions": "skip", 
-    "web-platform-tests/audio-output": "skip", 
+    "web-platform-tests/audio-output": "import", 
     "web-platform-tests/auxclick": "skip", 
     "web-platform-tests/background-fetch": "import", 
     "web-platform-tests/battery-status": "skip", 

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/META.yml (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/META.yml	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/META.yml	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,4 @@
+spec: https://w3c.github.io/mediacapture-output/
+suggested_reviewers:
+  - guidou
+  - jan-ivar

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window-expected.txt (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window-expected.txt	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,16 @@
+
+PASS idl_test setup 
+PASS idl_test validation 
+PASS Partial interface HTMLMediaElement: original interface defined 
+PASS Partial interface HTMLMediaElement: member names are unique 
+PASS HTMLElement includes GlobalEventHandlers: member names are unique 
+PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique 
+PASS HTMLElement includes ElementContentEditable: member names are unique 
+PASS HTMLElement includes HTMLOrSVGElement: member names are unique 
+PASS Element includes ParentNode: member names are unique 
+PASS Element includes NonDocumentTypeChildNode: member names are unique 
+PASS Element includes ChildNode: member names are unique 
+PASS Element includes Slottable: member names are unique 
+PASS HTMLMediaElement interface: attribute sinkId 
+PASS HTMLMediaElement interface: operation setSinkId(DOMString) 
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.html (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.html	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1 @@
+<!-- This file is required for WebKit test infrastructure to run the templated test -->
\ No newline at end of file

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.js (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.js	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.js	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,20 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+// https://w3c.github.io/mediacapture-output/
+
+'use strict';
+
+idl_test(
+  ['audio-output'],
+  ['mediacapture-streams', 'html', 'dom'],
+  idl_array => {
+    self.audio = document.createElement('audio');
+    self.video = document.createElement('video');
+    idl_array.add_objects({
+      HTMLAudioElement: ['audio'],
+      HTMLVideoElement: ['video'],
+      MediaDevices: ['navigator.mediaDevices'],
+    });
+  }
+);

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https-expected.txt (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https-expected.txt	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,5 @@
+
+PASS setSinkId on default audio output should always work 
+PASS setSinkId fails with NotFoundError on made up deviceid 
+FAIL List device, setSinkId should be allowed on the default, the rest of the devices will get a NotAllowedError assert_not_equals: media device list includes at least one audio output device got disallowed value 0
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https.html (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https.html	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,46 @@
+<!doctype html>
+<head>
+<title>Test setSinkId behavior </title>
+<link rel="author" title="Dominique Hazael-Massieux" href=""
+<link rel="help" href=""
+</head>
+<script src=""
+<script src=""
+<script>
+"use strict";
+
+const audio = new Audio();
+
+promise_test(t => audio.setSinkId(""), "setSinkId on default audio output should always work");
+
+promise_test(t => promise_rejects_dom(t, "NotFoundError", audio.setSinkId("nonexistent_device_id")),
+  "setSinkId fails with NotFoundError on made up deviceid");
+
+promise_test(async t => {
+  const list = await navigator.mediaDevices.enumerateDevices();
+  const outputDevicesList = list.filter(({kind}) => kind == "audiooutput");
+  assert_not_equals(outputDevicesList.length, 0,
+    "media device list includes at least one audio output device");
+
+  let acceptedDevices = 0;
+  for (const {deviceId} of outputDevicesList) {
+    const {deviceId} = outputDevicesList[0];
+    const p1 = audio.setSinkId(deviceId);
+    assert_equals(audio.sinkId, "", "before it resolves, setSinkId is unchanged");
+    try {
+      let r = await p1;
+      assert_equals(acceptedDevices, 0, "only the default sink device can be set");
+      acceptedDevices++;
+      assert_equals(r, undefined, "setSinkId resolves with undefined");
+      assert_equals(audio.sinkId, deviceId, "when it resolves, setSinkId updates sinkId to the requested deviceId");
+      r = await audio.setSinkId(deviceId);
+      assert_equals(r, undefined, "resetting sinkid on same current value should always work");
+      r = await audio.setSinkId("");
+      assert_equals(r, undefined, "resetting sinkid on default audio output should always work");
+    } catch (e) {
+      assert_equals(e.name, "NotAllowedError", "Non-default devices are failing with NotAllowed error");
+    }
+  }
+}, "List device, setSinkId should be allowed on the default, the rest of the devices will get a NotAllowedError");
+
+</script>

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/w3c-import.log (0 => 267472)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/w3c-import.log	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/audio-output/w3c-import.log	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,20 @@
+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/audio-output/META.yml
+/LayoutTests/imported/w3c/web-platform-tests/audio-output/idlharness.https.window.js
+/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId-manual.https.html
+/LayoutTests/imported/w3c/web-platform-tests/audio-output/setSinkId.https.html

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (267471 => 267472)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2020-09-23 14:04:20 UTC (rev 267472)
@@ -65,6 +65,9 @@
 http/tests/navigation/page-cache-getUserMedia-pending-promise.html [ Skip ]
 http/tests/navigation/page-cache-mediastream.html [ Skip ]
 
+http/wpt/audio-output [ Skip ]
+imported/w3c/web-platform-tests/audio-output [ Skip ]
+
 # Datalist is unsupported in WK1
 accessibility/datalist.html [ WontFix ]
 fast/forms/datalist [ WontFix ]

Modified: trunk/LayoutTests/platform/win/TestExpectations (267471 => 267472)


--- trunk/LayoutTests/platform/win/TestExpectations	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/LayoutTests/platform/win/TestExpectations	2020-09-23 14:04:20 UTC (rev 267472)
@@ -859,6 +859,9 @@
 http/tests/media/media-stream [ Skip ]
 http/tests/navigation/page-cache-getUserMedia-pending-promise.html [ Skip ]
 
+http/wpt/audio-output [ Skip ]
+imported/w3c/web-platform-tests/audio-output [ Skip ]
+
 # needs enhanced eventSender.contextMenu() return value
 #webkit.org/b/45021 media/context-menu-actions.html
 

Modified: trunk/Source/WTF/ChangeLog (267471 => 267472)


--- trunk/Source/WTF/ChangeLog	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WTF/ChangeLog	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1,3 +1,13 @@
+2020-09-23  Youenn Fablet  <[email protected]>
+
+        Add support for HTMLMediaElement.setSinkId
+        https://bugs.webkit.org/show_bug.cgi?id=216696
+
+        Reviewed by Eric Carlson.
+
+        Add HAVE_AUDIO_OUTPUT_DEVICE_UNIQUE_ID.
+        * wtf/PlatformHave.h:
+
 2020-09-21  Yusuke Suzuki  <[email protected]>
 
         [JSC] BigInt should work with Map / Set

Modified: trunk/Source/WTF/wtf/PlatformHave.h (267471 => 267472)


--- trunk/Source/WTF/wtf/PlatformHave.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WTF/wtf/PlatformHave.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -682,6 +682,10 @@
 #define HAVE_NSTABLEVIEWSTYLE 1
 #endif
 
+#if PLATFORM(MAC)
+#define HAVE_AUDIO_OUTPUT_DEVICE_UNIQUE_ID 1
+#endif
+
 #if ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 140000) \
     || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 70000) \
     || (PLATFORM(APPLETV) && __TV_OS_VERSION_MIN_REQUIRED >= 140000)

Modified: trunk/Source/WebCore/CMakeLists.txt (267471 => 267472)


--- trunk/Source/WebCore/CMakeLists.txt	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/CMakeLists.txt	2020-09-23 14:04:20 UTC (rev 267472)
@@ -919,6 +919,7 @@
     html/HTMLMapElement.idl
     html/HTMLMarqueeElement.idl
     html/HTMLMediaElement.idl
+    html/HTMLMediaElementAudioOutput.idl
     html/HTMLMenuElement.idl
     html/HTMLMenuItemElement.idl
     html/HTMLMetaElement.idl

Modified: trunk/Source/WebCore/ChangeLog (267471 => 267472)


--- trunk/Source/WebCore/ChangeLog	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/ChangeLog	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1,3 +1,68 @@
+2020-09-23  Youenn Fablet  <[email protected]>
+
+        Add support for HTMLMediaElement.setSinkId
+        https://bugs.webkit.org/show_bug.cgi?id=216696
+
+        Reviewed by Eric Carlson.
+
+        Implement setSinkId and sinkId as per https://w3c.github.io/mediacapture-output/#htmlmediaelement-extensions.
+        Introduce a setting to expose these methods and to enable/disable user gesture requirement.
+        Add interfaces to change device output for specific media players.
+        Add support for HLS, MSE and MediaStreamTrack renderers on MacOS.
+        In case of setting the empty string, the default output device is used
+
+        Tests: http/wpt/audio-output/setSinkId.https.html
+               imported/w3c/web-platform-tests/audio-output/idlharness.https.window.html
+               imported/w3c/web-platform-tests/audio-output/setSinkId.https.html
+
+        * CMakeLists.txt:
+        * DerivedSources.make:
+        * Modules/mediastream/MediaDevices.cpp:
+        (WebCore::MediaDevices::exposeDevices):
+        * Modules/mediastream/MediaDevices.h:
+        * WebCore.xcodeproj/project.pbxproj:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::audioOutputDevice const):
+        (WebCore::HTMLMediaElement::setAudioOutputDevice):
+        (WebCore::HTMLMediaElement::audioOutputDeviceId const):
+        * html/HTMLMediaElement.h:
+        (WebCore::HTMLMediaElement::audioOutputHashedDeviceId const):
+        * html/HTMLMediaElementAudioOutput.idl: Added.
+        * page/Settings.yaml:
+        * platform/graphics/MediaPlayer.cpp:
+        (WebCore::MediaPlayer::audioOutputDeviceChanged):
+        * platform/graphics/MediaPlayer.h:
+        (WebCore::MediaPlayerClient::audioOutputDeviceId const):
+        (WebCore::MediaPlayerClient::audioOutputDeviceIdOverride const):
+        (WebCore::MediaPlayer::audioOutputDeviceId const):
+        (WebCore::MediaPlayer::audioOutputDeviceIdOverride const):
+        * platform/graphics/MediaPlayerPrivate.h:
+        (WebCore::MediaPlayerPrivateInterface::audioOutputDeviceChanged):
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::createAVPlayer):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::audioOutputDeviceChanged):
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::audioOutputDeviceChanged):
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm:
+        (WebCore::MediaPlayerPrivateMediaStreamAVFObjC::updateTracks):
+        (WebCore::MediaPlayerPrivateMediaStreamAVFObjC::audioOutputDeviceChanged):
+        * platform/mediastream/AudioMediaStreamTrackRenderer.h:
+        (WebCore::AudioMediaStreamTrackRenderer::setAudioOutputDevice):
+        * platform/mediastream/AudioTrackPrivateMediaStream.cpp:
+        (WebCore::AudioTrackPrivateMediaStream::setAudioOutputDevice):
+        * platform/mediastream/AudioTrackPrivateMediaStream.h:
+        * platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.cpp:
+        (WebCore::AudioMediaStreamTrackRendererCocoa::setAudioOutputDevice):
+        (WebCore::AudioMediaStreamTrackRendererCocoa::pushSamples):
+        * platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.h:
+        * platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.cpp:
+        (WebCore::AudioMediaStreamTrackRendererUnit::setAudioOutputDevice):
+        (WebCore::AudioMediaStreamTrackRendererUnit::createAudioUnitIfNeeded):
+        * platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.h:
+
 2020-09-23  Philippe Normand  <[email protected]>
 
         REGRESSION(r267383): fast/mediastream/getUserMedia-webaudio.html is failing

Modified: trunk/Source/WebCore/DerivedSources-input.xcfilelist (267471 => 267472)


--- trunk/Source/WebCore/DerivedSources-input.xcfilelist	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/DerivedSources-input.xcfilelist	2020-09-23 14:04:20 UTC (rev 267472)
@@ -843,6 +843,7 @@
 $(PROJECT_DIR)/html/HTMLMapElement.idl
 $(PROJECT_DIR)/html/HTMLMarqueeElement.idl
 $(PROJECT_DIR)/html/HTMLMediaElement.idl
+$(PROJECT_DIR)/html/HTMLMediaElementAudioOutput.idl
 $(PROJECT_DIR)/html/HTMLMenuElement.idl
 $(PROJECT_DIR)/html/HTMLMenuItemElement.idl
 $(PROJECT_DIR)/html/HTMLMetaElement.idl

Modified: trunk/Source/WebCore/DerivedSources-output.xcfilelist (267471 => 267472)


--- trunk/Source/WebCore/DerivedSources-output.xcfilelist	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/DerivedSources-output.xcfilelist	2020-09-23 14:04:20 UTC (rev 267472)
@@ -966,6 +966,8 @@
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElement+RemotePlayback.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElement.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElement.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementAudioOutput.cpp
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementAudioOutput.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementMediaSession.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementMediaSession.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSHTMLMediaElementRemotePlayback.cpp

Modified: trunk/Source/WebCore/DerivedSources.make (267471 => 267472)


--- trunk/Source/WebCore/DerivedSources.make	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/DerivedSources.make	2020-09-23 14:04:20 UTC (rev 267472)
@@ -864,6 +864,7 @@
     $(WebCore)/html/HTMLMapElement.idl \
     $(WebCore)/html/HTMLMarqueeElement.idl \
     $(WebCore)/html/HTMLMediaElement.idl \
+    $(WebCore)/html/HTMLMediaElementAudioOutput.idl \
     $(WebCore)/html/HTMLMenuElement.idl \
     $(WebCore)/html/HTMLMenuItemElement.idl \
     $(WebCore)/html/HTMLMetaElement.idl \

Modified: trunk/Source/WebCore/Modules/mediastream/MediaDevices.cpp (267471 => 267472)


--- trunk/Source/WebCore/Modules/mediastream/MediaDevices.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/Modules/mediastream/MediaDevices.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -202,6 +202,8 @@
     bool canAccessMicrophone = checkMicrophoneAccess(document);
     bool canAccessSpeaker = checkSpeakerAccess(document);
 
+    m_audioOutputDeviceIdToPersistentId.clear();
+
     Vector<Ref<MediaDeviceInfo>> devices;
     for (auto& newDevice : newDevices) {
         if (!canAccessMicrophone && newDevice.type() == CaptureDevice::DeviceType::Microphone)
@@ -214,6 +216,9 @@
         auto deviceId = RealtimeMediaSourceCenter::singleton().hashStringWithSalt(newDevice.persistentId(), deviceIDHashSalt);
         auto groupId = RealtimeMediaSourceCenter::singleton().hashStringWithSalt(newDevice.groupId(), m_groupIdHashSalt);
 
+        if (newDevice.type() == CaptureDevice::DeviceType::Speaker)
+            m_audioOutputDeviceIdToPersistentId.add(deviceId, newDevice.persistentId());
+
         devices.append(MediaDeviceInfo::create(newDevice.label(), WTFMove(deviceId), WTFMove(groupId), toMediaDeviceInfoKind(newDevice.type())));
     }
     promise.resolve(devices);

Modified: trunk/Source/WebCore/Modules/mediastream/MediaDevices.h (267471 => 267472)


--- trunk/Source/WebCore/Modules/mediastream/MediaDevices.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/Modules/mediastream/MediaDevices.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -89,6 +89,8 @@
     void enumerateDevices(EnumerateDevicesPromise&&);
     MediaTrackSupportedConstraints getSupportedConstraints();
 
+    String deviceIdToPersistentId(const String& deviceId) const { return m_audioOutputDeviceIdToPersistentId.get(deviceId); }
+
     using RefCounted<MediaDevices>::ref;
     using RefCounted<MediaDevices>::deref;
 
@@ -130,6 +132,9 @@
 
     OptionSet<GestureAllowedRequest> m_requestTypesForCurrentGesture;
     WeakPtr<UserGestureToken> m_currentGestureToken;
+
+    HashMap<String, String> m_audioOutputDeviceIdToPersistentId;
+    String m_audioOutputDeviceId;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (267471 => 267472)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-09-23 14:04:20 UTC (rev 267472)
@@ -7696,6 +7696,7 @@
 		41D28D0C2139E01E00F4206F /* LibWebRTCStatsCollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LibWebRTCStatsCollector.h; path = libwebrtc/LibWebRTCStatsCollector.h; sourceTree = "<group>"; };
 		41D41C652256859200697942 /* ServiceWorkerInternals.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ServiceWorkerInternals.mm; sourceTree = "<group>"; };
 		41D51BB21E4E2E8100131A5B /* LibWebRTCAudioFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LibWebRTCAudioFormat.h; path = libwebrtc/LibWebRTCAudioFormat.h; sourceTree = "<group>"; };
+		41D6BD98251B3ADA0055A7B7 /* HTMLMediaElementAudioOutput.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = HTMLMediaElementAudioOutput.idl; sourceTree = "<group>"; };
 		41DE7C7A222DA13D00532B65 /* StorageQuotaManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StorageQuotaManager.cpp; sourceTree = "<group>"; };
 		41DE7C7B222DA13E00532B65 /* StorageQuotaManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StorageQuotaManager.h; sourceTree = "<group>"; };
 		41DEFCB21E56C1B9000D9E5F /* JSDOMBindingInternals.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; path = JSDOMBindingInternals.js; sourceTree = "<group>"; };
@@ -22916,6 +22917,7 @@
 				E44613920CD6331000FADA75 /* HTMLMediaElement.cpp */,
 				E44613930CD6331000FADA75 /* HTMLMediaElement.h */,
 				E44613940CD6331000FADA75 /* HTMLMediaElement.idl */,
+				41D6BD98251B3ADA0055A7B7 /* HTMLMediaElementAudioOutput.idl */,
 				CD5209E51B0BD9E10077184E /* HTMLMediaElementEnums.h */,
 				A8EA79EC0A1916DF00A8EF5F /* HTMLMenuElement.cpp */,
 				A8EA79E80A1916DF00A8EF5F /* HTMLMenuElement.h */,

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (267471 => 267472)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -71,6 +71,7 @@
 #include "MIMETypeRegistry.h"
 #include "MediaController.h"
 #include "MediaControlsHost.h"
+#include "MediaDevices.h"
 #include "MediaDocument.h"
 #include "MediaError.h"
 #include "MediaFragmentURIParser.h"
@@ -78,6 +79,7 @@
 #include "MediaPlayer.h"
 #include "MediaQueryEvaluator.h"
 #include "MediaResourceLoader.h"
+#include "NavigatorMediaDevices.h"
 #include "NetworkingContext.h"
 #include "PODIntervalTree.h"
 #include "Page.h"
@@ -2843,6 +2845,49 @@
     seekWithTolerance(time, negativeTolerance, MediaTime::zeroTime(), true);
 }
 
+#if ENABLE(MEDIA_STREAM)
+void HTMLMediaElement::setAudioOutputDevice(String&& deviceId, DOMPromiseDeferred<void>&& promise)
+{
+    auto* window = document().domWindow();
+    auto* mediaDevices = window ? NavigatorMediaDevices::mediaDevices(window->navigator()) : nullptr;
+    if (!mediaDevices) {
+        promise.reject(Exception { NotAllowedError });
+        return;
+    }
+
+    if (!document().processingUserGestureForMedia() && document().settings().speakerSelectionRequiresUserGesture()) {
+        promise.reject(Exception { NotAllowedError, "A user gesture is required"_s });
+        return;
+    }
+
+    if (deviceId.isEmpty())
+        deviceId = { };
+
+    if (deviceId == m_audioOutputHashedDeviceId) {
+        promise.resolve();
+        return;
+    }
+
+    String persistentId;
+    if (!deviceId.isNull()) {
+        persistentId = mediaDevices->deviceIdToPersistentId(deviceId);
+        if (persistentId.isNull()) {
+            promise.reject(Exception { NotFoundError });
+            return;
+        }
+    }
+
+    m_audioOutputPersistentDeviceId = WTFMove(persistentId);
+    if (m_player)
+        m_player->audioOutputDeviceChanged();
+
+    scriptExecutionContext()->eventLoop().queueTask(TaskSource::MediaElement, [this, protectedThis = makeRef(*this), deviceId = WTFMove(deviceId), promise = WTFMove(promise)]() mutable {
+        m_audioOutputHashedDeviceId = WTFMove(deviceId);
+        promise.resolve();
+    });
+}
+#endif
+
 void HTMLMediaElement::seek(const MediaTime& time)
 {
     ALWAYS_LOG(LOGIDENTIFIER, time);

Modified: trunk/Source/WebCore/html/HTMLMediaElement.h (267471 => 267472)


--- trunk/Source/WebCore/html/HTMLMediaElement.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/html/HTMLMediaElement.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -268,6 +268,11 @@
     double minFastReverseRate() const;
     double maxFastForwardRate() const;
 
+#if ENABLE(MEDIA_STREAM)
+    void setAudioOutputDevice(String&& deviceId, DOMPromiseDeferred<void>&&);
+    String audioOutputHashedDeviceId() const { return m_audioOutputHashedDeviceId; }
+#endif
+
     using HTMLMediaElementEnums::BufferingPolicy;
     void setBufferingPolicy(BufferingPolicy);
     WEBCORE_EXPORT BufferingPolicy bufferingPolicy() const;
@@ -605,6 +610,9 @@
 
     SecurityOriginData documentSecurityOrigin() const final;
 
+    String audioOutputDeviceId() const final { return m_audioOutputPersistentDeviceId; }
+    String audioOutputDeviceIdOverride() const final { return m_audioOutputPersistentDeviceId; }
+
     bool mediaControlsDependOnPageScaleFactor() const { return m_mediaControlsDependOnPageScaleFactor; }
     void setMediaControlsDependOnPageScaleFactor(bool);
     void updateMediaControlsAfterPresentationModeChange();
@@ -1197,6 +1205,11 @@
     bool m_isPlayingToWirelessTarget { false };
     bool m_playingOnSecondScreen { false };
     bool m_removedBehaviorRestrictionsAfterFirstUserGesture { false };
+
+    String m_audioOutputPersistentDeviceId;
+#if ENABLE(MEDIA_STREAM)
+    String m_audioOutputHashedDeviceId;
+#endif
 };
 
 String convertEnumerationToString(HTMLMediaElement::AutoplayEventPlaybackState);

Added: trunk/Source/WebCore/html/HTMLMediaElementAudioOutput.idl (0 => 267472)


--- trunk/Source/WebCore/html/HTMLMediaElementAudioOutput.idl	                        (rev 0)
+++ trunk/Source/WebCore/html/HTMLMediaElementAudioOutput.idl	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+
+[
+    Conditional=VIDEO&MEDIA_STREAM,
+] partial interface HTMLMediaElement {
+    [EnabledBySetting=ExposeSpeakers&PerElementSpeakerSelection, ImplementedAs=audioOutputHashedDeviceId] readonly attribute DOMString sinkId;
+    [EnabledBySetting=ExposeSpeakers&PerElementSpeakerSelection, ImplementedAs=setAudioOutputDevice] Promise<undefined> setSinkId(DOMString deviceId);
+};

Copied: trunk/Source/WebCore/page/AudioOutputProvider.h (from rev 267471, trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.h) (0 => 267472)


--- trunk/Source/WebCore/page/AudioOutputProvider.h	                        (rev 0)
+++ trunk/Source/WebCore/page/AudioOutputProvider.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace WebCore {
+
+class Page;
+
+class WEBCORE_EXPORT AudioOutputProvider {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    AudioOutputProvider() = default;
+    virtual ~AudioOutputProvider() = default;
+
+    virtual void defaultAudioOutputDeviceChanged(Page&);
+};
+
+inline void AudioOutputProvider::defaultAudioOutputDeviceChanged(Page&)
+{
+}
+
+}

Modified: trunk/Source/WebCore/page/Settings.yaml (267471 => 267472)


--- trunk/Source/WebCore/page/Settings.yaml	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/page/Settings.yaml	2020-09-23 14:04:20 UTC (rev 267472)
@@ -826,6 +826,12 @@
 exposeSpeakersEnabled:
   initial: false
 
+perElementSpeakerSelectionEnabled:
+  initial: false
+
+speakerSelectionRequiresUserGesture:
+  initial: true
+
 mediaDeviceIdentifierStorageDirectory:
   type: String
   conditional: ENABLE(MEDIA_STREAM)

Modified: trunk/Source/WebCore/platform/graphics/MediaPlayer.cpp (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/MediaPlayer.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/MediaPlayer.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1604,6 +1604,11 @@
     m_private->setPreferredDynamicRangeMode(mode);
 }
 
+void MediaPlayer::audioOutputDeviceChanged()
+{
+    m_private->audioOutputDeviceChanged();
+}
+
 #if !RELEASE_LOG_DISABLED
 const Logger& MediaPlayer::mediaPlayerLogger()
 {

Modified: trunk/Source/WebCore/platform/graphics/MediaPlayer.h (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/MediaPlayer.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/MediaPlayer.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -273,6 +273,9 @@
 
     virtual SecurityOriginData documentSecurityOrigin() const { return { }; }
 
+    virtual String audioOutputDeviceId() const { return { }; }
+    virtual String audioOutputDeviceIdOverride() const { return { }; }
+
 #if !RELEASE_LOG_DISABLED
     virtual const void* mediaPlayerLogIdentifier() { return nullptr; }
     virtual const Logger& mediaPlayerLogger() = 0;
@@ -631,6 +634,10 @@
     DynamicRangeMode preferredDynamicRangeMode() const { return m_preferredDynamicRangeMode; }
     void setPreferredDynamicRangeMode(DynamicRangeMode);
 
+    String audioOutputDeviceId() const;
+    String audioOutputDeviceIdOverride() const;
+    void audioOutputDeviceChanged();
+
 private:
     MediaPlayer(MediaPlayerClient&);
     MediaPlayer(MediaPlayerClient&, MediaPlayerEnums::MediaEngineIdentifier);
@@ -705,6 +712,16 @@
 };
 
 
+inline String MediaPlayer::audioOutputDeviceId() const
+{
+    return m_client ? m_client->audioOutputDeviceId() : String { };
+}
+
+inline String MediaPlayer::audioOutputDeviceIdOverride() const
+{
+    return m_client ? m_client->audioOutputDeviceIdOverride() : String { };
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(VIDEO)

Modified: trunk/Source/WebCore/platform/graphics/MediaPlayerPrivate.h (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/MediaPlayerPrivate.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/MediaPlayerPrivate.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -289,6 +289,8 @@
     virtual bool shouldIgnoreIntrinsicSize() { return false; }
 
     virtual void setPreferredDynamicRangeMode(DynamicRangeMode) { }
+
+    virtual void audioOutputDeviceChanged() { }
 };
 
 }

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -318,6 +318,7 @@
     void setShouldObserveTimeControlStatus(bool);
 
     void setPreferredDynamicRangeMode(DynamicRangeMode) final;
+    void audioOutputDeviceChanged() final;
 
     RetainPtr<AVURLAsset> m_avAsset;
     RetainPtr<AVPlayer> m_avPlayer;

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm	2020-09-23 14:04:20 UTC (rev 267472)
@@ -953,6 +953,16 @@
     if (m_avPlayerItem)
         setAVPlayerItem(m_avPlayerItem.get());
 
+#if HAVE(AUDIO_OUTPUT_DEVICE_UNIQUE_ID)
+    auto audioOutputDeviceId = player()->audioOutputDeviceIdOverride();
+    if (!audioOutputDeviceId.isNull()) {
+        if (audioOutputDeviceId.isEmpty())
+            m_avPlayer.get().audioOutputDeviceUniqueID = nil;
+        else
+            m_avPlayer.get().audioOutputDeviceUniqueID = audioOutputDeviceId;
+    }
+#endif
+
     setDelayCallbacks(false);
 }
 
@@ -3293,6 +3303,19 @@
 #endif
 }
 
+void MediaPlayerPrivateAVFoundationObjC::audioOutputDeviceChanged()
+{
+#if HAVE(AUDIO_OUTPUT_DEVICE_UNIQUE_ID)
+    if (!m_avPlayer || !player())
+        return;
+    auto deviceId = player()->audioOutputDeviceId();
+    if (deviceId.isEmpty())
+        m_avPlayer.get().audioOutputDeviceUniqueID = nil;
+    else
+        m_avPlayer.get().audioOutputDeviceUniqueID = deviceId;
+#endif
+}
+
 NSArray* assetMetadataKeyNames()
 {
     static NSArray* keys = [[NSArray alloc] initWithObjects:

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -254,6 +254,7 @@
 #endif
 
     bool performTaskAtMediaTime(Function<void()>&&, const MediaTime&) final;
+    void audioOutputDeviceChanged() final;
 
     void ensureLayer();
     void destroyLayer();

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1166,6 +1166,16 @@
     [audioRenderer setVolume:m_player->volume()];
     [audioRenderer setAudioTimePitchAlgorithm:(m_player->preservesPitch() ? AVAudioTimePitchAlgorithmSpectral : AVAudioTimePitchAlgorithmVarispeed)];
 
+#if HAVE(AUDIO_OUTPUT_DEVICE_UNIQUE_ID)
+    auto deviceId = m_player->audioOutputDeviceIdOverride();
+    if (!deviceId.isNull()) {
+        if (deviceId.isEmpty())
+            audioRenderer.audioOutputDeviceUniqueID = nil;
+        else
+            audioRenderer.audioOutputDeviceUniqueID = deviceId;
+    }
+#endif
+
     @try {
         [m_synchronizer addRenderer:audioRenderer];
     } @catch(NSException *exception) {
@@ -1276,6 +1286,22 @@
     return true;
 }
 
+void MediaPlayerPrivateMediaSourceAVFObjC::audioOutputDeviceChanged()
+{
+#if HAVE(AUDIO_OUTPUT_DEVICE_UNIQUE_ID)
+    if (!m_player)
+        return;
+    auto deviceId = m_player->audioOutputDeviceId();
+    for (auto& key : m_sampleBufferAudioRendererMap.keys()) {
+        auto renderer = ((__bridge AVSampleBufferAudioRenderer *)key.get());
+        if (deviceId.isEmpty())
+            renderer.audioOutputDeviceUniqueID = nil;
+        else
+            renderer.audioOutputDeviceUniqueID = deviceId;
+    }
+#endif
+}
+
 WTFLogChannel& MediaPlayerPrivateMediaSourceAVFObjC::logChannel() const
 {
     return LogMediaSource;

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -159,6 +159,7 @@
     bool ended() const override { return m_ended; }
 
     void setBufferingPolicy(MediaPlayer::BufferingPolicy) override;
+    void audioOutputDeviceChanged() final;
 
     MediaPlayer::ReadyState currentReadyState();
     void updateReadyState();

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm (267471 => 267472)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm	2020-09-23 14:04:20 UTC (rev 267472)
@@ -867,7 +867,8 @@
 {
     MediaStreamTrackPrivateVector currentTracks = m_mediaStreamPrivate->tracks();
 
-    auto setAudioTrackState = [this](AudioTrackPrivateMediaStream& track, int index, TrackState state)
+    auto deviceId = m_player->audioOutputDeviceIdOverride();
+    auto setAudioTrackState = [this, &deviceId](AudioTrackPrivateMediaStream& track, int index, TrackState state)
     {
         switch (state) {
         case TrackState::Remove:
@@ -884,6 +885,9 @@
             track.setVolume(m_volume);
             track.setMuted(m_muted);
             track.setEnabled(track.streamTrack().enabled() && !track.streamTrack().muted());
+            if (!deviceId.isNull())
+                track.setAudioOutputDevice(deviceId);
+
             if (playing())
                 track.play();
             break;
@@ -1017,6 +1021,15 @@
         m_sampleBufferDisplayLayer->flushAndRemoveImage();
 }
 
+void MediaPlayerPrivateMediaStreamAVFObjC::audioOutputDeviceChanged()
+{
+    if (!m_player)
+        return;
+    auto deviceId = m_player->audioOutputDeviceId();
+    for (auto& audioTrack : m_audioTrackMap.values())
+        audioTrack->setAudioOutputDevice(deviceId);
+}
+
 void MediaPlayerPrivateMediaStreamAVFObjC::scheduleDeferredTask(Function<void ()>&& function)
 {
     ASSERT(function);

Modified: trunk/Source/WebCore/platform/mediastream/AudioMediaStreamTrackRenderer.h (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/AudioMediaStreamTrackRenderer.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/AudioMediaStreamTrackRenderer.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -53,6 +53,8 @@
     virtual void setVolume(float);
     float volume() const;
 
+    virtual void setAudioOutputDevice(const String&);
+
 #if !RELEASE_LOG_DISABLED
     void setLogger(const Logger&, const void*);
 #endif
@@ -109,6 +111,10 @@
 }
 #endif
 
+inline void AudioMediaStreamTrackRenderer::setAudioOutputDevice(const String&)
+{
 }
 
+}
+
 #endif // ENABLE(MEDIA_STREAM)

Modified: trunk/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -97,6 +97,11 @@
     updateRenderer();
 }
 
+void AudioTrackPrivateMediaStream::setAudioOutputDevice(const String& deviceId)
+{
+    m_renderer->setAudioOutputDevice(deviceId);
+}
+
 float AudioTrackPrivateMediaStream::volume() const
 {
     return m_renderer->volume();

Modified: trunk/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.h (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/AudioTrackPrivateMediaStream.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -47,6 +47,7 @@
     ~AudioTrackPrivateMediaStream();
 
     void setTrackIndex(int index) { m_index = index; }
+    void setAudioOutputDevice(const String&);
 
     MediaStreamTrackPrivate& streamTrack() { return m_streamTrack.get(); }
 

Modified: trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.cpp (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -67,11 +67,18 @@
         m_dataSource->setVolume(volume);
 }
 
+void AudioMediaStreamTrackRendererCocoa::setAudioOutputDevice(const String& deviceId)
+{
+    // FIXME: We should create a unit for ourselves here or use the default unit if deviceId is matching.
+    AudioMediaStreamTrackRendererUnit::singleton().setAudioOutputDevice(deviceId);
+    m_shouldReset = true;
+}
+
 void AudioMediaStreamTrackRendererCocoa::pushSamples(const MediaTime& sampleTime, const PlatformAudioData& audioData, const AudioStreamDescription& description, size_t sampleCount)
 {
     ASSERT(!isMainThread());
     ASSERT(description.platformDescription().type == PlatformDescription::CAAudioStreamBasicType);
-    if (!m_dataSource || !m_dataSource->inputDescription() || *m_dataSource->inputDescription() != description) {
+    if (!m_dataSource || m_shouldReset || !m_dataSource->inputDescription() || *m_dataSource->inputDescription() != description) {
         auto dataSource = AudioSampleDataSource::create(description.sampleRate() * 2, *this);
 
         if (dataSource->setInputFormat(toCAAudioStreamDescription(description))) {
@@ -95,6 +102,7 @@
             AudioMediaStreamTrackRendererUnit::singleton().addSource(WTFMove(newSource));
         });
         m_dataSource = WTFMove(dataSource);
+        m_shouldReset = false;
     }
 
     m_dataSource->pushSamples(sampleTime, audioData, sampleCount);

Modified: trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.h (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererCocoa.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -53,9 +53,11 @@
     void stop() final;
     void clear() final;
     void setVolume(float) final;
+    void setAudioOutputDevice(const String&) final;
 
     std::unique_ptr<CAAudioStreamDescription> m_outputDescription;
     RefPtr<AudioSampleDataSource> m_dataSource;
+    bool m_shouldReset { false };
 };
 
 }

Modified: trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.cpp (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -36,6 +36,11 @@
 #include <pal/spi/cocoa/AudioToolboxSPI.h>
 #include <wtf/Lock.h>
 
+#if PLATFORM(COCOA)
+#include "CoreAudioCaptureDevice.h"
+#include "CoreAudioCaptureDeviceManager.h"
+#endif
+
 #include <pal/cf/CoreMediaSoftLink.h>
 
 namespace WebCore {
@@ -51,6 +56,33 @@
     stop();
 }
 
+void AudioMediaStreamTrackRendererUnit::setAudioOutputDevice(const String& deviceID)
+{
+#if PLATFORM(MAC)
+    auto device = CoreAudioCaptureDeviceManager::singleton().coreAudioDeviceWithUID(deviceID);
+
+    if (!device && !deviceID.isEmpty()) {
+        RELEASE_LOG(WebRTC, "AudioMediaStreamTrackRendererUnit::setAudioOutputDeviceId - did not find device");
+        return;
+    }
+
+    auto audioUnitDeviceID = device ? device->deviceID() : 0;
+    if (m_deviceID == audioUnitDeviceID)
+        return;
+
+    bool shouldRestart = m_isStarted;
+    if (m_isStarted)
+        stop();
+
+    m_deviceID = audioUnitDeviceID;
+
+    if (shouldRestart)
+        start();
+#else
+    UNUSED_PARAM(deviceID);
+#endif
+}
+
 void AudioMediaStreamTrackRendererUnit::addSource(Ref<AudioSampleDataSource>&& source)
 {
     RELEASE_LOG(WebRTC, "AudioMediaStreamTrackRendererUnit::addSource");
@@ -161,6 +193,16 @@
     }
 #endif
 
+#if PLATFORM(MAC)
+    if (m_deviceID) {
+        error = AudioUnitSetProperty(remoteIOUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &m_deviceID, sizeof(m_deviceID));
+        if (error) {
+            RELEASE_LOG_ERROR(WebRTC, "AudioMediaStreamTrackRendererUnit::createAudioUnit unable to set unit device ID %d, error %d (%.4s)", (int)m_deviceID, (int)error, (char*)&error);
+            return;
+        }
+    }
+#endif
+
     AURenderCallbackStruct callback = { inputProc, nullptr };
     error = AudioUnitSetProperty(remoteIOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback));
     if (error) {

Modified: trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.h (267471 => 267472)


--- trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.h	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebCore/platform/mediastream/mac/AudioMediaStreamTrackRendererUnit.h	2020-09-23 14:04:20 UTC (rev 267472)
@@ -51,6 +51,8 @@
     void start();
     void stop();
 
+    void setAudioOutputDevice(const String&);
+
     void addSource(Ref<AudioSampleDataSource>&&);
     void removeSource(AudioSampleDataSource&);
 
@@ -68,6 +70,9 @@
     bool m_shouldUpdateRenderSources { false };
     Lock m_sourcesLock;
     bool m_isStarted { false };
+#if PLATFORM(MAC)
+    uint32_t m_deviceID { 0 };
+#endif
 };
 
 }

Modified: trunk/Source/WebKit/ChangeLog (267471 => 267472)


--- trunk/Source/WebKit/ChangeLog	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebKit/ChangeLog	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1,3 +1,16 @@
+2020-09-23  Youenn Fablet  <[email protected]>
+
+        Add support for HTMLMediaElement.setSinkId
+        https://bugs.webkit.org/show_bug.cgi?id=216696
+
+        Reviewed by Eric Carlson.
+
+        Add internal flag to enable/disable user gesture requirement for setting audio output device,
+        and for per media element setting of audio output device.
+
+        * Shared/WebPreferencesExperimental.yaml:
+        * Shared/WebPreferencesInternals.yaml:
+
 2020-09-22  Wenson Hsieh  <[email protected]>
 
         [GPU Process] fast/canvas/canvas-blend-image.html and fast/canvas/canvas-blend-solid.html fail on macOS

Modified: trunk/Source/WebKit/Shared/WebPreferencesExperimental.yaml (267471 => 267472)


--- trunk/Source/WebKit/Shared/WebPreferencesExperimental.yaml	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebKit/Shared/WebPreferencesExperimental.yaml	2020-09-23 14:04:20 UTC (rev 267472)
@@ -144,8 +144,15 @@
   defaultValue: false
   humanReadableName: "Allow speaker device selection"
   humanReadableDescription: "Allow speaker device selection"
-  condition: ENABLE(WEB_RTC)
+  condition: ENABLE(MEDIA_STREAM)
 
+PerElementSpeakerSelectionEnabled:
+  type: bool
+  defaultValue: false
+  humanReadableName: "Allow per media element speaker device selection"
+  humanReadableDescription: "Allow per media element speaker device selection"
+  condition: ENABLE(MEDIA_STREAM)
+
 VP9DecoderEnabled:
   type: bool
   defaultValue: defaultVP9DecoderEnabled()

Modified: trunk/Source/WebKit/Shared/WebPreferencesInternal.yaml (267471 => 267472)


--- trunk/Source/WebKit/Shared/WebPreferencesInternal.yaml	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Source/WebKit/Shared/WebPreferencesInternal.yaml	2020-09-23 14:04:20 UTC (rev 267472)
@@ -72,6 +72,13 @@
   webcoreBinding: RuntimeEnabledFeatures
   condition: ENABLE(WEB_RTC)
 
+SpeakerSelectionRequiresUserGesture:
+  type: bool
+  defaultValue: true
+  humanReadableName: "Require a user gesture for speaker selection"
+  humanReadableDescription: "Require a user gesture for speaker selection"
+  condition: ENABLE(MEDIA_STREAM)
+
 FrameFlatteningEnabled:
   type: bool
   defaultValue: DEFAULT_FRAME_FLATTENING

Modified: trunk/Tools/ChangeLog (267471 => 267472)


--- trunk/Tools/ChangeLog	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Tools/ChangeLog	2020-09-23 14:04:20 UTC (rev 267472)
@@ -1,3 +1,13 @@
+2020-09-23  Youenn Fablet  <[email protected]>
+
+        Add support for HTMLMediaElement.setSinkId
+        https://bugs.webkit.org/show_bug.cgi?id=216696
+
+        Reviewed by Eric Carlson.
+
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::resetPreferencesToConsistentValues):
+
 2020-09-22  Keith Rollin  <[email protected]>
 
         Unify debug and release target aliases

Modified: trunk/Tools/WebKitTestRunner/TestController.cpp (267471 => 267472)


--- trunk/Tools/WebKitTestRunner/TestController.cpp	2020-09-23 13:36:28 UTC (rev 267471)
+++ trunk/Tools/WebKitTestRunner/TestController.cpp	2020-09-23 14:04:20 UTC (rev 267472)
@@ -992,6 +992,7 @@
     WKPreferencesSetMediaPlaybackRequiresUserGesture(preferences, false);
     WKPreferencesSetVideoPlaybackRequiresUserGesture(preferences, false);
     WKPreferencesSetAudioPlaybackRequiresUserGesture(preferences, false);
+    WKPreferencesSetInternalDebugFeatureForKey(preferences, false, WKStringCreateWithUTF8CString("SpeakerSelectionRequiresUserGesture"));
 
     WKPreferencesSetShouldUseServiceWorkerShortTimeout(preferences, options.contextOptions.useServiceWorkerShortTimeout);
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to