| var WebRtcStreamer = (function() { | 
|   | 
| /**  | 
|  * Interface with WebRTC-streamer API | 
|  * @constructor | 
|  * @param {string} videoElement - id of the video element tag | 
|  * @param {string} srvurl -  url of webrtc-streamer (default is current location) | 
| */ | 
| var WebRtcStreamer = function WebRtcStreamer (videoElement, srvurl) { | 
|     if (typeof videoElement === "string") { | 
|         this.videoElement = document.getElementById(videoElement); | 
|     } else { | 
|         this.videoElement = videoElement; | 
|     } | 
|     this.srvurl           = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port; | 
|     this.pc               = null;     | 
|   | 
|     this.mediaConstraints = { offerToReceiveAudio: true, offerToReceiveVideo: true }; | 
|   | 
|     this.iceServers = null; | 
|     this.earlyCandidates = []; | 
| } | 
|   | 
| WebRtcStreamer.prototype._handleHttpErrors = function (response) { | 
|     if (!response.ok) { | 
|         throw Error(response.statusText); | 
|     } | 
|     return response; | 
| } | 
|   | 
| /**  | 
|  * Connect a WebRTC Stream to videoElement  | 
|  * @param {string} videourl - id of WebRTC video stream | 
|  * @param {string} audiourl - id of WebRTC audio stream | 
|  * @param {string} options -  options of WebRTC call | 
|  * @param {string} stream  -  local stream to send | 
| */ | 
| WebRtcStreamer.prototype.connect = function(videourl, audiourl, options, localstream) { | 
|     this.disconnect(); | 
|      | 
|     // getIceServers is not already received | 
|     if (!this.iceServers) { | 
|         console.log("Get IceServers"); | 
|          | 
|         fetch(this.srvurl + "/api/getIceServers") | 
|             .then(this._handleHttpErrors) | 
|             .then( (response) => (response.json()) ) | 
|             .then( (response) =>  this.onReceiveGetIceServers.call(this,response, videourl, audiourl, options, localstream)) | 
|             .catch( (error) => this.onError("getIceServers " + error )) | 
|                  | 
|     } else { | 
|         this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream); | 
|     } | 
| } | 
|   | 
| /**  | 
|  * Disconnect a WebRTC Stream and clear videoElement source | 
| */ | 
| WebRtcStreamer.prototype.disconnect = function() {         | 
|     if (this.videoElement?.srcObject) { | 
|         this.videoElement.srcObject.getTracks().forEach(track => { | 
|             track.stop() | 
|             this.videoElement.srcObject.removeTrack(track); | 
|         }); | 
|     } | 
|     if (this.pc) { | 
|         fetch(this.srvurl + "/api/hangup?peerid="+this.pc.peerid) | 
|             .then(this._handleHttpErrors) | 
|             .catch( (error) => this.onError("hangup " + error )) | 
|   | 
|          | 
|         try { | 
|             this.pc.close(); | 
|         } | 
|         catch (e) { | 
|             console.log ("Failure close peer connection:" + e); | 
|         } | 
|         this.pc = null; | 
|     } | 
| }     | 
|   | 
| /* | 
| * GetIceServers callback | 
| */ | 
| WebRtcStreamer.prototype.onReceiveGetIceServers = function(iceServers, videourl, audiourl, options, stream) { | 
|     this.iceServers       = iceServers; | 
|     this.pcConfig         = iceServers || {"iceServers": [] }; | 
|     try {             | 
|         this.createPeerConnection(); | 
|   | 
|         var callurl = this.srvurl + "/api/call?peerid="+ this.pc.peerid+"&url="+encodeURIComponent(videourl); | 
|         if (audiourl) { | 
|             callurl += "&audiourl="+encodeURIComponent(audiourl); | 
|         } | 
|         if (options) { | 
|             callurl += "&options="+encodeURIComponent(options); | 
|         } | 
|          | 
|         if (stream) { | 
|             this.pc.addStream(stream); | 
|         } | 
|   | 
|                 // clear early candidates | 
|         this.earlyCandidates.length = 0; | 
|          | 
|         // create Offer | 
|         var bind = this; | 
|         this.pc.createOffer(this.mediaConstraints).then(function(sessionDescription) { | 
|             console.log("Create offer:" + JSON.stringify(sessionDescription)); | 
|              | 
|             bind.pc.setLocalDescription(sessionDescription | 
|                 , function() { | 
|                     fetch(callurl, { method: "POST", body: JSON.stringify(sessionDescription) }) | 
|                         .then(bind._handleHttpErrors) | 
|                         .then( (response) => (response.json()) ) | 
|                         .catch( (error) => bind.onError("call " + error )) | 
|                         .then( (response) =>  bind.onReceiveCall.call(bind,response) ) | 
|                         .catch( (error) => bind.onError("call " + error )) | 
|                  | 
|                 } | 
|                 , function(error) { | 
|                     console.log ("setLocalDescription error:" + JSON.stringify(error));  | 
|                 } ); | 
|              | 
|         }, function(error) {  | 
|             alert("Create offer error:" + JSON.stringify(error)); | 
|         }); | 
|   | 
|     } catch (e) { | 
|         this.disconnect(); | 
|         alert("connect error: " + e); | 
|     }         | 
| } | 
|   | 
|   | 
| WebRtcStreamer.prototype.getIceCandidate = function() { | 
|     fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid) | 
|         .then(this._handleHttpErrors) | 
|         .then( (response) => (response.json()) ) | 
|         .then( (response) =>  this.onReceiveCandidate.call(this, response)) | 
|         .catch( (error) => bind.onError("getIceCandidate " + error )) | 
| } | 
|                      | 
| /* | 
| * create RTCPeerConnection  | 
| */ | 
| WebRtcStreamer.prototype.createPeerConnection = function() { | 
|     console.log("createPeerConnection  config: " + JSON.stringify(this.pcConfig)); | 
|     this.pc = new RTCPeerConnection(this.pcConfig); | 
|     var pc = this.pc; | 
|     pc.peerid = Math.random();         | 
|      | 
|     var bind = this; | 
|     pc.onicecandidate = function(evt) { bind.onIceCandidate.call(bind, evt); }; | 
|     pc.onaddstream    = function(evt) { bind.onAddStream.call(bind,evt); }; | 
|     pc.oniceconnectionstatechange = function(evt) {   | 
|         console.log("oniceconnectionstatechange  state: " + pc.iceConnectionState); | 
|         if (bind.videoElement) { | 
|             if (pc.iceConnectionState === "connected") { | 
|                 bind.videoElement.style.opacity = "1.0"; | 
|             }             | 
|             else if (pc.iceConnectionState === "disconnected") { | 
|                 bind.videoElement.style.opacity = "0.25"; | 
|             }             | 
|             else if ( (pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed") )  { | 
|                 bind.videoElement.style.opacity = "0.5"; | 
|             } else if (pc.iceConnectionState === "new") { | 
|                 bind.getIceCandidate.call(bind) | 
|             } | 
|         } | 
|     } | 
|     pc.ondatachannel = function(evt) {   | 
|         console.log("remote datachannel created:"+JSON.stringify(evt)); | 
|          | 
|         evt.channel.onopen = function () { | 
|             console.log("remote datachannel open"); | 
|             this.send("remote channel openned"); | 
|         } | 
|         evt.channel.onmessage = function (event) { | 
|             console.log("remote datachannel recv:"+JSON.stringify(event.data)); | 
|         } | 
|     } | 
|     pc.onicegatheringstatechange = function() { | 
|         if (pc.iceGatheringState === "complete") { | 
|             const recvs = pc.getReceivers(); | 
|          | 
|             recvs.forEach((recv) => { | 
|               if (recv.track && recv.track.kind === "video") { | 
|                 console.log("codecs:" + JSON.stringify(recv.getParameters().codecs)) | 
|               } | 
|             }); | 
|           } | 
|     } | 
|   | 
|     try { | 
|         var dataChannel = pc.createDataChannel("ClientDataChannel"); | 
|         dataChannel.onopen = function() { | 
|             console.log("local datachannel open"); | 
|             this.send("local channel openned"); | 
|         } | 
|         dataChannel.onmessage = function(evt) { | 
|             console.log("local datachannel recv:"+JSON.stringify(evt.data)); | 
|         } | 
|     } catch (e) { | 
|         console.log("Cannor create datachannel error: " + e); | 
|     }     | 
|      | 
|     console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) ); | 
|     return pc; | 
| } | 
|   | 
|   | 
| /* | 
| * RTCPeerConnection IceCandidate callback | 
| */ | 
| WebRtcStreamer.prototype.onIceCandidate = function (event) { | 
|     if (event.candidate) { | 
|         if (this.pc.currentRemoteDescription)  { | 
|             this.addIceCandidate(this.pc.peerid, event.candidate);                     | 
|         } else { | 
|             this.earlyCandidates.push(event.candidate); | 
|         } | 
|     }  | 
|     else { | 
|         console.log("End of candidates."); | 
|     } | 
| } | 
|   | 
|   | 
| WebRtcStreamer.prototype.addIceCandidate = function(peerid, candidate) { | 
|     fetch(this.srvurl + "/api/addIceCandidate?peerid="+peerid, { method: "POST", body: JSON.stringify(candidate) }) | 
|         .then(this._handleHttpErrors) | 
|         .then( (response) => (response.json()) ) | 
|         .then( (response) =>  {console.log("addIceCandidate ok:" + response)}) | 
|         .catch( (error) => this.onError("addIceCandidate " + error )) | 
| } | 
|                  | 
| /* | 
| * RTCPeerConnection AddTrack callback | 
| */ | 
| WebRtcStreamer.prototype.onAddStream = function(event) { | 
|     console.log("Remote track added:" +  JSON.stringify(event)); | 
|      | 
|     this.videoElement.srcObject = event.stream; | 
|     var promise = this.videoElement.play(); | 
|     if (promise !== undefined) { | 
|       var bind = this; | 
|       promise.catch(function(error) { | 
|         console.warn("error:"+error); | 
|         bind.videoElement.setAttribute("controls", true); | 
|       }); | 
|     } | 
| } | 
|          | 
| /* | 
| * AJAX /call callback | 
| */ | 
| WebRtcStreamer.prototype.onReceiveCall = function(dataJson) { | 
|     var bind = this; | 
|     console.log("offer: " + JSON.stringify(dataJson)); | 
|     var descr = new RTCSessionDescription(dataJson); | 
|     this.pc.setRemoteDescription(descr | 
|         , function()      {  | 
|             console.log ("setRemoteDescription ok"); | 
|             while (bind.earlyCandidates.length) { | 
|                 var candidate = bind.earlyCandidates.shift(); | 
|                 bind.addIceCandidate.call(bind, bind.pc.peerid, candidate);                 | 
|             } | 
|          | 
|             bind.getIceCandidate.call(bind) | 
|         } | 
|         , function(error) {  | 
|             console.log ("setRemoteDescription error:" + JSON.stringify(error));  | 
|         }); | 
| }     | 
|   | 
| /* | 
| * AJAX /getIceCandidate callback | 
| */ | 
| WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) { | 
|     console.log("candidate: " + JSON.stringify(dataJson)); | 
|     if (dataJson) { | 
|         for (var i=0; i<dataJson.length; i++) { | 
|             var candidate = new RTCIceCandidate(dataJson[i]); | 
|              | 
|             console.log("Adding ICE candidate :" + JSON.stringify(candidate) ); | 
|             this.pc.addIceCandidate(candidate | 
|                 , function()      { console.log ("addIceCandidate OK"); } | 
|                 , function(error) { console.log ("addIceCandidate error:" + JSON.stringify(error)); } ); | 
|         } | 
|         this.pc.addIceCandidate(); | 
|     } | 
| } | 
|   | 
|   | 
| /* | 
| * AJAX callback for Error | 
| */ | 
| WebRtcStreamer.prototype.onError = function(status) { | 
|     console.log("onError:" + status); | 
| } | 
|   | 
| return WebRtcStreamer; | 
| })(); | 
|   | 
| if (typeof window !== 'undefined' && typeof window.document !== 'undefined') { | 
|     window.WebRtcStreamer = WebRtcStreamer; | 
| } | 
| if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { | 
|     module.exports = WebRtcStreamer; | 
| } |