diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/webrtc/RTCRtpSender-replaceTrack.https.html | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCRtpSender-replaceTrack.https.html')
-rw-r--r-- | testing/web-platform/tests/webrtc/RTCRtpSender-replaceTrack.https.html | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCRtpSender-replaceTrack.https.html b/testing/web-platform/tests/webrtc/RTCRtpSender-replaceTrack.https.html new file mode 100644 index 0000000000..bec44c53e4 --- /dev/null +++ b/testing/web-platform/tests/webrtc/RTCRtpSender-replaceTrack.https.html @@ -0,0 +1,338 @@ +<!doctype html> +<meta charset=utf-8> +<meta name="timeout" content="long"> +<title>RTCRtpSender.prototype.replaceTrack</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="RTCPeerConnection-helper.js"></script> +<script> + 'use strict'; + + // Test is based on the following editor draft: + // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html + + /* + 5.2. RTCRtpSender Interface + interface RTCRtpSender { + readonly attribute MediaStreamTrack? track; + Promise<void> replaceTrack(MediaStreamTrack? withTrack); + ... + }; + + replaceTrack + Attempts to replace the track being sent with another track provided + (or with a null track), without renegotiation. + */ + + /* + 5.2. replaceTrack + 4. If connection's [[isClosed]] slot is true, return a promise rejected + with a newly created InvalidStateError and abort these steps. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + const [track] = stream.getTracks(); + + const transceiver = pc.addTransceiver('audio'); + const { sender } = transceiver; + pc.close(); + + return promise_rejects_dom(t, 'InvalidStateError', + sender.replaceTrack(track)); + }, 'Calling replaceTrack on closed connection should reject with InvalidStateError'); + + /* + 5.2. replaceTrack + 7. If withTrack is non-null and withTrack.kind differs from the + transceiver kind of transceiver, return a promise rejected with a + newly created TypeError. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream = await getNoiseStream({video: true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + const [track] = stream.getTracks(); + + const transceiver = pc.addTransceiver('audio'); + const { sender } = transceiver; + + return promise_rejects_js(t, TypeError, + sender.replaceTrack(track)); + }, 'Calling replaceTrack with track of different kind should reject with TypeError'); + + /* + 5.2. replaceTrack + 5. If transceiver.stopped is true, return a promise rejected with a newly + created InvalidStateError. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + const [track] = stream.getTracks(); + + const transceiver = pc.addTransceiver('audio'); + const { sender } = transceiver; + transceiver.stop(); + + return promise_rejects_dom(t, 'InvalidStateError', + sender.replaceTrack(track)); + }, 'Calling replaceTrack on stopped sender should reject with InvalidStateError'); + + /* + 5.2. replaceTrack + 8. If transceiver is not yet associated with a media description [JSEP] + (section 3.4.1.), then set sender's track attribute to withTrack, and + return a promise resolved with undefined. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + const [track] = stream.getTracks(); + + const transceiver = pc.addTransceiver('audio'); + const { sender } = transceiver; + assert_equals(sender.track, null); + + return sender.replaceTrack(track) + .then(() => { + assert_equals(sender.track, track); + }); + }, 'Calling replaceTrack on sender with null track and not set to session description should resolve with sender.track set to given track'); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream1 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop())); + const [track1] = stream1.getTracks(); + const stream2 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop())); + const [track2] = stream2.getTracks(); + + const transceiver = pc.addTransceiver(track1); + const { sender } = transceiver; + + assert_equals(sender.track, track1); + + return sender.replaceTrack(track2) + .then(() => { + assert_equals(sender.track, track2); + }); + }, 'Calling replaceTrack on sender not set to session description should resolve with sender.track set to given track'); + + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + const [track] = stream.getTracks(); + + const transceiver = pc.addTransceiver(track); + const { sender } = transceiver; + + assert_equals(sender.track, track); + + return sender.replaceTrack(null) + .then(() => { + assert_equals(sender.track, null); + }); + }, 'Calling replaceTrack(null) on sender not set to session description should resolve with sender.track set to null'); + + /* + 5.2. replaceTrack + 10. Run the following steps in parallel: + 1. Determine if negotiation is needed to transmit withTrack in place + of the sender's existing track. + + Negotiation is not needed if withTrack is null. + + 3. Queue a task that runs the following steps: + 2. Set sender's track attribute to withTrack. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); + const [track] = stream.getTracks(); + + const transceiver = pc.addTransceiver(track); + const { sender } = transceiver; + + assert_equals(sender.track, track); + + return pc.createOffer() + .then(offer => pc.setLocalDescription(offer)) + .then(() => sender.replaceTrack(null)) + .then(() => { + assert_equals(sender.track, null); + }); + }, 'Calling replaceTrack(null) on sender set to session description should resolve with sender.track set to null'); + + /* + 5.2. replaceTrack + 10. Run the following steps in parallel: + 1. Determine if negotiation is needed to transmit withTrack in place + of the sender's existing track. + + Negotiation is not needed if the sender's existing track is + ended (which appears as though the track was muted). + + 3. Queue a task that runs the following steps: + 2. Set sender's track attribute to withTrack. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream1 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop())); + const [track1] = stream1.getTracks(); + const stream2 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop())); + const [track2] = stream1.getTracks(); + + const transceiver = pc.addTransceiver(track1); + const { sender } = transceiver; + assert_equals(sender.track, track1); + + track1.stop(); + + return pc.createOffer() + .then(offer => pc.setLocalDescription(offer)) + .then(() => sender.replaceTrack(track2)) + .then(() => { + assert_equals(sender.track, track2); + }); + }, 'Calling replaceTrack on sender with stopped track and and set to session description should resolve with sender.track set to given track'); + + /* + 5.2. replaceTrack + 10. Run the following steps in parallel: + 1. Determine if negotiation is needed to transmit withTrack in place + of the sender's existing track. + + (tracks generated with default parameters *should* be similar + enough to not require re-negotiation) + + 3. Queue a task that runs the following steps: + 2. Set sender's track attribute to withTrack. + */ + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + const stream1 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop())); + const [track1] = stream1.getTracks(); + const stream2 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop())); + const [track2] = stream1.getTracks(); + + const transceiver = pc.addTransceiver(track1); + const { sender } = transceiver; + assert_equals(sender.track, track1); + + return pc.createOffer() + .then(offer => pc.setLocalDescription(offer)) + .then(() => sender.replaceTrack(track2)) + .then(() => { + assert_equals(sender.track, track2); + }); + }, 'Calling replaceTrack on sender with similar track and and set to session description should resolve with sender.track set to new track'); + + /* + TODO + 5.2. replaceTrack + To avoid track identifiers changing on the remote receiving end when + a track is replaced, the sender must retain the original track + identifier and stream associations and use these in subsequent + negotiations. + + Non-Testable + 5.2. replaceTrack + 10. Run the following steps in parallel: + 1. Determine if negotiation is needed to transmit withTrack in place + of the sender's existing track. + + Ignore which MediaStream the track resides in and the id attribute + of the track in this determination. + + If negotiation is needed, then reject p with a newly created + InvalidModificationError and abort these steps. + + 2. If withTrack is null, have the sender stop sending, without + negotiating. Otherwise, have the sender switch seamlessly to + transmitting withTrack instead of the sender's existing track, + without negotiating. + 3. Queue a task that runs the following steps: + 1. If connection's [[isClosed]] slot is true, abort these steps. + */ + + promise_test(async t => { + const v = document.createElement('video'); + v.autoplay = true; + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + const stream1 = await getNoiseStream({video: {signal: 20}}); + t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop())); + const [track1] = stream1.getTracks(); + const stream2 = await getNoiseStream({video: {signal: 250}}); + t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop())); + const [track2] = stream2.getTracks(); + const sender = pc1.addTrack(track1); + pc2.ontrack = (e) => { + v.srcObject = new MediaStream([e.track]); + }; + const metadataToBeLoaded = new Promise((resolve) => { + v.addEventListener('loadedmetadata', () => { + resolve(); + }); + }); + exchangeIceCandidates(pc1, pc2); + exchangeOfferAnswer(pc1, pc2); + await metadataToBeLoaded; + await detectSignal(t, v, 20); + await sender.replaceTrack(track2); + await detectSignal(t, v, 250); + }, 'ReplaceTrack transmits the new track not the old track'); + + promise_test(async t => { + const v = document.createElement('video'); + v.autoplay = true; + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + const stream1 = await getNoiseStream({video: {signal: 20}}); + t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop())); + const [track1] = stream1.getTracks(); + const stream2 = await getNoiseStream({video: {signal: 250}}); + t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop())); + const [track2] = stream2.getTracks(); + const sender = pc1.addTrack(track1); + pc2.ontrack = (e) => { + v.srcObject = new MediaStream([e.track]); + }; + const metadataToBeLoaded = new Promise((resolve) => { + v.addEventListener('loadedmetadata', () => { + resolve(); + }); + }); + exchangeIceCandidates(pc1, pc2); + exchangeOfferAnswer(pc1, pc2); + await metadataToBeLoaded; + await detectSignal(t, v, 20); + await sender.replaceTrack(null); + await sender.replaceTrack(track2); + await detectSignal(t, v, 250); + }, 'ReplaceTrack null -> new track transmits the new track'); +</script> |