summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-addTrack.https.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-addTrack.https.html')
-rw-r--r--testing/web-platform/tests/webrtc/RTCPeerConnection-addTrack.https.html394
1 files changed, 394 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-addTrack.https.html b/testing/web-platform/tests/webrtc/RTCPeerConnection-addTrack.https.html
new file mode 100644
index 0000000000..91665822c4
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-addTrack.https.html
@@ -0,0 +1,394 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.addTrack</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src='../mediacapture-streams/permission-helper.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
+
+ // The following helper functions are called from RTCPeerConnection-helper.js:
+ // getNoiseStream()
+
+ /*
+ 5.1. RTCPeerConnection Interface Extensions
+ partial interface RTCPeerConnection {
+ ...
+ sequence<RTCRtpSender> getSenders();
+ sequence<RTCRtpReceiver> getReceivers();
+ sequence<RTCRtpTransceiver> getTransceivers();
+ RTCRtpSender addTrack(MediaStreamTrack track,
+ MediaStream... streams);
+ RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
+ optional RTCRtpTransceiverInit init);
+ };
+
+ Note
+ While addTrack checks if the MediaStreamTrack given as an argument is
+ already being sent to avoid sending the same MediaStreamTrack twice,
+ the other ways do not, allowing the same MediaStreamTrack to be sent
+ several times simultaneously.
+ */
+
+ /*
+ 5.1. addTrack
+ 4. If connection's [[isClosed]] slot is true, throw an 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();
+
+ pc.close();
+ assert_throws_dom('InvalidStateError', () => pc.addTrack(track, stream))
+ }, 'addTrack when pc is closed should throw InvalidStateError');
+
+ /*
+ 5.1. addTrack
+ 8. If sender is null, run the following steps:
+ 1. Create an RTCRtpSender with track and streams and let sender be
+ the result.
+ 2. Create an RTCRtpReceiver with track.kind as kind and let receiver
+ be the result.
+ 3. Create an RTCRtpTransceiver with sender and receiver and let
+ transceiver be the result.
+ 4. Add transceiver to connection's set of transceivers.
+ */
+ 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 sender = pc.addTrack(track);
+
+ assert_true(sender instanceof RTCRtpSender,
+ 'Expect sender to be instance of RTCRtpSender');
+
+ assert_equals(sender.track, track,
+ `Expect sender's track to be the added track`);
+
+ const transceivers = pc.getTransceivers();
+ assert_equals(transceivers.length, 1,
+ 'Expect only one transceiver with sender added');
+
+ const [transceiver] = transceivers;
+ assert_equals(transceiver.sender, sender);
+
+ assert_array_equals([sender], pc.getSenders(),
+ 'Expect only one sender with given track added');
+
+ const { receiver } = transceiver;
+ assert_equals(receiver.track.kind, 'audio');
+ assert_array_equals([transceiver.receiver], pc.getReceivers(),
+ 'Expect only one receiver associated with transceiver added');
+ }, 'addTrack with single track argument and no stream should succeed');
+
+ 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 sender = pc.addTrack(track, stream);
+
+ assert_true(sender instanceof RTCRtpSender,
+ 'Expect sender to be instance of RTCRtpSender');
+
+ assert_equals(sender.track, track,
+ `Expect sender's track to be the added track`);
+ }, 'addTrack with single track argument and single stream should succeed');
+
+ 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 stream2 = new MediaStream([track]);
+ const sender = pc.addTrack(track, stream, stream2);
+
+ assert_true(sender instanceof RTCRtpSender,
+ 'Expect sender to be instance of RTCRtpSender');
+
+ assert_equals(sender.track, track,
+ `Expect sender's track to be the added track`);
+ }, 'addTrack with single track argument and multiple streams should succeed');
+
+ /*
+ 5.1. addTrack
+ 5. Let senders be the result of executing the CollectSenders algorithm.
+ If an RTCRtpSender for track already exists in senders, throw an
+ InvalidAccessError.
+ */
+ 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();
+
+ pc.addTrack(track, stream);
+ assert_throws_dom('InvalidAccessError', () => pc.addTrack(track, stream));
+ }, 'Adding the same track multiple times should throw InvalidAccessError');
+
+ /*
+ 5.1. addTrack
+ 6. The steps below describe how to determine if an existing sender can
+ be reused.
+
+ If any RTCRtpSender object in senders matches all the following
+ criteria, let sender be that object, or null otherwise:
+ - The sender's track is null.
+ - The transceiver kind of the RTCRtpTransceiver, associated with
+ the sender, matches track's kind.
+ - The sender has never been used to send. More precisely, the
+ RTCRtpTransceiver associated with the sender has never had a
+ currentDirection of sendrecv or sendonly.
+ 7. If sender is not null, run the following steps to use that sender:
+ 1. Set sender.track to track.
+ 3. Enable sending direction on the RTCRtpTransceiver associated
+ with sender.
+ */
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const transceiver = pc.addTransceiver('audio', { direction: 'recvonly' });
+ assert_equals(transceiver.sender.track, null);
+ assert_equals(transceiver.direction, 'recvonly');
+
+ await setMediaPermission("granted", ["microphone"]);
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+ const sender = pc.addTrack(track);
+
+ assert_equals(sender, transceiver.sender);
+ assert_equals(sender.track, track);
+ assert_equals(transceiver.direction, 'sendrecv');
+ assert_array_equals([sender], pc.getSenders());
+ }, 'addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const transceiver = pc.addTransceiver('audio');
+ assert_equals(transceiver.sender.track, null);
+ assert_equals(transceiver.direction, 'sendrecv');
+
+ const stream = await getNoiseStream({audio: true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+ const sender = pc.addTrack(track);
+
+ assert_equals(sender.track, track);
+ assert_equals(sender, transceiver.sender);
+ }, 'addTrack with existing sender that has not been used to send should reuse the sender');
+
+ promise_test(async t => {
+ const caller = new RTCPeerConnection();
+ t.add_cleanup(() => caller.close());
+ const callee = new RTCPeerConnection();
+ t.add_cleanup(() => callee.close());
+
+ const stream = await getNoiseStream({audio: true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+ const transceiver = caller.addTransceiver(track);
+ {
+ const offer = await caller.createOffer();
+ await caller.setLocalDescription(offer);
+ await callee.setRemoteDescription(offer);
+ const answer = await callee.createAnswer();
+ await callee.setLocalDescription(answer);
+ await caller.setRemoteDescription(answer);
+ }
+ assert_equals(transceiver.currentDirection, 'sendonly');
+
+ caller.removeTrack(transceiver.sender);
+ {
+ const offer = await caller.createOffer();
+ await caller.setLocalDescription(offer);
+ await callee.setRemoteDescription(offer);
+ const answer = await callee.createAnswer();
+ await callee.setLocalDescription(answer);
+ await caller.setRemoteDescription(answer);
+ }
+ assert_equals(transceiver.direction, 'recvonly');
+ assert_equals(transceiver.currentDirection, 'inactive');
+
+ // |transceiver.sender| is currently not used for sending, but it should not
+ // be reused because it has been used for sending before.
+ const sender = caller.addTrack(track);
+ assert_true(sender != null);
+ assert_not_equals(sender, transceiver.sender);
+ }, 'addTrack with existing sender that has been used to send should create new sender');
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const transceiver = pc.addTransceiver('video', { direction: 'recvonly' });
+ assert_equals(transceiver.sender.track, null);
+ assert_equals(transceiver.direction, 'recvonly');
+
+ const stream = await getNoiseStream({audio: true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+ const sender = pc.addTrack(track);
+
+ assert_equals(sender.track, track);
+ assert_not_equals(sender, transceiver.sender);
+
+ const senders = pc.getSenders();
+ assert_equals(senders.length, 2,
+ 'Expect 2 senders added to connection');
+
+ assert_true(senders.includes(sender),
+ 'Expect senders list to include sender');
+
+ assert_true(senders.includes(transceiver.sender),
+ `Expect senders list to include first transceiver's sender`);
+ }, 'addTrack with existing sender with null track, different kind, and recvonly direction should create new sender');
+
+ /*
+ TODO
+ 5.1. addTrack
+ 3. Let streams be a list of MediaStream objects constructed from the
+ method's remaining arguments, or an empty list if the method was
+ called with a single argument.
+ 6. The steps below describe how to determine if an existing sender can
+ be reused. Doing so will cause future calls to createOffer and
+ createAnswer to mark the corresponding media description as sendrecv
+ or sendonly and add the MSID of the track added, as defined in [JSEP]
+ (section 5.2.2. and section 5.3.2.).
+
+ Non-Testable
+ 5.1. addTrack
+ 7. If sender is not null, run the following steps to use that sender:
+ 2. Set sender's [[associated MediaStreams]] to streams.
+
+ Tested in RTCPeerConnection-onnegotiationneeded.html:
+ 5.1. addTrack
+ 10. Update the negotiation-needed flag for connection.
+
+ */
+
+ promise_test(async t => {
+ const caller = new RTCPeerConnection();
+ t.add_cleanup(() => caller.close());
+ const callee = new RTCPeerConnection();
+ t.add_cleanup(() => callee.close());
+
+ const stream = await getNoiseStream({audio: true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+ const transceiver = caller.addTransceiver(track);
+ // Note that this test doesn't process canididates.
+ {
+ const offer = await caller.createOffer();
+ await caller.setLocalDescription(offer);
+ await callee.setRemoteDescription(offer);
+ const answer = await callee.createAnswer();
+ await callee.setLocalDescription(answer);
+ await caller.setRemoteDescription(answer);
+ }
+ assert_equals(transceiver.currentDirection, 'sendonly');
+ await waitForIceGatheringState(caller, ['complete']);
+ await waitForIceGatheringState(callee, ['complete']);
+
+ const second_stream = await getNoiseStream({audio: true});
+ t.add_cleanup(() => second_stream.getTracks().forEach(track => track.stop()));
+ // There may be callee candidates in flight. It seems that waiting
+ // for a createOffer() is enough time to let them complete processing.
+ // TODO(https://crbug.com/webrtc/13095): Fix bug and remove.
+ await caller.createOffer();
+
+ const [second_track] = second_stream.getTracks();
+ caller.onicecandidate = t.unreached_func(
+ 'No caller candidates should be generated.');
+ callee.onicecandidate = t.unreached_func(
+ 'No callee candidates should be generated.');
+ caller.addTrack(second_track);
+ {
+ const offer = await caller.createOffer();
+ await caller.setLocalDescription(offer);
+ await callee.setRemoteDescription(offer);
+ const answer = await callee.createAnswer();
+ await callee.setLocalDescription(answer);
+ await caller.setRemoteDescription(answer);
+ }
+ // Check that we're bundled.
+ const [first_transceiver, second_transceiver] = caller.getTransceivers();
+ assert_equals(first_transceiver.transport, second_transceiver.transport);
+
+ }, 'Adding more tracks does not generate more candidates if bundled');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const stream = await getNoiseStream({ audio: true });
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+
+ pc1.addTrack(track);
+ const offer = await pc1.createOffer();
+ // We do not await here; we want to ensure that the transceiver this creates
+ // is untouched by addTrack, and that addTrack creates _another_ transceiver
+ const srdPromise = pc2.setRemoteDescription(offer);
+
+ const sender = pc2.addTrack(track);
+
+ await srdPromise;
+
+ assert_equals(pc2.getTransceivers().length, 1, "Should have 1 transceiver");
+ assert_equals(pc2.getTransceivers()[0].sender, sender, "The transceiver should be the one added by addTrack");
+ }, 'Calling addTrack while sRD(offer) is pending should allow the new remote transceiver to be the same one that addTrack creates');
+
+ promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+ pc1.addTransceiver('video');
+
+ const stream = await getNoiseStream({ audio: true });
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ const [track] = stream.getTracks();
+
+ const offer = await pc1.createOffer();
+ const srdPromise = pc2.setRemoteDescription(offer);
+ assert_equals(pc2.getTransceivers().length, 0);
+ pc2.addTrack(track);
+ assert_equals(pc2.getTransceivers().length, 1);
+ const transceiver0 = pc2.getTransceivers()[0];
+ assert_equals(transceiver0.mid, null);
+ await srdPromise;
+ assert_equals(pc2.getTransceivers().length, 2);
+ const transceiver1 = pc2.getTransceivers()[1];
+ assert_equals(transceiver0.mid, null);
+ assert_not_equals(transceiver1.mid, null);
+ }, 'When addTrack is called while sRD is in progress, and both addTrack and sRD add a transceiver of different media types, the addTrack transceiver should come first, and then the sRD transceiver.');
+
+</script>