Add libwebrtc backend support for RTCRtpSender::replaceTrack
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Mar 2017 18:01:18 +0000 (18:01 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Mar 2017 18:01:18 +0000 (18:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=169841

Patch by Youenn Fablet <youenn@apple.com> on 2017-03-24
Reviewed by Alex Christensen.

Source/WebCore:

Tests: webrtc/audio-replace-track.html
       webrtc/video-replace-track.html

Adding support for replaceTrack for audio and video sources.
Replacing tracks will always succeed for audio sources.
For video tracks, it will only succeed if the video resolution is not greater.
LibWebRTCPeerConnectionBackend will delegate the track replacing by replacing the source of the outgoing sources with the source wrapped in the replacing track.

Video test is not fully passing as size constraints for mock video sources are not providing the right video stream resolution.

* Modules/mediastream/RTCRtpSender.cpp:
(WebCore::RTCRtpSender::replaceTrack):
* Modules/mediastream/RTCRtpSender.h:
* Modules/mediastream/RTCRtpSender.idl:
* Modules/mediastream/libwebrtc/LibWebRTCPeerConnectionBackend.cpp:
(WebCore::LibWebRTCPeerConnectionBackend::replaceTrack):
* Modules/mediastream/libwebrtc/LibWebRTCPeerConnectionBackend.h:
* platform/mediastream/mac/RealtimeOutgoingAudioSource.cpp:
(WebCore::RealtimeOutgoingAudioSource::setSource):
* platform/mediastream/mac/RealtimeOutgoingAudioSource.h:
* platform/mediastream/mac/RealtimeOutgoingVideoSource.cpp:
(WebCore::RealtimeOutgoingVideoSource::setSource):
* platform/mediastream/mac/RealtimeOutgoingVideoSource.h:
* platform/mock/MockRealtimeVideoSource.cpp:
(WebCore::MockRealtimeVideoSource::drawText):
(WebCore::MockRealtimeVideoSource::generateFrame):

LayoutTests:

* webrtc/audio-replace-track-expected.txt: Added.
* webrtc/audio-replace-track.html: Added.
* webrtc/video-replace-track-expected.txt: Added.
* webrtc/video-replace-track.html: Added.
* webrtc/video-replace-track-to-null-expected.txt: Added.
* webrtc/video-replace-track-to-null.html: Added.

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

20 files changed:
LayoutTests/ChangeLog
LayoutTests/webrtc/audio-replace-track-expected.txt [new file with mode: 0644]
LayoutTests/webrtc/audio-replace-track.html [new file with mode: 0644]
LayoutTests/webrtc/video-replace-track-expected.txt [new file with mode: 0644]
LayoutTests/webrtc/video-replace-track-to-null-expected.txt [new file with mode: 0644]
LayoutTests/webrtc/video-replace-track-to-null.html [new file with mode: 0644]
LayoutTests/webrtc/video-replace-track.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/mediastream/RTCPeerConnection.cpp
Source/WebCore/Modules/mediastream/RTCPeerConnection.h
Source/WebCore/Modules/mediastream/RTCRtpSender.cpp
Source/WebCore/Modules/mediastream/RTCRtpSender.h
Source/WebCore/Modules/mediastream/RTCRtpSender.idl
Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCPeerConnectionBackend.cpp
Source/WebCore/Modules/mediastream/libwebrtc/LibWebRTCPeerConnectionBackend.h
Source/WebCore/platform/mediastream/mac/RealtimeOutgoingAudioSource.cpp
Source/WebCore/platform/mediastream/mac/RealtimeOutgoingAudioSource.h
Source/WebCore/platform/mediastream/mac/RealtimeOutgoingVideoSource.cpp
Source/WebCore/platform/mediastream/mac/RealtimeOutgoingVideoSource.h
Source/WebCore/platform/mock/MockRealtimeVideoSource.cpp

index db26c66..aef6a34 100644 (file)
@@ -1,3 +1,17 @@
+2017-03-24  Youenn Fablet  <youenn@apple.com>
+
+        Add libwebrtc backend support for RTCRtpSender::replaceTrack
+        https://bugs.webkit.org/show_bug.cgi?id=169841
+
+        Reviewed by Alex Christensen.
+
+        * webrtc/audio-replace-track-expected.txt: Added.
+        * webrtc/audio-replace-track.html: Added.
+        * webrtc/video-replace-track-expected.txt: Added.
+        * webrtc/video-replace-track.html: Added.
+        * webrtc/video-replace-track-to-null-expected.txt: Added.
+        * webrtc/video-replace-track-to-null.html: Added.
+
 2017-03-24  Ryan Haddad  <ryanhaddad@apple.com>
 
         Update TestExpectations for media/restore-from-page-cache.html.
diff --git a/LayoutTests/webrtc/audio-replace-track-expected.txt b/LayoutTests/webrtc/audio-replace-track-expected.txt
new file mode 100644 (file)
index 0000000..47b9b81
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Replacing audio track from a peer connection 
+
diff --git a/LayoutTests/webrtc/audio-replace-track.html b/LayoutTests/webrtc/audio-replace-track.html
new file mode 100644 (file)
index 0000000..a3fe4bc
--- /dev/null
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Testing local audio capture playback causes "playing" event to fire</title>
+    <script src="../resources/testharness.js"></script>
+    <script src="../resources/testharnessreport.js"></script>
+    <script src ="routines.js"></script>
+    <script>
+    promise_test((test) => {
+        if (window.testRunner)
+            testRunner.setUserMediaPermission(true);
+
+        var sender;
+        var firsStream;
+        var secondStream;
+        var remoteStream;
+        return navigator.mediaDevices.getUserMedia({ audio: { sampleRate: { exact: 48000 } } }).then((stream) => {
+            firstStream = stream;
+            return navigator.mediaDevices.getUserMedia({ audio: { sampleRate: { exact: 48000 } } });
+        }).then((stream) => {
+            secondStream = stream;
+            if (window.internals)
+                internals.useMockRTCPeerConnectionFactory("TwoRealPeerConnections");
+            return new Promise((resolve, reject) => {
+                createConnections((firstConnection) => {
+                    sender = firstConnection.addTrack(firstStream.getAudioTracks()[0], firstStream);
+                }, (secondConnection) => {
+                    secondConnection.ontrack = (trackEvent) => { resolve(trackEvent.streams[0]); };
+                });
+                setTimeout(() => reject("Test timed out"), 5000);
+            });
+        }).then((stream) => {
+            remoteStream = stream;
+            return analyseAudio(remoteStream, 1000);
+        }).then((results) => {
+            assert_true(results.heardHum, "heard hum");
+            return sender.replaceTrack(secondStream.getAudioTracks()[0], secondStream);
+        }).then(() => {
+            assert_true(sender.track === secondStream.getAudioTracks()[0]);
+            return waitFor(500);
+        }).then((results) => {
+            return analyseAudio(remoteStream, 1000);
+        }).then((results) => {
+            assert_true(results.heardHum, "heard hum");
+        });
+    }, "Replacing audio track from a peer connection");
+    </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/LayoutTests/webrtc/video-replace-track-expected.txt b/LayoutTests/webrtc/video-replace-track-expected.txt
new file mode 100644 (file)
index 0000000..8359913
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS Switching from front to back camera 
+FAIL Switching from front to back camera, with lower resolution assert_true: backStream should be smaller expected true got false
+FAIL Switching from front to back camera, with higher resolution assert_true: front stream should be smaller expected true got false
+
diff --git a/LayoutTests/webrtc/video-replace-track-to-null-expected.txt b/LayoutTests/webrtc/video-replace-track-to-null-expected.txt
new file mode 100644 (file)
index 0000000..e8cad01
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Stopping sending video using replaceTrack 
+
diff --git a/LayoutTests/webrtc/video-replace-track-to-null.html b/LayoutTests/webrtc/video-replace-track-to-null.html
new file mode 100644 (file)
index 0000000..c904d1c
--- /dev/null
@@ -0,0 +1,103 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Testing basic video exchange from offerer to receiver</title>
+        <script src="../resources/testharness.js"></script>
+        <script src="../resources/testharnessreport.js"></script>
+    </head>
+    <body>
+<div id="log"></div>
+        <video id="video" autoplay=""></video>
+        <canvas id="canvas" width="640" height="480"></canvas>
+        <script src ="routines.js"></script>
+        <script>
+video = document.getElementById("video");
+canvas = document.getElementById("canvas");
+
+function grabImagePixels()
+{
+    canvas.width = video.videoWidth;
+    canvas.height = video.videoHeight;
+    canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
+
+    imageData = canvas.getContext('2d').getImageData(20, 20, 100, 100);
+    return imageData.data;
+ }
+
+var firstFrameData;
+
+function storeFrame()
+{
+    firstFrameData = grabImagePixels();
+}
+
+function testCameraImage()
+{
+    data = grabImagePixels();
+
+    assert_true(data[0] < 20);
+    assert_true(data[1] < 20);
+    assert_true(data[2] < 20);
+
+    var same = true;
+    for (var cptr = 0; cptr < data.length; ++cptr) {
+        if (data[cptr] != firstFrameData[cptr]) {
+            same = false;
+            break;
+        }
+    }
+    assert_false(same);
+}
+
+function testStoppedImage()
+{
+    assert_array_equals(grabImagePixels(), firstFrameData);
+}
+
+promise_test((test) => {
+    if (window.testRunner)
+        testRunner.setUserMediaPermission(true);
+
+    var sender;
+    var frontStream;
+    return navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
+        frontStream = stream;
+    }).then(() => {
+        return new Promise((resolve, reject) => {
+            if (window.internals)
+                internals.useMockRTCPeerConnectionFactory("TwoRealPeerConnections");
+
+            createConnections((firstConnection) => {
+                sender = firstConnection.addTrack(frontStream.getVideoTracks()[0], frontStream);
+            }, (secondConnection) => {
+                secondConnection.ontrack = (trackEvent) => {
+                    resolve(trackEvent.streams[0]);
+                };
+            });
+            setTimeout(() => reject("Test timed out"), 5000);
+        });
+    }).then((remoteStream) => {
+        video.srcObject = remoteStream;
+        return video.play();
+    }).then(() => {
+        storeFrame();
+        return waitFor(100);
+    }).then(() => {
+        testCameraImage();
+    }).then(() => {
+        promise = sender.replaceTrack(null);
+        assert_true(!!sender.track);
+        return promise;
+    }).then(() => {
+        return waitFor(100);
+    }).then(() => {
+        storeFrame();
+        return waitFor(100);
+    }).then(() => {
+        testStoppedImage();
+    });
+}, "Stopping sending video using replaceTrack");
+        </script>
+    </body>
+</html>
diff --git a/LayoutTests/webrtc/video-replace-track.html b/LayoutTests/webrtc/video-replace-track.html
new file mode 100644 (file)
index 0000000..ffd8a7c
--- /dev/null
@@ -0,0 +1,181 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Testing basic video exchange from offerer to receiver</title>
+        <script src="../resources/testharness.js"></script>
+        <script src="../resources/testharnessreport.js"></script>
+    </head>
+    <body>
+<div id="log"></div>
+        <video id="video" autoplay=""></video>
+        <canvas id="canvas" width="640" height="480"></canvas>
+        <script src ="routines.js"></script>
+        <script>
+video = document.getElementById("video");
+canvas = document.getElementById("canvas");
+
+function grabImagePixels()
+{
+    canvas.width = video.videoWidth;
+    canvas.height = video.videoHeight;
+    canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
+
+    imageData = canvas.getContext('2d').getImageData(20, 20, 2, 2);
+    return imageData.data;
+ }
+
+function testFrontCameraImage()
+{
+    data = grabImagePixels();
+
+    assert_true(data[0] < 20);
+    assert_true(data[1] < 20);
+    assert_true(data[2] < 20);
+}
+
+function testBackCameraImage()
+{
+    data = grabImagePixels();
+
+    assert_true(data[0] > 100);
+    assert_true(data[1] > 100);
+    assert_true(data[2] > 100);
+
+    assert_true(data[0] < 200);
+    assert_true(data[1] < 200);
+    assert_true(data[2] < 200);
+}
+
+promise_test((test) => {
+    if (window.testRunner)
+        testRunner.setUserMediaPermission(true);
+
+    var sender;
+    var frontStream;
+    var backStream;
+    return navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: ["user"] } } }).then((stream) => {
+        frontStream = stream;
+        return navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: ["environment"] } } });
+    }).then((stream) => {
+        backStream = stream;
+    }).then(() => {
+        return new Promise((resolve, reject) => {
+            if (window.internals)
+                internals.useMockRTCPeerConnectionFactory("TwoRealPeerConnections");
+
+            createConnections((firstConnection) => {
+                sender = firstConnection.addTrack(frontStream.getVideoTracks()[0], frontStream);
+            }, (secondConnection) => {
+                secondConnection.ontrack = (trackEvent) => {
+                    resolve(trackEvent.streams[0]);
+                };
+            });
+            setTimeout(() => reject("Test timed out"), 5000);
+        });
+    }).then((remoteStream) => {
+        video.srcObject = remoteStream;
+        return video.play();
+    }).then(() => {
+        testFrontCameraImage();
+    }).then(() => {
+        var currentTrack = sender.track;
+        promise = sender.replaceTrack(backStream.getVideoTracks()[0]);
+        assert_true(currentTrack === sender.track);
+        return promise;
+    }).then(() => {
+        assert_true(sender.track === backStream.getVideoTracks()[0]);
+        return waitFor(500);
+    }).then(() => {
+        testBackCameraImage();
+    });
+}, "Switching from front to back camera");
+
+promise_test((test) => {
+    if (window.testRunner)
+        testRunner.setUserMediaPermission(true);
+
+    var sender;
+    var frontStream;
+    var backStream;
+
+    return navigator.mediaDevices.getUserMedia({ video: { height: { min: 400 }, facingMode: { exact: ["user"] } } }).then((stream) => {
+        frontStream = stream;
+        return navigator.mediaDevices.getUserMedia({ video: { height: { max: 400 }, facingMode: { exact: ["environment"] } } });
+    }).then((stream) => {
+        backStream = stream;
+        assert_true(frontStream.getVideoTracks()[0].getSettings().height >= 400, "frontStream should be bigger");
+        assert_true(backStream.getVideoTracks()[0].getSettings().height < 400, "backStream should be smaller");
+    }).then(() => {
+        return new Promise((resolve, reject) => {
+            if (window.internals)
+                internals.useMockRTCPeerConnectionFactory("TwoRealPeerConnections");
+
+            createConnections((firstConnection) => {
+                sender = firstConnection.addTrack(frontStream.getVideoTracks()[0], frontStream);
+            }, (secondConnection) => {
+                secondConnection.ontrack = (trackEvent) => {
+                    resolve(trackEvent.streams[0]);
+                };
+            });
+            setTimeout(() => reject("Test timed out"), 5000);
+        });
+    }).then((remoteStream) => {
+        video.srcObject = remoteStream;
+        return video.play();
+    }).then(() => {
+        testFrontCameraImage();
+    }).then(() => {
+        return sender.replaceTrack(backStream.getVideoTracks()[0]);
+    }).then(() => {
+        return waitFor(500);
+    }).then(() => {
+        testBackCameraImage();
+    });
+}, "Switching from front to back camera, with lower resolution");
+
+promise_test((test) => {
+    if (window.testRunner)
+        testRunner.setUserMediaPermission(true);
+
+    var sender;
+    var frontStream;
+    var backStream;
+
+    return navigator.mediaDevices.getUserMedia({ video: { height: { max: 400 }, facingMode: { exact: ["user"] } } }).then((stream) => {
+        frontStream = stream;
+        return navigator.mediaDevices.getUserMedia({ video: { height: { min: 400 }, facingMode: { exact: ["environment"] } } });
+    }).then((stream) => {
+        backStream = stream;
+        assert_true(frontStream.getVideoTracks()[0].getSettings().height < 400, "front stream should be smaller");
+        assert_true(backStream.getVideoTracks()[0].getSettings().height >= 400, "back stream should be bigger");
+    }).then(() => {
+        return new Promise((resolve, reject) => {
+            if (window.internals)
+                internals.useMockRTCPeerConnectionFactory("TwoRealPeerConnections");
+
+            createConnections((firstConnection) => {
+                sender = firstConnection.addTrack(frontStream.getVideoTracks()[0], frontStream);
+            }, (secondConnection) => {
+                secondConnection.ontrack = (trackEvent) => {
+                    resolve(trackEvent.streams[0]);
+                };
+            });
+            setTimeout(() => reject("Test timed out"), 5000);
+        });
+    }).then((remoteStream) => {
+        video.srcObject = remoteStream;
+        return video.play();
+    }).then(() => {
+        testFrontCameraImage();
+    }).then(() => {
+        return promise_rejects(test, "InvalidModificationError", sender.replaceTrack(backStream.getVideoTracks()[0]));
+    }).then(() => {
+        return waitFor(500);
+    }).then(() => {
+        testBackCameraImage();
+    });
+}, "Switching from front to back camera, with higher resolution");
+        </script>
+    </body>
+</html>
index 95b1477..6cee3e4 100644 (file)
@@ -1,3 +1,37 @@
+2017-03-24  Youenn Fablet  <youenn@apple.com>
+
+        Add libwebrtc backend support for RTCRtpSender::replaceTrack
+        https://bugs.webkit.org/show_bug.cgi?id=169841
+
+        Reviewed by Alex Christensen.
+
+        Tests: webrtc/audio-replace-track.html
+               webrtc/video-replace-track.html
+
+        Adding support for replaceTrack for audio and video sources.
+        Replacing tracks will always succeed for audio sources.
+        For video tracks, it will only succeed if the video resolution is not greater.
+        LibWebRTCPeerConnectionBackend will delegate the track replacing by replacing the source of the outgoing sources with the source wrapped in the replacing track.
+
+        Video test is not fully passing as size constraints for mock video sources are not providing the right video stream resolution.
+
+        * Modules/mediastream/RTCRtpSender.cpp:
+        (WebCore::RTCRtpSender::replaceTrack):
+        * Modules/mediastream/RTCRtpSender.h:
+        * Modules/mediastream/RTCRtpSender.idl:
+        * Modules/mediastream/libwebrtc/LibWebRTCPeerConnectionBackend.cpp:
+        (WebCore::LibWebRTCPeerConnectionBackend::replaceTrack):
+        * Modules/mediastream/libwebrtc/LibWebRTCPeerConnectionBackend.h:
+        * platform/mediastream/mac/RealtimeOutgoingAudioSource.cpp:
+        (WebCore::RealtimeOutgoingAudioSource::setSource):
+        * platform/mediastream/mac/RealtimeOutgoingAudioSource.h:
+        * platform/mediastream/mac/RealtimeOutgoingVideoSource.cpp:
+        (WebCore::RealtimeOutgoingVideoSource::setSource):
+        * platform/mediastream/mac/RealtimeOutgoingVideoSource.h:
+        * platform/mock/MockRealtimeVideoSource.cpp:
+        (WebCore::MockRealtimeVideoSource::drawText):
+        (WebCore::MockRealtimeVideoSource::generateFrame):
+
 2017-03-24  Jon Lee  <jonlee@apple.com>
 
         Remove comment from RTCStatsReport.idl to convert ssrc to DOMString.
