224 lines
9.5 KiB
HTML
224 lines
9.5 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script>
|
|
'use strict';
|
|
|
|
['audio', 'video'].forEach((kind) => {
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const transceiver = pc1.addTransceiver(kind);
|
|
|
|
// Complete O/A exchange such that the transceiver gets associated.
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
assert_not_equals(transceiver.mid, null, 'mid before stop()');
|
|
assert_not_equals(transceiver.direction, 'stopped',
|
|
'direction before stop()');
|
|
assert_not_equals(transceiver.currentDirection, 'stopped',
|
|
'currentDirection before stop()');
|
|
|
|
// Stop makes it stopping, but not stopped.
|
|
transceiver.stop();
|
|
assert_not_equals(transceiver.mid, null, 'mid after stop()');
|
|
assert_equals(transceiver.direction, 'stopped', 'direction after stop()');
|
|
assert_not_equals(transceiver.currentDirection, 'stopped',
|
|
'currentDirection after stop()');
|
|
|
|
// Negotiating makes it stopped.
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
assert_equals(transceiver.mid, null, 'mid after negotiation');
|
|
assert_equals(transceiver.direction, 'stopped',
|
|
'direction after negotiation');
|
|
assert_equals(transceiver.currentDirection, 'stopped',
|
|
'currentDirection after negotiation');
|
|
}, `[${kind}] Locally stopped transceiver goes from stopping to stopped`);
|
|
|
|
promise_test(async t => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
|
|
const transceiver = pc.addTransceiver(kind);
|
|
const trackEnded = new Promise(r => transceiver.receiver.track.onended = r);
|
|
assert_equals(transceiver.receiver.track.readyState, 'live');
|
|
transceiver.stop();
|
|
// Stopping triggers ending the track, but this happens asynchronously.
|
|
assert_equals(transceiver.receiver.track.readyState, 'live');
|
|
await trackEnded;
|
|
assert_equals(transceiver.receiver.track.readyState, 'ended');
|
|
}, `[${kind}] Locally stopping a transceiver ends the track`);
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver(kind);
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const [pc2Transceiver] = pc2.getTransceivers();
|
|
|
|
pc1Transceiver.stop();
|
|
|
|
await pc1.setLocalDescription();
|
|
assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
|
|
// Applying the remote offer immediately ends the track, we don't need to
|
|
// create or apply an answer.
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
// sRD just resolved, so we're in the success task for sRD. The transition
|
|
// from live -> ended is queued right now.
|
|
assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
|
|
await new Promise(r => pc2Transceiver.receiver.track.onended = r);
|
|
assert_equals(pc2Transceiver.receiver.track.readyState, 'ended');
|
|
}, `[${kind}] Remotely stopping a transceiver ends the track`);
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver(kind);
|
|
|
|
// Complete O/A exchange such that the transceiver gets associated.
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription();
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
const [pc2Transceiver] = pc2.getTransceivers();
|
|
assert_not_equals(pc2Transceiver.mid, null, 'mid before stop()');
|
|
assert_not_equals(pc2Transceiver.direction, 'stopped',
|
|
'direction before stop()');
|
|
assert_not_equals(pc2Transceiver.currentDirection, 'stopped',
|
|
'currentDirection before stop()');
|
|
|
|
// Make the remote transceiver stopped.
|
|
pc1Transceiver.stop();
|
|
|
|
// Negotiating makes it stopped.
|
|
assert_equals(pc2.getTransceivers().length, 1);
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
// As soon as the remote offer is set, the transceiver is stopped but it is
|
|
// not disassociated or removed until setting the local answer.
|
|
assert_equals(pc2.getTransceivers().length, 1);
|
|
assert_not_equals(pc2Transceiver.mid, null, 'mid during negotiation');
|
|
assert_equals(pc2Transceiver.direction, 'stopped',
|
|
'direction during negotiation');
|
|
assert_equals(pc2Transceiver.currentDirection, 'stopped',
|
|
'currentDirection during negotiation');
|
|
await pc2.setLocalDescription();
|
|
assert_equals(pc2.getTransceivers().length, 0);
|
|
assert_equals(pc2Transceiver.mid, null, 'mid after negotiation');
|
|
assert_equals(pc2Transceiver.direction, 'stopped',
|
|
'direction after negotiation');
|
|
assert_equals(pc2Transceiver.currentDirection, 'stopped',
|
|
'currentDirection after negotiation');
|
|
}, `[${kind}] Remotely stopped transceiver goes directly to stopped`);
|
|
|
|
promise_test(async t => {
|
|
const pc = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc.close());
|
|
|
|
const transceiver = pc.addTransceiver(kind);
|
|
|
|
// Rollback does not end the track, because the transceiver is not removed.
|
|
await pc.setLocalDescription();
|
|
await pc.setLocalDescription({type:'rollback'});
|
|
assert_equals(transceiver.receiver.track.readyState, 'live');
|
|
}, `[${kind}] Rollback when transceiver is not removed does not end track`);
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver(kind);
|
|
|
|
// Start negotiation, causing a transceiver to be created.
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
const [pc2Transceiver] = pc2.getTransceivers();
|
|
|
|
// Rollback such that the transceiver is removed.
|
|
await pc2.setRemoteDescription({type:'rollback'});
|
|
assert_equals(pc2.getTransceivers().length, 0);
|
|
// sRD just resolved, so we're in the success task for sRD. The transition
|
|
// from live -> ended is queued right now.
|
|
assert_equals(pc2Transceiver.receiver.track.readyState, 'live');
|
|
await new Promise(r => pc2Transceiver.receiver.track.onended = r);
|
|
assert_equals(pc2Transceiver.receiver.track.readyState, 'ended');
|
|
}, `[${kind}] Rollback when removing transceiver does end the track`);
|
|
|
|
// Same test as above but looking at direction and currentDirection.
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const pc1Transceiver = pc1.addTransceiver(kind);
|
|
|
|
// Start negotiation, causing a transceiver to be created.
|
|
await pc1.setLocalDescription();
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
const [pc2Transceiver] = pc2.getTransceivers();
|
|
|
|
// Rollback such that the transceiver is removed.
|
|
await pc2.setRemoteDescription({type:'rollback'});
|
|
assert_equals(pc2.getTransceivers().length, 0);
|
|
// The removed transceiver is stopped.
|
|
assert_equals(pc2Transceiver.currentDirection, 'stopped',
|
|
'currentDirection indicate stopped');
|
|
// A stopped transceiver is necessarily also stopping.
|
|
assert_equals(pc2Transceiver.direction, 'stopped',
|
|
'direction indicate stopping');
|
|
// A stopped transceiver has no mid.
|
|
assert_equals(pc2Transceiver.mid, null, 'not associated');
|
|
}, `[${kind}] Rollback when removing transceiver makes it stopped`);
|
|
|
|
promise_test(async t => {
|
|
const pc1 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc1.close());
|
|
const pc2 = new RTCPeerConnection();
|
|
t.add_cleanup(() => pc2.close());
|
|
|
|
const constraints = {};
|
|
constraints[kind] = true;
|
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
const [track] = stream.getTracks();
|
|
|
|
pc1.addTrack(track);
|
|
pc2.addTrack(track);
|
|
const transceiver = pc2.getTransceivers()[0];
|
|
|
|
const ontrackEvent = new Promise(r => {
|
|
pc2.ontrack = e => r(e.track);
|
|
});
|
|
|
|
// Simulate glare: both peer connections set local offers.
|
|
await pc1.setLocalDescription();
|
|
await pc2.setLocalDescription();
|
|
// Set remote offer, which implicitly rolls back the local offer. Because
|
|
// `transceiver` is an addTrack-transceiver, it should get repurposed.
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
assert_equals(transceiver.receiver.track.readyState, 'live');
|
|
// Sanity check: the track should still be live when ontrack fires.
|
|
assert_equals((await ontrackEvent).readyState, 'live');
|
|
}, `[${kind}] Glare when transceiver is not removed does not end track`);
|
|
});
|
|
</script>
|