mirror of
https://github.com/servo/servo.git
synced 2025-06-25 09:34:32 +01:00
394 lines
16 KiB
HTML
394 lines
16 KiB
HTML
<!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>
|