index e885f7c..3860daa 100644 (file)
@@ -496,9 +496,30 @@ void RTCPeerConnection::fireEvent(Event& event)
     dispatchEvent(event);
 }
 
-void RTCPeerConnection::replaceTrack(RTCRtpSender& sender, Ref<MediaStreamTrack>&& withTrack, DOMPromise<void>&& promise)
+void RTCPeerConnection::enqueueReplaceTrackTask(RTCRtpSender& sender, Ref<MediaStreamTrack>&& withTrack, DOMPromise<void>&& promise)
 {
-    m_backend->replaceTrack(sender, WTFMove(withTrack), WTFMove(promise));
+    scriptExecutionContext()->postTask([protectedSender = makeRef(sender), promise = WTFMove(promise), withTrack = WTFMove(withTrack)](ScriptExecutionContext&) mutable {
+        protectedSender->setTrack(WTFMove(withTrack));
+        promise.resolve();
+    });
+}
+
+void RTCPeerConnection::replaceTrack(RTCRtpSender& sender, RefPtr<MediaStreamTrack>&& withTrack, DOMPromise<void>&& promise)
+{
+    if (!withTrack) {
+        scriptExecutionContext()->postTask([protectedSender = makeRef(sender), promise = WTFMove(promise)](ScriptExecutionContext&) mutable {
+            protectedSender->setTrackToNull();
+            promise.resolve();
+        });
+        return;
+    }
+    
+    if (!sender.track()) {
+        enqueueReplaceTrackTask(sender, withTrack.releaseNonNull(), WTFMove(promise));
+        return;
+    }
+
+    m_backend->replaceTrack(sender, withTrack.releaseNonNull(), WTFMove(promise));
 }
 
 } // namespace WebCore
