blob: 4564f15062b9771643c196f74ec9947a575d4cec [file] [log] [blame]
youenn@apple.com48452c82018-10-03 12:11:23 +00001<!doctype html>
2<html>
3<head>
4<script src="../resources/testharness.js"></script>
5<script src="../resources/testharnessreport.js"></script>
6</head>
7<body>
8<div>
9 <video id="low" playsinline autoplay width="320"></video>
10 <video id="mid" playsinline autoplay width="320"></video>
11 <video id="high" playsinline autoplay width="320"></video>
12</div>
13<script>
youenn@apple.com5e7f4762019-10-09 08:48:10 +000014// Code taken from Chrome/Firefox tests and/or simulcast playground.
15function splitUnifiedPlanOffer(offer) {
16 let sdpLines = offer.sdp.split("\r\n");
17
18 mSectionStart = sdpLines.findIndex(line => line.startsWith("m="));
19 mSection = sdpLines.splice(mSectionStart);
20
21 let ssrcs = mSection.filter((line) => {
22 return line.startsWith("a=ssrc");
23 });
24
25 let layerRIDS = mSection.filter(line => line.startsWith("a=simulcast:")).map(
26 line => line.replace("a=simulcast:send ", "").split(";")
27 )[0];
28
29 let midExtmapId = mSection.filter(line => line.includes("urn:ietf:params:rtp-hdrext:sdes:mid")).map(line =>
30 line.replace("a=extmap:", "").split(" ")[0]
31 )[0];
32
33 let ridExtmapId = mSection.filter(line => line.includes("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id")).map(line =>
34 line.replace("a=extmap:", "").split(" ")[0]
35 )[0];
36
37 mSection = mSection.filter((line) => {
38 return !line.startsWith("a=ssrc") && !line.startsWith("a=simulcast");
39 });
40
41 sdpLines = sdpLines.map(line => {
42 if (line.startsWith("a=group:BUNDLE"))
43 return "a=group:BUNDLE " + layerRIDS.join(" ");
44
45 return line;
46 });
47
48 let counter = 0;
49 for (let layerName of layerRIDS) {
50 sdpLines = sdpLines.concat(mSection.map(line => {
51 if (line.match(/a=msid:/)) {
52 return "a=msid:" + layerName + " " + layerName;
53 }
54
55 if (line.startsWith("a=mid:"))
56 return "a=mid:" + layerName;
57
58 if (line.startsWith("a=extmap:" + midExtmapId + " "))
59 return "a=extmap:" + midExtmapId + " urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
60
61 if (line.startsWith("a=extmap:" + ridExtmapId + " "))
62 return "a=extmap:" + ridExtmapId + " urn:ietf:params:rtp-hdrext:sdes:mid";
63
64 if (line.startsWith("a=rid:") || line.startsWith("a=simulcast:"))
65 return null;
66
67 return line;
68 }));
69 sdpLines = sdpLines.concat([ssrcs[counter]]);
70 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4]]);
71 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 1]]);
72 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 2]]);
73 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 3]]);
74 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 4]]);
75 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 5]]);
76 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 6]]);
77 sdpLines = sdpLines.concat([ssrcs[8 * counter + 4 + 7]]);
78 counter = counter + 1;
79 }
80
81 offer.sdp = sdpLines
82 .filter(line => line && line.length > 0)
83 .join("\r\n") + "\r\n";
84}
85
86function splitUnifiedPlanAnswer(answer) {
87 let sdpLines = answer.sdp.split("\r\n");
88
89 let mSectionStart = sdpLines.findIndex(line => line.startsWith("m="));
90 let mSection = sdpLines.splice(mSectionStart);
91
92 // Remove extra m= sections
93 mSectionStart = mSection.slice(1).findIndex(line => line.startsWith("m="));
94 if (mSectionStart != -1)
95 mSection.splice(mSectionStart);
96
97 let midExtmapId = mSection.filter(line => line.includes("urn:ietf:params:rtp-hdrext:sdes:mid")).map(line =>
98 line.replace("a=extmap:", "").split(" ")[0]
99 )[0];
100
101 let ridExtmapId = mSection.filter(line => line.includes("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id")).map(line =>
102 line.replace("a=extmap:", "").split(" ")[0]
103 )[0];
104
105 sdpLines = sdpLines.map(line => {
106 if (line.startsWith("a=group:BUNDLE"))
107 return "a=group:BUNDLE 0";
108
109 return line;
110 });
111
112 mSection = mSection.map(line => {
113 if (line.startsWith("a=mid:"))
114 return "a=mid:0";
115
116 if (line.startsWith("a=extmap:" + midExtmapId + " "))
117 return "a=extmap:" + midExtmapId + " urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
118
119 if (line.startsWith("a=extmap:" + ridExtmapId + " "))
120 return "a=extmap:" + ridExtmapId + " urn:ietf:params:rtp-hdrext:sdes:mid";
121
122 return line;
123 });
124
125 let params = ["0", "1", "2"];
126 for(let r in params) {
127 mSection.push("a=rid:" + r + " recv");
128 }
129 mSection.push("a=simulcast:recv " + params.join(";"));
130
131 answer.sdp = sdpLines.concat(mSection)
132 .filter(line => line && line.length > 0).join("\r\n") + "\r\n";
133}
134
135function enableSimulcastThroughSDP(offer)
136{
137 match = offer.sdp.match(/a=ssrc:(\d+) cname:(.*)\r\n/);
138 msid = offer.sdp.match(/a=ssrc:(\d+) msid:(.*)\r\n/);
139 var lines = offer.sdp.trim().split('\r\n');
140 var removed = lines.splice(lines.length - 4, 4);
141 var videoSSRC1 = parseInt(match[1]);
142 rtxSSRC1 = offer.sdp.split('\r\n').filter((line) => { return line.startsWith('a=ssrc-group:FID ')})[0].split(' ')[2];
143 var videoSSRC2 = videoSSRC1 + 1;
144 var rtxSSRC2 = videoSSRC1 + 2;
145 var videoSSRC3 = videoSSRC1 + 3;
146 var rtxSSRC3 = videoSSRC1 + 4;
147 lines.push(removed[0]);
148 lines.push(removed[1]);
149 lines.push('a=ssrc:' + videoSSRC2 + ' cname:' + match[2]);
150 lines.push('a=ssrc:' + videoSSRC2 + ' msid:' + msid[2]);
151 lines.push('a=ssrc:' + rtxSSRC2 + ' cname:' + match[2]);
152 lines.push('a=ssrc:' + rtxSSRC2 + ' msid:' + msid[2]);
153
154 lines.push('a=ssrc:' + videoSSRC3 + ' cname:' + match[2]);
155 lines.push('a=ssrc:' + videoSSRC3 + ' msid:' + msid[2]);
156 lines.push('a=ssrc:' + rtxSSRC3 + ' cname:' + match[2]);
157 lines.push('a=ssrc:' + rtxSSRC3 + ' msid:' + msid[2]);
158
159 lines.push('a=ssrc-group:FID ' + videoSSRC2 + ' ' + rtxSSRC2);
160 lines.push('a=ssrc-group:FID ' + videoSSRC3 + ' ' + rtxSSRC3);
161 lines.push('a=ssrc-group:SIM ' + videoSSRC1 + ' ' + videoSSRC2 + ' ' + videoSSRC3);
162
163 offer.sdp = lines.join('\r\n') + '\r\n';
164}
165
166function enableSimulcastThroughSDP2(offer)
167{
168 var lines = offer.sdp.trim().split('\r\n');
169
170 lines.push('a=simulcast:send 0;1;2');
171
172 offer.sdp = lines.join('\r\n') + '\r\n';
173}
174
175</script>
176<script>
177async function setupCall(pc1, pc2)
178{
179 let pc1Offer = await pc1.createOffer();
180 enableSimulcastThroughSDP(pc1Offer);
181 await pc1.setLocalDescription(pc1Offer);
182
183 let pc2Offer = {
184 type: 'offer',
185 sdp: pc1.localDescription.sdp,
186 };
187 enableSimulcastThroughSDP2(pc2Offer);
188
189 splitUnifiedPlanOffer(pc2Offer);
190 await pc2.setRemoteDescription(pc2Offer);
191
192 let answer = await pc2.createAnswer();
193 await pc2.setLocalDescription(answer);
194 let pc1Answer = {
195 type: "answer",
196 sdp: pc2.localDescription.sdp,
197 }
198 splitUnifiedPlanAnswer(pc1Answer);
199
200 await pc1.setRemoteDescription(pc1Answer).then(() => {}, (e) => console.log(e));
201}
202
youenn@apple.com48452c82018-10-03 12:11:23 +0000203var state;
204var finished = false;
youenn@apple.com5e7f4762019-10-09 08:48:10 +0000205
206const pc1 = new RTCPeerConnection();
207const pc2 = new RTCPeerConnection();
208
youenn@apple.com48452c82018-10-03 12:11:23 +0000209promise_test(async (test) => {
210 if (window.testRunner && testRunner.timeout) {
211 setTimeout(() => {
212 if (!finished)
213 throw "test stucked in state: " + state;
214 }, testRunner.timeout * 0.9);
215 }
216
217 state = "start";
218
youenn@apple.com5e7f4762019-10-09 08:48:10 +0000219 pc1.onicecandidate = e => {
220 if (e.candidate) {
221 for(let layerIndex in ["0", "1", "2"]) {
222 let newCandidate = new RTCIceCandidate({
223 candidate: e.candidate.candidate,
224 sdpMid: layerIndex,
225 sdpMLineIndex: layerIndex,
226 usernameFragment: e.candidate.usernameFragment,
227 });
228 setTimeout(() => pc2.addIceCandidate(newCandidate), 5);
229 }
230 } else
231 setTimeout(() => pc1.addIceCandidate(e.candidate), 5);
232 };
233
234 pc2.onicecandidate = e => {
235 if (e.candidate) {
236 let newCandidate = new RTCIceCandidate({
237 candidate: e.candidate.candidate,
238 sdpMid: "0", //e.candidate.sdpMid,
239 sdpMLineIndex: e.candidate.sdpMLineIndex,
240 usernameFragment: e.candidate.usernameFragment,
241 });
242 setTimeout(() => pc1.addIceCandidate(newCandidate), 5);
243 } else
244 setTimeout(() => pc1.addIceCandidate(e.candidate), 5);
245 };
youenn@apple.com48452c82018-10-03 12:11:23 +0000246
247 let counter = 0;
248 pc2.ontrack = (e) => {
249 if (counter == 0)
250 low.srcObject = new MediaStream([e.track]);
251 else if (counter == 1)
252 mid.srcObject = new MediaStream([e.track]);
253 else
254 high.srcObject = new MediaStream([e.track]);
255 ++counter;
256 }
257
258 const localStream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } });
259 pc1.addTrack(localStream.getVideoTracks()[0], localStream);
youenn@apple.com48452c82018-10-03 12:11:23 +0000260
youenn@apple.com5e7f4762019-10-09 08:48:10 +0000261 await setupCall(pc1, pc2);
youenn@apple.com48452c82018-10-03 12:11:23 +0000262
263 await low.play();
264 state = "video low plays";
265
266 assert_equals(low.srcObject.getVideoTracks()[0].getSettings().height, 240);
267 assert_equals(low.srcObject.getVideoTracks()[0].getSettings().width, 320);
268
269 await mid.play();
270 state = "video mid plays";
271
272 assert_equals(mid.srcObject.getVideoTracks()[0].getSettings().height, 480);
273 assert_equals(mid.srcObject.getVideoTracks()[0].getSettings().width, 640);
274
275 finished = true;
276}, "Testing simulcast");
277</script>
278</body>
279</html>