| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>WebRTC Simulcast Test</title> |
| <style> |
| video { |
| border:5px solid black; |
| } |
| button { |
| font: 18px sans-serif; |
| padding: 8px; |
| } |
| </style> |
| </head> |
| <body> |
| <video id="localVideo" autoplay></video> |
| <!-- These video tags and canvases should match the stream labels returned by |
| PC_SERVER_REMOTE_OFFER. The canvases are there for video detection. --!> |
| <video id="remoteVideo1" autoplay></video> |
| <video id="remoteVideo2" autoplay></video> |
| <video id="remoteVideo3" autoplay></video> |
| <canvas id="remoteVideo1-canvas" autoplay style="display:none"></video> |
| <canvas id="remoteVideo2-canvas" autoplay style="display:none"></video> |
| <canvas id="remoteVideo3-canvas" autoplay style="display:none"></video> |
| |
| <script type="text/javascript" src="webrtc_test_utilities.js"></script> |
| <script type="text/javascript"> |
| |
| var pcClient, pcServer; |
| var localStream; |
| var remoteVideoUrls = []; |
| var serverAnswer; |
| |
| // When all required video tags are playing video, succeed the test. |
| setAllEventsOccuredHandler(function() { |
| returnToTest('OK'); |
| }); |
| |
| // Checks that we can get a simulcast stream call up on VGA. |
| function testVgaReturnsTwoSimulcastStreams() { |
| initialize(); |
| openCamera(640, 480); |
| |
| // For VGA we should get a QVGA and VGA stream. The video tags are named after |
| // the stream IDs which are defined in PC_SERVER_REMOTE_OFFER. |
| waitForVideo('remoteVideo1'); |
| waitForVideo('remoteVideo2'); |
| } |
| |
| // Template for the local offer, representing a single input stream backing |
| // 3 different SSRCs, all in one SIM ssrc-group. |
| function makeClientOffer() { |
| var lines = [ |
| 'v=0', |
| 'o=- 0 3 IN IP4 127.0.0.1', |
| 's=-', |
| 't=0 0', |
| 'm=video 1 RTP/SAVPF 100', |
| 'a=rtcp-mux', |
| 'a=ice-ufrag:6HHHdzzeIhkE0CKj', |
| 'a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew', |
| 'a=sendonly', |
| 'a=mid:video', |
| 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' + |
| 'inline:Rlz8z1nMtwq9VF7j06kTc7uyio1iYuEdeZ7z1P9E', |
| 'a=rtpmap:100 VP8/30', |
| 'a=fmtp:100 x-google-start-bitrate=100000', |
| 'a=fmtp:100 x-google-min-bitrate=80000', |
| 'a=x-google-flag:conference' |
| ]; |
| |
| if (localStream) { |
| var videoTracks = localStream.getVideoTracks(); |
| if (videoTracks.length > 0) { |
| trace('Using Video device: ' + videoTracks[0].id); |
| } else { |
| trace('WARNING: No video device!'); |
| return lines.join('\n'); |
| } |
| |
| lines = lines.concat([ |
| 'a=ssrc-group:SIM 1 2 3', |
| 'a=ssrc:1 cname:localVideo', |
| 'a=ssrc:1 msid:' + localStream.id + ' ' + videoTracks[0].id, |
| 'a=ssrc:2 cname:localVideo', |
| 'a=ssrc:2 msid:' + localStream.id + ' ' + videoTracks[0].id, |
| 'a=ssrc:3 cname:localVideo', |
| 'a=ssrc:3 msid:' + localStream.id + ' ' + videoTracks[0].id |
| ]); |
| } |
| lines.push(''); |
| |
| return new RTCSessionDescription({ |
| 'type': 'offer', |
| 'sdp': lines.join('\n') |
| }); |
| } |
| |
| // Remote perspective on that offer, representing each SSRC as a distinct |
| // (non-synchronized) output video stream. |
| var PC_SERVER_REMOTE_OFFER = [ |
| 'v=0', |
| 'o=- 0 3 IN IP4 127.0.0.1', |
| 's=-', |
| 't=0 0', |
| 'm=video 1 RTP/SAVPF 100', |
| 'a=sendonly', |
| 'a=mid:video', |
| 'a=rtcp-mux', |
| 'a=ice-ufrag:6HHHdzzeIhkE0CKj', |
| 'a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew', |
| 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' + |
| 'inline:Rlz8z1nMtwq9VF7j06kTc7uyio1iYuEdeZ7z1P9E', |
| 'a=rtpmap:100 VP8/30', |
| 'a=x-google-flag:conference', |
| 'a=fmtp:100 x-google-start-bitrate=100000', |
| 'a=fmtp:100 x-google-min-bitrate=80000', |
| 'a=ssrc:1 cname:remoteVideo1', |
| 'a=ssrc:1 msid:remoteVideo1 remoteVideo1v0', |
| 'a=ssrc:2 cname:remoteVideo2', |
| 'a=ssrc:2 msid:remoteVideo2 remoteVideo2v0', |
| 'a=ssrc:3 cname:remoteVideo3', |
| 'a=ssrc:3 msid:remoteVideo3 remoteVideo3v0', |
| '' |
| ].join('\n'); |
| |
| function trace(text) { |
| // This function is used for logging. |
| if (text[text.length - 1] == '\n') { |
| text = text.substring(0, text.length - 1); |
| } |
| console.log((performance.now() / 1000).toFixed(3) + ': ' + text); |
| } |
| |
| function fail(e) { |
| trace('Failure : ' + e.name + ': ' + e.message); |
| throw e; |
| } |
| |
| function initialize() { |
| trace('Setting up for a new call.'); |
| var configs = {iceServers:[]}; |
| var constraints = {'mandatory': {'DtlsSrtpKeyAgreement': false}}; |
| pcClient = new RTCPeerConnection(configs, constraints); |
| trace('Created local peer connection object pcClient'); |
| pcClient.onicecandidate = onClientIceCandidate; |
| pcServer = new RTCPeerConnection(configs, constraints); |
| trace('Created remote peer connection object pcServer'); |
| pcServer.onicecandidate = onServerIceCandidate; |
| pcServer.onaddstream = onServerGotStream; |
| |
| var pcClientInitialOffer = makeClientOffer(); |
| trace('Setting initial local Offer to:\n' + pcClientInitialOffer.sdp); |
| pcClient.setLocalDescription(pcClientInitialOffer, |
| setServerRemoteDescription, fail); |
| } |
| |
| function gotStream(stream) { |
| trace('Received local stream'); |
| localVideo.srcObject = stream; |
| localStream = stream; |
| pcClient.addStream(localStream); |
| renegotiateClient(); |
| } |
| |
| function didntGetStream(err) { |
| returnToTest('Unexpectedly failed to acquire user media: ' + err); |
| } |
| |
| function openCamera(width, height) { |
| if (localStream) { |
| pcClient.removeStream(localStream); |
| localStream.getVideoTracks()[0].stop(); |
| localStream = null; |
| } |
| navigator.webkitGetUserMedia({ |
| audio: false, |
| video: {'mandatory': {'minWidth': width, 'maxWidth': width, |
| 'minHeight': height, 'maxHeight': height}} |
| }, gotStream, didntGetStream); |
| } |
| |
| function renegotiateClient() { |
| pcClient.setLocalDescription(makeClientOffer(), function() { |
| pcClient.setRemoteDescription(serverAnswer, function(){}, fail); |
| }, fail); |
| } |
| |
| function setServerRemoteDescription() { |
| trace('Setting remote Offer to:\n' + PC_SERVER_REMOTE_OFFER); |
| pcServer.setRemoteDescription(new RTCSessionDescription({ |
| 'type': 'offer', |
| 'sdp': PC_SERVER_REMOTE_OFFER |
| }), afterSetServerRemoteDescription, fail); |
| } |
| |
| function afterSetServerRemoteDescription() { |
| pcServer.createAnswer(onServerAnswer, fail); |
| } |
| |
| function onServerAnswer(desc) { |
| desc.sdp += 'a=x-google-flag:conference\n'; |
| serverAnswer = desc; |
| trace('Setting both Answers to:\n' + desc.sdp); |
| pcServer.setLocalDescription(desc, function() {}, fail); |
| pcClient.setRemoteDescription(desc, function() {}, fail); |
| } |
| |
| function onServerGotStream(e) { |
| trace('Received remote stream: ' + e.stream.id + |
| '; looking up corresponding video tag.'); |
| |
| var remoteVideo = $(e.stream.id); |
| if (!remoteVideo) { |
| // All streams we receive must have a corresponding video tag defined in the |
| // html, otherwise we can't detect video in it. |
| throw 'Received video with unexpected id ' + e.stream.id; |
| } |
| remoteVideo.srcObject = e.stream; |
| } |
| |
| function onClientIceCandidate(event) { |
| if (event.candidate) { |
| pcServer.addIceCandidate(event.candidate); |
| trace('Local ICE candidate:\n' + event.candidate.candidate); |
| } |
| } |
| |
| function onServerIceCandidate(event) { |
| if (event.candidate) { |
| pcClient.addIceCandidate(event.candidate); |
| trace('Remote ICE candidate:\n' + event.candidate.candidate); |
| } |
| } |
| |
| function returnToTest(message) { |
| if (!window.domAutomationController) |
| throw 'Expected to run in an automated context.'; |
| window.domAutomationController.send(message); |
| } |
| |
| $ = function(id) { |
| return document.getElementById(id); |
| }; |
| |
| </script> |
| </body> |