index 78d1a04..a475d72 100644 (file)
@@ -140,6 +140,8 @@ public:
     void disableICECandidateFiltering() { m_backend->disableICECandidateFiltering(); }
     void enableICECandidateFiltering() { m_backend->enableICECandidateFiltering(); }
 
+    void enqueueReplaceTrackTask(RTCRtpSender&, Ref<MediaStreamTrack>&&, DOMPromise<void>&&);
+
 private:
     RTCPeerConnection(ScriptExecutionContext&);
 
@@ -159,7 +161,7 @@ private:
     bool canSuspendForDocumentSuspension() const final;
 
     // RTCRtpSenderClient
-    void replaceTrack(RTCRtpSender&, Ref<MediaStreamTrack>&&, DOMPromise<void>&&) final;
+    void replaceTrack(RTCRtpSender&, RefPtr<MediaStreamTrack>&&, DOMPromise<void>&&) final;
 
     void updateConnectionState();
     bool doClose();
index 33b1417..b964063 100644 (file)
@@ -57,27 +57,37 @@ RTCRtpSender::RTCRtpSender(String&& trackKind, Vector<String>&& mediaStreamIds,
 {
 }
 
+void RTCRtpSender::setTrackToNull()
+{
+    ASSERT(m_track);
+    m_trackId = { };
+    m_track = nullptr;
+}
+
 void RTCRtpSender::setTrack(Ref<MediaStreamTrack>&& track)
 {
     ASSERT(!isStopped());
-    ASSERT(!m_track);
-    m_trackId = track->id();
+    if (!m_track)
+        m_trackId = track->id();
     m_track = WTFMove(track);
 }
 
-ExceptionOr<void> RTCRtpSender::replaceTrack(Ref<MediaStreamTrack>&& withTrack, DOMPromise<void>&& promise)
+void RTCRtpSender::replaceTrack(RefPtr<MediaStreamTrack>&& withTrack, DOMPromise<void>&& promise)
 {
     if (isStopped()) {
         promise.reject(INVALID_STATE_ERR);
-        return { };
+        return;
     }
 
-    if (m_trackKind != withTrack->kind())
-        return Exception { TypeError };
+    if (withTrack && m_trackKind != withTrack->kind()) {
+        promise.reject(TypeError);
+        return;
+    }
 
-    m_client->replaceTrack(*this, WTFMove(withTrack), WTFMove(promise));
+    if (!withTrack && m_track)
+        m_track->stopProducingData();
 
-    return { };
+    m_client->replaceTrack(*this, WTFMove(withTrack), WTFMove(promise));
 }
 
 } // namespace WebCore
