<!doctype html>
<meta charset=utf-8>
<title>Change of msid in remote description should trigger related track events</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const sdpBase =`v=0
o=- 5511237691691746 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=ice-options:trickle
a=ice-lite
a=msid-semantic:WMS *
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 102 0 8 105 13 110 113 126
c=IN IP6 ::
a=rtcp:9 IN IP6 ::
a=rtcp-mux
a=mid:0
a=sendrecv
a=ice-ufrag:z0i8R3C9C4hPRWls
a=ice-pwd:O7bPpOFAqasqoidV4yxnFVbc
a=ice-lite
a=fingerprint:sha-256 B7:9C:0D:C9:D1:42:57:97:82:4D:F9:B7:93:75:49:C3:42:21:5A:DD:9C:B5:ED:53:53:F0:B4:C8:AE:88:7A:E7
a=setup:actpass
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=rtpmap:0 PCMU/8000`;
const sdp0 = sdpBase + `
`;
const sdp1 = sdpBase + `
a=msid:1 2
a=ssrc:3 cname:4
a=ssrc:3 msid:1 2
`;
const sdp2 = sdpBase + `
a=ssrc:3 cname:4
a=ssrc:3 msid:1 2
`;
const sdp3 = sdpBase + `
a=msid:1 2
a=ssrc:3 cname:4
a=ssrc:3 msid:3 2
`;
const sdp4 = sdp1.replace('msid-semantic', 'unknownattr');
const sdp5 = sdpBase + `
a=msid:-
`;
const sdp6 = sdpBase + `
a=msid:1 2
a=msid:1 2
`;
async function applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp)
{
const testTrackPromise = new Promise(resolve => {
pc.ontrack = (event) => { resolve([event.track, event.streams]); };
});
await pc.setRemoteDescription({type: 'offer', sdp: sdp});
return testTrackPromise;
}
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp0);
assert_equals(streams.length, 1, "track event has a stream");
}, "When a=msid is absent, the track should still be associated with a stream");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp1);
assert_equals(streams.length, 1, "track event has a stream");
assert_equals(streams[0].id, "1", "msid should match");
}, "Source-level msid should be ignored if media-level msid is present");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp2);
assert_equals(streams.length, 1, "track event has a stream");
assert_equals(streams[0].id, "1", "msid should match");
}, "Source-level msid should be parsed if media-level msid is absent");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
let track;
let streams;
try {
[track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp3);
} catch (e) {
return;
}
assert_equals(streams.length, 1, "track event has a stream");
assert_equals(streams[0].id, "1", "msid should match");
}, "Source-level msid should be ignored, or an error should be thrown, if a different media-level msid is present");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp4);
assert_equals(streams.length, 1, "track event has a stream");
assert_equals(streams[0].id, "1", "msid should match");
}, "stream ids should be found even if msid-semantic is absent");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp5);
assert_equals(streams.length, 0, "track event has no stream");
}, "a=msid:- should result in a track event with no streams");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp6);
assert_equals(streams.length, 1, "track event has one stream");
}, "Duplicate a=msid should result in a track event with one stream");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
const [track, streams] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp1);
assert_equals(streams.length, 1, "track event has a stream");
assert_equals(streams[0].id, "1", "msid should match");
const stream = streams[0];
await pc.setLocalDescription(await pc.createAnswer());
const testTrackPromise = new Promise((resolve) => { stream.onremovetrack = resolve; });
await pc.setRemoteDescription({type: 'offer', 'sdp': sdp0});
await testTrackPromise;
assert_equals(stream.getAudioTracks().length, 0, "stream should be empty");
}, "Applying a remote description with removed msid should trigger firing a removetrack event on the corresponding stream");
promise_test(async test => {
const pc = new RTCPeerConnection();
test.add_cleanup(() => pc.close());
let [track0, streams0] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp0);
await pc.setLocalDescription(await pc.createAnswer());
let [track1, streams1] = await applyRemoteDescriptionAndReturnRemoteTrackAndStreams(pc, sdp1);
assert_equals(streams1.length, 1, "track event has a stream");
assert_equals(streams1[0].id, "1", "msid should match");
assert_equals(streams1[0].getTracks()[0], track0, "track should match");
}, "Applying a remote description with a new msid should trigger firing an event with populated streams");
</script>