pc_test_loop_h264.html (348 lines of code) (raw):

<html><head> <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> <title>Simple RTCPeerConnection Video Test</title> <style> #player1 { position:relative; width:640px; height:480px; float:right; } #player2 { position:relative; width:640px; height:480px; float:left; } #mainvideo { position:absolute; top:0; left:0; z-index:0; } #localvideo1, #localvideo2 { position:absolute; top:0; left:0; z-index:1; } .hidden { display: none; } </style> </head> <body> <h1>Simple RTCPeerConnection Video Test</h1> <div> <div id="player1"> <video tabindex="0" id="pc1video" class="mainvideo" controls="controls" height="480" width="640" muted autoplay></video> <video tabindex="0" id="localvideo1" controls="controls" height="120" width="160" muted></video></div> <div id="player2"> <video tabindex="0" id="pc2video" class="mainvideo" controls="controls" height="480" width="640" muted autoplay></video> <video tabindex="0" id="localvideo2" controls="controls" height="120" width="160" muted></video></div><br> <br style="clear: left;" /> </div> <div id="log"></div> <script type="application/javascript"> // Lame clone function. Not efficient but we don't have jQuery. function copy_dictionary (obj) { return JSON.parse(JSON.stringify(obj)); } function log(msg) { let div = document.getElementById("log"); div.innerHTML = "<p>" + msg + "</p>" + div.innerHTML; } var rounds = 0; let pc1video = document.getElementById("pc1video"); let pc2video = document.getElementById("pc2video"); //pc1video.onplay = function() {log("Play for pc1");}; //pc2video.onplay = function() {log("Play for pc2");}; let button = document.getElementById("tehbutton"); let localvideo1 = document.getElementById("localvideo1"); let localvideo2 = document.getElementById("localvideo2"); let pc1; let pc2; let pc1_offer; let pc2_answer; let offer_constraints; let answer_constraints; function disable_media(pc, type, array_index) { log("disable_media"); var stream; if (pc == pc1) stream = localvideo1.mozSrcObject; else if (pc == pc2) stream = localvideo2.mozSrcObject; else log("bad pc " + pc); if (stream) { log("track[" + array_index + "] = " + stream.getVideoTracks()[array_index]); if (type == 1) stream.getVideoTracks()[array_index].enabled = !video_disable.checked; else if (type == 0) stream.getAudioTracks()[array_index].enabled = !audio_disable.checked; else log("bad type"); } else log("no stream"); } function failed(code) { var err = code.message || code; console.log(err); log("Failure callback: " + JSON.stringify(err)); } // replace CR NL with HTML breaks function sdpPrettyPrint(sdp) { return sdp.replace(/[\r\n]+/g, '<br>'); } function h264StateChange(cb) { var h264opts = document.getElementById('divH264'); if(cb.checked){ h264opts.style.display = 'block'; } else { h264opts.style.display = 'none'; } } // Also remove mode 0 if it's offered // Note, we don't bother removing the fmtp lines, which makes a good test // for some SDP parsing issues. function removeAllButH264(sdp) { updated_sdp = sdp.replace("a=rtpmap:120 VP8/90000\r\n",""); updated_sdp = updated_sdp.replace("a=rtpmap:121 VP9/90000\r\n",""); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF ([0-9 ]*) 120/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF $2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF 120([0-9 ]*)/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF$2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF ([0-9 ]*) 121/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF $2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF 121([0-9 ]*)/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF$2"); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 goog-remb\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack pli\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 ccm fir\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:121 goog-remb\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:121 nack\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:121 nack pli\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:121 ccm fir\r\n",""); return updated_sdp; } function removeAllButVP9(sdp) { updated_sdp = sdp.replace("a=rtpmap:120 VP8/90000\r\n",""); updated_sdp = updated_sdp.replace("a=rtpmap:126 H264/90000\r\n",""); updated_sdp = updated_sdp.replace("a=rtpmap:97 H264/90000\r\n",""); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF ([0-9 ]*) 120/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF $2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF 120([0-9 ]*)/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF$2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF ([0-9 ]*) 126/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF $2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF 126([0-9 ]*)/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF$2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF ([0-9 ]*) 97/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF $2"); updated_sdp = updated_sdp.replace(/m=video ([0-9]+) UDP\/TLS\/RTP\/SAVPF 97([0-9 ]*)/g, "m=video $1 UDP\/TLS\/RTP\/SAVPF$2"); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 goog-remb\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 nack pli\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:120 ccm fir\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:126 goog-remb\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:126 nack\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:126 nack pli\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:126 ccm fir\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:97 goog-remb\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:97 nack\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:97 nack pli\r\n",""); updated_sdp = updated_sdp.replace("a=rtcp-fb:97 ccm fir\r\n",""); return updated_sdp; } // Remove anything that's not G.722 from the m=audio line function removeNonG722(sdp) { updated_sdp = sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, "m=audio $1 RTP\/SAVPF 9"); updated_sdp = updated_sdp.replace(/m=audio ([0-9]+) UDP\/TLS\/RTP\/SAVPF ([0-9 ]*)/g, "m=audio $1 UDP\/TLS\/RTP\/SAVPF 9"); return updated_sdp; } function preferMode0(sdp) { updated_sdp = updated_sdp.replace("126 97", "97 126"); return updated_sdp; } // pc1.createOffer finished, call pc1.setLocal function offerSetLocal(offer) { //log("Offer: " + sdpPrettyPrint(offer.sdp)); //pc1_offer = offer; // to enforce the usage of H264 we remove the VP8 & VP9 codecs from the offer offer.sdp = removeAllButH264(offer.sdp); pc1_offer = offer; //log("H264-only Offer: " + sdpPrettyPrint(offer.sdp)); if (!offer.sdp.match(/a=rtpmap:[0-9]+ H264/g)) { log("No H264 found in the offer!!!"); return Promise.reject(); } return pc1.setLocalDescription(offer); } // pc1.setLocal finished, call pc2.setRemote function offerSetRemote() { pc1.onicecandidate = function(obj) { if (obj.candidate) { //log("pc1 found ICE candidate: " + JSON.stringify(obj.candidate)); pc2.addIceCandidate(obj.candidate); } else { //log("pc1 got end-of-candidates signal"); } } return pc2.setRemoteDescription(pc1_offer); }; // pc2.setRemote finished, call pc2.createAnswer function createAnswer() { pc2.didSetRemote = true; while (pc2.ice_queued.length > 0) { pc2.addIceCandidate(pc2.ice_queued.shift()); } return pc2.createAnswer(answer_constraints); } // pc2.createAnswer finished, call pc2.setLocal function answerSetLocal(answer) { //log("Answer: " + sdpPrettyPrint(answer.sdp)); pc2_answer = answer; return pc2.setLocalDescription(answer); } // pc2.setLocal finished, call pc1.setRemote function answerSetRemote() { pc2.onicecandidate = function(obj) { if (obj.candidate) { //log("pc2 found ICE candidate: " + JSON.stringify(obj.candidate)); pc1.addIceCandidate(obj.candidate); } else { //log("pc2 got end-of-candidates signal"); } } return pc1.setRemoteDescription(pc2_answer); } // pc1.setRemote finished, media should be running! function finished() { pc1.didSetRemote = true; while (pc1.ice_queued.length > 0) { pc1.addIceCandidate(pc1.ice_queued.shift()); } //log("Signaling done"); } function connected() { if ((pc1.iceConnectionState == 'connected') && (pc2.iceConnectionState == 'connected')){ log("call " + rounds + " connected"); setTimeout(stop, 5000); } } function start() { var pc1config = {}; var pc2config = {}; let idp; var myrequest = {}; myrequest.video = true; myrequest.audio = false; myrequest_reverse = copy_dictionary(myrequest); myrequest_reverse.fake = true; offer_constraints = null; answer_constraints = null; pc1 = new RTCPeerConnection(pc1config); pc2 = new RTCPeerConnection(pc2config); pc1.didSetRemote = false; pc2.didSetRemote = false; pc1.ice_queued = []; pc2.ice_queued = []; pc2.onicecandidate = function(obj) { if (obj.candidate) { //log("pc2 found ICE candidate: " + JSON.stringify(obj.candidate)); if (pc1.didSetRemote) { pc1.addIceCandidate(obj.candidate); } else { pc1.ice_queued.push(obj.candidate); } } else { //log("pc2 got end-of-candidates signal"); } } pc1.onicecandidate = function(obj) { if (obj.candidate) { //log("pc1 found ICE candidate: " + JSON.stringify(obj.candidate)); if (pc2.didSetRemote) { pc2.addIceCandidate(obj.candidate); } else { pc2.ice_queued.push(obj.candidate); } } else { //log("pc1 got end-of-candidates signal"); } } pc1.oniceconnectionstatechange = function(obj) { //log("pc1 ICE connection state: " + pc1.iceConnectionState); if (pc1.iceConnectionState == 'connected') { //log('PC1: HIP HIP HOORAY'); connected(); } } pc2.oniceconnectionstatechange = function(obj) { //log("pc2 ICE connection state: " + pc2.iceConnectionState); if (pc2.iceConnectionState == 'connected') { //log('PC2: HIP HIP HOORAY'); connected(); } } pc1.ontrack = function(obj) { //log("pc1 got remote tracks from pc2 " + obj.type); pc1video.mozSrcObject = obj.streams[0]; } pc2.ontrack = function(obj) { //log("pc2 got remote tracks from pc1 " + obj.type); pc2video.mozSrcObject = obj.streams[0]; } if (idp) { pc1.setIdentityProvider(idp.domain, idp.protocol, idp.usera); pc1.peerIdentity.then(id => log('pc1 connected to "' + id.name + '"')); pc2.setIdentityProvider(idp.domain, idp.protocol, idp.userb); pc2.peerIdentity.then(id => log('pc2 connected to "' + id.name + '"')); } navigator.mediaDevices.getUserMedia(myrequest).then(function(video1) { // Add stream obtained from gUM to <video> to start media flow. localvideo1.mozSrcObject = video1; localvideo1.play(); pc1.addStream(video1); return Promise.resolve(); }) .then(() => { return navigator.mediaDevices.getUserMedia(myrequest_reverse).then(function(video2) { localvideo2.mozSrcObject = video2; localvideo2.play(); pc2.addStream(video2); return Promise.resolve(); }); }) .then(() => { return pc1.createOffer(offer_constraints); }) .then((offer) => { return offerSetLocal(offer); }) .then(() => { return offerSetRemote(); }) .then(() => { return createAnswer(); }) .then((answer) => { return answerSetLocal(answer); }) .then(() => { return answerSetRemote(); }) .then(() => { finished(); }) .catch((e) => { failed(e); }); } function stop() { if (rounds < 50) { pc1.close(); pc2.close(); rounds += 1; start(); } } window.onload = start(); </script> </body></html>