index 92aabd6..2d2d794 100644 (file)
@@ -39,7 +39,7 @@ namespace WebCore {
 
 class RTCRtpSenderClient {
 public:
-    virtual void replaceTrack(RTCRtpSender&, Ref<MediaStreamTrack>&&, DOMPromise<void>&&) = 0;
+    virtual void replaceTrack(RTCRtpSender&, RefPtr<MediaStreamTrack>&&, DOMPromise<void>&&) = 0;
 
     virtual ~RTCRtpSenderClient() { }
 };
@@ -58,8 +58,9 @@ public:
     bool isStopped() const { return !m_client; }
     void stop() { m_client = nullptr; }
     void setTrack(Ref<MediaStreamTrack>&&);
+    void setTrackToNull();
 
-    ExceptionOr<void> replaceTrack(Ref<MediaStreamTrack>&&, DOMPromise<void>&&);
+    void replaceTrack(RefPtr<MediaStreamTrack>&&, DOMPromise<void>&&);
 
 private:
     RTCRtpSender(String&& trackKind, Vector<String>&& mediaStreamIds, RTCRtpSenderClient&);
index 0786746..7623eed 100644 (file)
@@ -39,5 +39,5 @@
     // FIXME 169662: missing getCapabilities
     // FIXME 169662: missing setParameters
     // FIXME 169662: missing getParameters
-    [MayThrowException] Promise<void> replaceTrack(MediaStreamTrack withTrack);
+    Promise<void> replaceTrack(MediaStreamTrack? withTrack);
 };
index 2530b10..ece3f4a 100644 (file)
@@ -28,6 +28,7 @@
 #if USE(LIBWEBRTC)
 
 #include "Document.h"
+#include "ExceptionCode.h"
 #include "IceCandidate.h"
 #include "JSRTCStatsReport.h"
 #include "LibWebRTCDataChannelHandler.h"
@@ -321,6 +322,48 @@ void LibWebRTCPeerConnectionBackend::addRemoteStream(Ref<MediaStream>&& mediaStr
     m_remoteStreams.append(WTFMove(mediaStream));
 }
 
+void LibWebRTCPeerConnectionBackend::replaceTrack(RTCRtpSender& sender, Ref<MediaStreamTrack>&& track, DOMPromise<void>&& promise)
+{
+    ASSERT(sender.track());
+    auto* currentTrack = sender.track();
+
+    ASSERT(currentTrack->source().type() == track->source().type());
+    switch (currentTrack->source().type()) {
+    case RealtimeMediaSource::Type::None:
+        ASSERT_NOT_REACHED();
+        promise.reject(INVALID_MODIFICATION_ERR);
+        break;
+    case RealtimeMediaSource::Type::Audio: {
+        for (auto& audioSource : m_audioSources) {
+            if (&audioSource->source() == &currentTrack->source()) {
+                if (!audioSource->setSource(track->source())) {
+                    promise.reject(INVALID_MODIFICATION_ERR);
+                    return;
+                }
+                connection().enqueueReplaceTrackTask(sender, WTFMove(track), WTFMove(promise));
+                return;
+            }
+        }
+        promise.reject(INVALID_MODIFICATION_ERR);
+        break;
+    }
+    case RealtimeMediaSource::Type::Video: {
+        for (auto& videoSource : m_videoSources) {
+            if (&videoSource->source() == &currentTrack->source()) {
+                if (!videoSource->setSource(track->source())) {
+                    promise.reject(INVALID_MODIFICATION_ERR);
+                    return;
+                }
+                connection().enqueueReplaceTrackTask(sender, WTFMove(track), WTFMove(promise));
+                return;
+            }
+        }
+        promise.reject(INVALID_MODIFICATION_ERR);
+        break;
+    }
+    }
+}
+
 } // namespace WebCore
 
 #endif // USE(LIBWEBRTC)
index 85426cc..b632d8f 100644 (file)
@@ -69,8 +69,7 @@ private:
     RefPtr<RTCSessionDescription> currentRemoteDescription() const final;
     RefPtr<RTCSessionDescription> pendingRemoteDescription() const final;
 
-    // FIXME: API to implement for real
-    void replaceTrack(RTCRtpSender&, Ref<MediaStreamTrack>&&, DOMPromise<void>&&) final { }
+    void replaceTrack(RTCRtpSender&, Ref<MediaStreamTrack>&&, DOMPromise<void>&&) final;
 
     void emulatePlatformEvent(const String&) final { }
 
index 3687a20..c544e0b 100644 (file)
@@ -51,6 +51,14 @@ RealtimeOutgoingAudioSource::RealtimeOutgoingAudioSource(Ref<RealtimeMediaSource
     m_audioSource->addObserver(*this);
 }
 
+bool RealtimeOutgoingAudioSource::setSource(Ref<RealtimeMediaSource>&& newSource)
+{
+    m_audioSource->removeObserver(*this);
+    m_audioSource = WTFMove(newSource);
+    m_audioSource->addObserver(*this);
+    return true;
+}
+
 void RealtimeOutgoingAudioSource::stop()
 {
     m_audioSource->removeObserver(*this);
index 26ee9a3..32d49e5 100644 (file)
@@ -51,6 +51,9 @@ public:
     void setTrack(rtc::scoped_refptr<webrtc::AudioTrackInterface>&& track) { m_track = WTFMove(track); }
     void stop();
 
+    bool setSource(Ref<RealtimeMediaSource>&&);
+    RealtimeMediaSource& source() const { return m_audioSource.get(); }
+
 private:
     explicit RealtimeOutgoingAudioSource(Ref<RealtimeMediaSource>&&);
 
index c11f9e2..926ef0b 100644 (file)
@@ -46,6 +46,22 @@ RealtimeOutgoingVideoSource::RealtimeOutgoingVideoSource(Ref<RealtimeMediaSource
     m_videoSource->addObserver(*this);
 }
 
+bool RealtimeOutgoingVideoSource::setSource(Ref<RealtimeMediaSource>&& newSource)
+{
+    if (!m_initialSettings)
+        m_initialSettings = m_videoSource->settings();
+
+    auto newSettings = newSource->settings();
+
+    if (m_initialSettings->width() < newSettings.width() || m_initialSettings->height() < newSettings.height())
+        return false;
+
+    m_videoSource->removeObserver(*this);
+    m_videoSource = WTFMove(newSource);
+    m_videoSource->addObserver(*this);
+    return true;
+}
+
 void RealtimeOutgoingVideoSource::stop()
 {
     m_videoSource->removeObserver(*this);
index 6cf0208..c200ec6 100644 (file)
@@ -36,6 +36,7 @@
 #include <webrtc/base/optional.h>
 #include <webrtc/common_video/include/i420_buffer_pool.h>
 #include <webrtc/media/base/videosinkinterface.h>
+#include <wtf/Optional.h>
 #include <wtf/ThreadSafeRefCounted.h>
 
 namespace WebCore {
@@ -46,6 +47,8 @@ public:
     ~RealtimeOutgoingVideoSource() { stop(); }
 
     void stop();
+    bool setSource(Ref<RealtimeMediaSource>&&);
+    RealtimeMediaSource& source() const { return m_videoSource.get(); }
 
     int AddRef() const final { ref(); return refCount(); }
     int Release() const final { deref(); return refCount(); }
@@ -82,6 +85,7 @@ private:
     Ref<RealtimeMediaSource> m_videoSource;
     bool m_enabled { true };
     bool m_muted { false };
+    std::optional<RealtimeMediaSourceSettings> m_initialSettings;
 };
 
 } // namespace WebCore
index 54f3021..b17d899 100644 (file)
@@ -327,11 +327,11 @@ void MockRealtimeVideoSource::drawText(GraphicsContext& context)
     FloatPoint bipBopLocation(size.width() * .6, size.height() * .6);
     unsigned frameMod = m_frameNumber % 60;
     if (frameMod <= 15) {
-        context.setFillColor(Color::gray);
+        context.setFillColor(Color::cyan);
         String bip(ASCIILiteral("Bip"));
         context.drawText(m_bipBopFont, TextRun(StringView(bip)), bipBopLocation);
     } else if (frameMod > 30 && frameMod <= 45) {
-        context.setFillColor(Color::white);
+        context.setFillColor(Color::yellow);
         String bop(ASCIILiteral("Bop"));
         context.drawText(m_bipBopFont, TextRun(StringView(bop)), bipBopLocation);
     }
@@ -348,7 +348,7 @@ void MockRealtimeVideoSource::generateFrame()
 
     IntSize size = this->size();
     FloatRect frameRect(FloatPoint(), size);
-    context.fillRect(FloatRect(FloatPoint(), size), Color::black);
+    context.fillRect(FloatRect(FloatPoint(), size), facingMode() ==  RealtimeMediaSourceSettings::User ? Color::black : Color::gray);
 
     if (!m_muted && m_enabled) {
         drawText(context);