diff --git a/coffee/gum.coffee b/coffee/gum.coffee index 7e8f64f..d2f230d 100644 --- a/coffee/gum.coffee +++ b/coffee/gum.coffee @@ -5,7 +5,9 @@ palava = @palava class palava.Gum extends @EventEmitter constructor: (config) -> @config = config || { video: true, audio: true } - @stream = null + @stream = null # this stream switches from localStream to displayStream on request + @localStream = null + @displayStream = null changeConfig: (config) => @config = config @@ -17,6 +19,7 @@ class palava.Gum extends @EventEmitter @config ).then( (stream) => + @localStream = stream.clone() @stream = stream @emit 'stream_ready', stream ).catch( @@ -24,9 +27,36 @@ class palava.Gum extends @EventEmitter @emit 'stream_error', error ) + requestDisplaySharing: => + navigator.mediaDevices.getDisplayMedia( + {video:true} + ).then( + (stream) => + # add audio track to the display stream (if any) + if @localStream.getAudioTracks().length > 0 + stream.addTrack(@localStream.getAudioTracks()[0], @localStream) + @displayStream = stream.clone() + @stream = stream + @emit 'display_stream_ready', stream + ).catch( + (error) => + @emit 'display_stream_error', error + ) + + stopDisplaySharing: => + @stream = @localStream + @emit 'display_stream_stop', @displayStream + @displayStream = null + getStream: => @stream + getLocalStream: => + @localStream + + getDisplayStream: => + @displayStream + releaseStream: => if @stream @stream.getAudioTracks().forEach( (track) => track.stop() ) diff --git a/coffee/local_peer.coffee b/coffee/local_peer.coffee index b4a524f..b9fee7e 100644 --- a/coffee/local_peer.coffee +++ b/coffee/local_peer.coffee @@ -33,6 +33,10 @@ class palava.LocalPeer extends palava.Peer @emit 'stream_ready', e @userMedia.on 'stream_error', (e) => @emit 'stream_error', e + @userMedia.on 'display_stream_ready', (e) => + @emit 'display_stream_ready', e + @userMedia.on 'display_stream_error', (e) => + @emit 'display_stream_error', e if @getStream() @ready = true @emit 'stream_ready' @@ -54,6 +58,9 @@ class palava.LocalPeer extends palava.Peer getStream: => @userMedia.getStream() + requestDisplaySharing: => + @userMedia.requestDisplaySharing() + # Updates the status of the local peer. The status is extended or updated with the given items. # # @param status [Object] Object containing the new items diff --git a/coffee/remote_peer.coffee b/coffee/remote_peer.coffee index 5de671f..b9e2d28 100644 --- a/coffee/remote_peer.coffee +++ b/coffee/remote_peer.coffee @@ -28,10 +28,14 @@ class palava.RemotePeer extends palava.Peer @dataChannels = {} + @offers = offers + @makingOffer = false + @ignoreOffer = false + @setRemoteAnswerPending = false + @setupRoom() - @setupPeerConnection(offers) @setupDistributor() - + @setupPeerConnection(offers) if offers @sendOffer() @@ -62,8 +66,7 @@ class palava.RemotePeer extends palava.Peer credential: @turnCredentials.password {iceServers: options} - # Sets up the peer connection and its events - # + # Sets up the peer connection and its events # # @nodoc # setupPeerConnection: (offers) => @@ -87,6 +90,9 @@ class palava.RemotePeer extends palava.Peer @ready = false @emit 'stream_removed' + @peerConnection.onnegotiationneeded = () => + @sendOffer() + @peerConnection.oniceconnectionstatechange = (event) => connectionState = event.target.iceConnectionState @@ -107,12 +113,33 @@ class palava.RemotePeer extends palava.Peer @error = "connection_closed" @emit 'connection_closed' - # TODO onsignalingstatechange - if @room.localPeer.getStream() - @peerConnection.addStream @room.localPeer.getStream() - else - # not suppored yet + for track in @room.localPeer.getStream().getTracks() + @peerConnection.addTrack(track, @room.localPeer.getStream()) + + @room.localPeer.on 'display_stream_ready', (stream) => + videoSender = @peerConnection.getSenders().find( + (sender) => sender.track.kind == "video" + ) + if videoSender + # if there is a local video track then replace it because that's faster + # and does not need renegotiation + videoSender.replaceTrack(track) + else + @peerConnection.addTrack(stream.getVideoTracks()[0], + @room.localPeer.getStream()) + + @room.localPeer.on 'display_stream_stop', (stream) => + localVideoTracks = @room.localPeer.getLocalStream().getVideoTracks() + if localVideoTracks.length > 0 + # if there was a local video track then reuse the display video track + # because it's faster and does not need renegotiation + videoSender = @peerConnection.getSenders().find( + (sender) => sender.track.kind == "video" + ) + videoSender.replaceTrack(localVideoTracks[0]) + else + @peerConnection.removeTrack(stream.getVideoTracks()[0]) # data channel setup @@ -140,8 +167,6 @@ class palava.RemotePeer extends palava.Peer # @nodoc # setupDistributor: => - # TODO _ in events also in rtc-server - # TODO consistent protocol naming @distributor = new palava.Distributor(@room.channel, @id) @distributor.on 'peer_left', (msg) => @@ -157,16 +182,22 @@ class palava.RemotePeer extends palava.Peer return if msg.candidate == "" candidate = new RTCIceCandidate({candidate: msg.candidate, sdpMLineIndex: msg.sdpmlineindex, sdpMid: msg.sdpmid}) unless @room.options.filterIceCandidateTypes.includes(candidate.type) - @peerConnection.addIceCandidate(candidate) + await @peerConnection.addIceCandidate(candidate) @distributor.on 'offer', (msg) => - @peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp)) - @emit 'offer' # ignored so far + # we sent an offer already and are the preferred offerer, so we don't back down + return if @pendingOffer && @offers + + # we backed down, so we drop the pending offer and choose the other peer's offer + @pendingOffer = null + + await @peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp)) @sendAnswer() @distributor.on 'answer', (msg) => - @peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp)) - @emit 'answer' # ignored so far + await @peerConnection.setLocalDescription(@pendingOffer) if @pendingOffer + @pendingOffer = null + await @peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp)) @distributor.on 'peer_updated_status', (msg) => @status = msg.status @@ -199,30 +230,32 @@ class palava.RemotePeer extends palava.Peer @on 'oaerror', (e) => @room.emit('peer_oaerror', @, e) @on 'channel_ready', (n, c) => @room.emit('peer_channel_ready', @, n, c) - # Sends the offer for a peer connection - # - # @nodoc + sendMessage: (data) => + @distributor.send + event: 'message' + data: data + + # Sends the offer to create a peer connection # sendOffer: => - @peerConnection.createOffer @sdpSender('offer'), @oaError, palava.browser.getConstraints() + @peerConnection.createOffer @sdpSender('offer'), @oaError, palava.browser.getConstraints() if @peerConnection.signalingState == "stable" && !@pendingOffer # Sends the answer to create a peer connection # sendAnswer: => @peerConnection.createAnswer @sdpSender('answer'), @oaError, palava.browser.getConstraints() - sendMessage: (data) => - @distributor.send - event: 'message' - data: data - # Helper for sending sdp + # Send offer/answer # # @nodoc # sdpSender: (event) => (sdp) => - @peerConnection.setLocalDescription(sdp) + if event == 'offer' + @pendingOffer = sdp + else + await @peerConnection.setLocalDescription(sdp) @distributor.send event: event sdp: sdp diff --git a/coffee/room.coffee b/coffee/room.coffee index a4fd1c3..10d49bb 100644 --- a/coffee/room.coffee +++ b/coffee/room.coffee @@ -85,6 +85,7 @@ class palava.Room extends @EventEmitter @options.ownStatus[key] = status[key] for key in status @options.ownStatus.user_agent ||= palava.browser.getUserAgent() + @options.ownStatus.user_agent_version ||= palava.browser.getUserAgentVersion() @distributor.send event: 'join_room' diff --git a/palava.bundle.js b/palava.bundle.js index 89e6869..3621890 100644 --- a/palava.bundle.js +++ b/palava.bundle.js @@ -3547,4 +3547,4 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ -(function(){}).call(this),function(){this.palava={browser:{}}}.call(this),function(){"object"==typeof module&&"object"==typeof module.exports&&(module.exports=this.palava),"object"!=typeof EventEmitter&&"function"==typeof require?this.EventEmitter=require("wolfy87-eventemitter"):this.EventEmitter=EventEmitter,"object"!=typeof adapter&&"function"==typeof require?this.adapter=require("webrtc-adapter/out/adapter_no_edge"):this.adapter=adapter}.call(this),function(){var t,r;r=this.palava,t=this.adapter,r.browser.isMozilla=function(){return"firefox"===t.browserDetails.browser},r.browser.isChrome=function(){return"chrome"===t.browserDetails.browser},r.browser.getUserAgent=function(){return t.browserDetails.browser},r.browser.getUserAgentVersion=function(){return t.browserDetails.version},r.browser.checkForWebrtcError=function(){try{new window.RTCPeerConnection({iceServers:[]})}catch(t){return t}return!(window.RTCPeerConnection&&window.RTCIceCandidate&&window.RTCSessionDescription&&navigator.mediaDevices&&navigator.mediaDevices.getUserMedia)},r.browser.getConstraints=function(){return{optional:[],mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0}}},r.browser.getPeerConnectionOptions=function(){return r.browser.isChrome()?{optional:[{DtlsSrtpKeyAgreement:!0}]}:{}},r.browser.registerFullscreen=function(t,e){return console.log("DEPRECATED: palava.browser.registerFullscreen will be removed from the palava library in early 2021"),t.requestFullscreen?t.addEventListener(e,function(){return this.requestFullscreen()}):t.mozRequestFullScreen?t.addEventListener(e,function(){return this.mozRequestFullScreen()}):t.webkitRequestFullscreen?t.addEventListener(e,function(){return this.webkitRequestFullscreen()}):void 0},r.browser.attachMediaStream=function(t,e){return e?t.srcObject=e:(t.pause(),t.srcObject=null)},r.browser.attachPeer=function(t,e){var n;return n=function(){return r.browser.attachMediaStream(t,e.getStream()),e.isLocal()&&t.setAttribute("muted",!0),t.play()},e.getStream()?n():e.on("stream_ready",function(){return n()})}}.call(this),function(){var n=function(t,e){return function(){return t.apply(e,arguments)}},r=function(t,e){function n(){this.constructor=t}for(var r in e)i.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},i={}.hasOwnProperty;this.palava.Gum=function(t){function e(t){this.releaseStream=n(this.releaseStream,this),this.getStream=n(this.getStream,this),this.requestStream=n(this.requestStream,this),this.changeConfig=n(this.changeConfig,this),this.config=t||{video:!0,audio:!0},this.stream=null}return r(e,t),e.prototype.changeConfig=function(t){return this.config=t,this.releaseStream(),this.requestStream()},e.prototype.requestStream=function(){return navigator.mediaDevices.getUserMedia(this.config).then((n=this,function(t){return n.stream=t,n.emit("stream_ready",t)}))["catch"]((e=this,function(t){return e.emit("stream_error",t)}));var e,n},e.prototype.getStream=function(){return this.stream},e.prototype.releaseStream=function(){return!!this.stream&&(this.stream.getAudioTracks().forEach(function(t){return t.stop()}),this.stream.getVideoTracks().forEach(function(t){return t.stop()}),this.stream=null,this.emit("stream_released",this),!0)},e}(this.EventEmitter)}.call(this),function(){var e,n=function(t,e){return function(){return t.apply(e,arguments)}};(e=this.palava).Identity=function(){function t(t){this.getStatus=n(this.getStatus,this),this.getName=n(this.getName,this),this.userMediaConfig=t.userMediaConfig,this.status=t.status||{},this.status.name=t.name}return t.prototype.newUserMedia=function(){return new e.Gum(this.userMediaConfig)},t.prototype.getName=function(){return this.name},t.prototype.getStatus=function(){return this.status},t}()}.call(this),function(){var r,i=function(t,e){return function(){return t.apply(e,arguments)}},n=function(t,e){function n(){this.constructor=t}for(var r in e)o.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},o={}.hasOwnProperty;(r=this.palava).Peer=function(t){function e(t,e){var n;this.isRemote=i(this.isRemote,this),this.isLocal=i(this.isLocal,this),this.isReady=i(this.isReady,this),this.isMuted=i(this.isMuted,this),this.getError=i(this.getError,this),this.hasError=i(this.hasError,this),this.hasVideo=i(this.hasVideo,this),this.transmitsVideo=i(this.transmitsVideo,this),this.hasAudio=i(this.hasAudio,this),this.transmitsAudio=i(this.transmitsAudio,this),this.id=t,this.status=e||{},(n=this.status).user_agent||(n.user_agent=r.browser.getUserAgent()),this.joinTime=(new Date).getTime(),this.ready=!1,this.error=null}return n(e,t),e.prototype.transmitsAudio=function(){var t,e,n;return!!(null!=(t=this.getStream())&&null!=(e=t.getAudioTracks())&&null!=(n=e[0])?n.enabled:void 0)},e.prototype.hasAudio=function(){var t,e;return!!(null!=(t=this.getStream())&&null!=(e=t.getAudioTracks())?e[0]:void 0)},e.prototype.transmitsVideo=function(){var t,e,n;return!!(null!=(t=this.getStream())&&null!=(e=t.getVideoTracks())&&null!=(n=e[0])?n.enabled:void 0)},e.prototype.hasVideo=function(){var t,e;return!!(null!=(t=this.getStream())&&null!=(e=t.getVideoTracks())?e[0]:void 0)},e.prototype.hasError=function(){return!!this.error},e.prototype.getError=function(){return this.error},e.prototype.isMuted=function(){return!!this.muted},e.prototype.isReady=function(){return!!this.ready},e.prototype.isLocal=function(){return!!this.local},e.prototype.isRemote=function(){return!this.local},e}(this.EventEmitter)}.call(this),function(){var i,o=function(t,e){return function(){return t.apply(e,arguments)}},e=function(t,e){function n(){this.constructor=t}for(var r in e)s.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},s={}.hasOwnProperty;(i=this.palava).LocalPeer=function(t){function r(t,e,n){this.leave=o(this.leave,this),this.enableVideo=o(this.enableVideo,this),this.disableVideo=o(this.disableVideo,this),this.enableAudio=o(this.enableAudio,this),this.disableAudio=o(this.disableAudio,this),this.updateStatus=o(this.updateStatus,this),this.getStream=o(this.getStream,this),this.setupRoom=o(this.setupRoom,this),this.setupUserMedia=o(this.setupUserMedia,this),this.muted=!0,this.local=!0,r.__super__.constructor.call(this,t,e),this.room=n,this.userMedia=n.userMedia,this.setupRoom(),this.setupUserMedia()}return e(r,t),r.prototype.setupUserMedia=function(){var t,e,n;if(this.userMedia.on("stream_released",(t=this,function(){return t.ready=!1,t.emit("stream_removed")})),this.userMedia.on("stream_ready",(e=this,function(t){return e.ready=!0,e.emit("stream_ready",t)})),this.userMedia.on("stream_error",(n=this,function(t){return n.emit("stream_error",t)})),this.getStream())return this.ready=!0,this.emit("stream_ready")},r.prototype.setupRoom=function(){var t,e,n;return this.room.peers[this.id]=this.room.localPeer=this,this.on("update",(t=this,function(){return t.room.emit("peer_update",t)})),this.on("stream_ready",(e=this,function(){return e.room.emit("peer_stream_ready",e)})),this.on("stream_removed",(n=this,function(){return n.room.emit("peer_stream_removed",n)}))},r.prototype.getStream=function(){return this.userMedia.getStream()},r.prototype.updateStatus=function(t){var e,n;if(!(t&&t instanceof Object&&0!==Object.keys(t).length))return t;for(n in t)this.status[n]=t[n];return(e=this.status).user_agent||(e.user_agent=i.browser.getUserAgent()),this.room.channel.send({event:"update_status",status:this.status}),this.status},r.prototype.disableAudio=function(){var t,e,n,r,i;if(this.ready){for(r=[],t=0,e=(n=this.getStream().getAudioTracks()).length;tthis.MAX_BUFFER)return void setTimeout(this.actualSend.bind(this),1);e=(r=this.sendBuffer[0])[0],t=r[1];try{this.channel.send(e)}catch(i){return n=i,void setTimeout(this.actualSend.bind(this),1)}try{"function"==typeof t&&t()}catch(i){n=i,console.log("Exception in write callback:",n)}this.sendBuffer.shift()}else console.log("Not sending when not open!")},e}(this.EventEmitter)}.call(this),function(){var c,s=function(t,e){return function(){return t.apply(e,arguments)}},e=function(t,e){function n(){this.constructor=t}for(var r in e)i.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},i={}.hasOwnProperty;(c=this.palava).RemotePeer=function(t){function o(t,e,n,r,i){this.closePeerConnection=s(this.closePeerConnection,this),this.oaError=s(this.oaError,this),this.sdpSender=s(this.sdpSender,this),this.sendMessage=s(this.sendMessage,this),this.sendAnswer=s(this.sendAnswer,this),this.sendOffer=s(this.sendOffer,this),this.setupRoom=s(this.setupRoom,this),this.setupDistributor=s(this.setupDistributor,this),this.setupPeerConnection=s(this.setupPeerConnection,this),this.generateIceOptions=s(this.generateIceOptions,this),this.toggleMute=s(this.toggleMute,this),this.getStream=s(this.getStream,this),this.muted=!1,this.local=!1,o.__super__.constructor.call(this,t,e),this.room=n,this.remoteStream=null,this.turnCredentials=i,this.dataChannels={},this.setupRoom(),this.setupPeerConnection(r),this.setupDistributor(),r&&this.sendOffer()}return e(o,t),o.prototype.getStream=function(){return this.remoteStream},o.prototype.toggleMute=function(){return this.muted=!this.muted},o.prototype.generateIceOptions=function(){var t;return t=[],this.room.options.stun&&t.push({urls:[this.room.options.stun]}),this.room.options.turnUrls&&this.turnCredentials&&t.push({urls:this.room.options.turnUrls,username:this.turnCredentials.user,credential:this.turnCredentials.password}),{iceServers:t}},o.prototype.setupPeerConnection=function(t){var e,n,r,i,o,s,a,u,h;if(this.peerConnection=new RTCPeerConnection(this.generateIceOptions(),c.browser.getPeerConnectionOptions()),this.peerConnection.onicecandidate=(o=this,function(t){if(t.candidate)return o.distributor.send({event:"ice_candidate",sdpmlineindex:t.candidate.sdpMLineIndex,sdpmid:t.candidate.sdpMid,candidate:t.candidate.candidate})}),this.peerConnection.ontrack=(s=this,function(t){return s.remoteStream=t.streams[0],s.ready=!0,s.emit("stream_ready")}),this.peerConnection.onremovestream=(a=this,function(){return a.remoteStream=null,a.ready=!1,a.emit("stream_removed")}),this.peerConnection.oniceconnectionstatechange=(u=this,function(t){switch(t.target.iceConnectionState){case"connecting":return u.error=null,u.emit("connection_pending");case"connected":return u.error=null,u.emit("connection_established");case"failed":return u.error="connection_failed",u.emit("connection_failed");case"disconnected":return u.error="connection_disconnected",u.emit("connection_disconnected");case"closed":return u.error="connection_closed",u.emit("connection_closed")}}),this.room.localPeer.getStream()&&this.peerConnection.addStream(this.room.localPeer.getStream()),null!=this.room.options.dataChannels)if(h=this,i=function(t){var e,n;return e=t.label,n=new c.DataChannel(t),h.dataChannels[e]=n,h.emit("channel_ready",e,n)},t)for(e in r=this.room.options.dataChannels)n=r[e],this.peerConnection.createDataChannel(e,n).onopen=function(){return i(this)};else this.peerConnection.ondatachannel=function(t){return i(t.channel)};return this.peerConnection},o.prototype.setupDistributor=function(){var t,n,e,r,i,o;return this.distributor=new c.Distributor(this.room.channel,this.id),this.distributor.on("peer_left",(t=this,function(){return t.ready&&(t.remoteStream=null,t.emit("stream_removed"),t.ready=!1),t.peerConnection.close(),t.emit("left")})),this.distributor.on("ice_candidate",(n=this,function(t){var e;if(""!==t.candidate)return e=new RTCIceCandidate({candidate:t.candidate,sdpMLineIndex:t.sdpmlineindex,sdpMid:t.sdpmid}),n.room.options.filterIceCandidateTypes.includes(e.type)?void 0:n.peerConnection.addIceCandidate(e)})),this.distributor.on("offer",(e=this,function(t){return e.peerConnection.setRemoteDescription(new RTCSessionDescription(t.sdp)),e.emit("offer"),e.sendAnswer()})),this.distributor.on("answer",(r=this,function(t){return r.peerConnection.setRemoteDescription(new RTCSessionDescription(t.sdp)),r.emit("answer")})),this.distributor.on("peer_updated_status",(i=this,function(t){return i.status=t.status,i.emit("update")})),this.distributor.on("message",(o=this,function(t){return o.emit("message",t.data)})),this.distributor},o.prototype.setupRoom=function(){var t,e,n,r,i,o,s,a,u,h,c,p,d;return(this.room.peers[this.id]=this).on("left",(t=this,function(){return delete t.room.peers[t.id],t.room.emit("peer_left",t)})),this.on("offer",(e=this,function(){return e.room.emit("peer_offer",e)})),this.on("answer",(n=this,function(){return n.room.emit("peer_answer",n)})),this.on("update",(r=this,function(){return r.room.emit("peer_update",r)})),this.on("stream_ready",(i=this,function(){return i.room.emit("peer_stream_ready",i)})),this.on("stream_removed",(o=this,function(){return o.room.emit("peer_stream_removed",o)})),this.on("connection_pending",(s=this,function(){return s.room.emit("peer_connection_pending",s)})),this.on("connection_established",(a=this,function(){return a.room.emit("peer_connection_established",a)})),this.on("connection_failed",(u=this,function(){return u.room.emit("peer_connection_failed",u)})),this.on("connection_disconnected",(h=this,function(){return h.room.emit("peer_connection_disconnected",h)})),this.on("connection_closed",(c=this,function(){return c.room.emit("peer_connection_closed",c)})),this.on("oaerror",(p=this,function(t){return p.room.emit("peer_oaerror",p,t)})),this.on("channel_ready",(d=this,function(t,e){return d.room.emit("peer_channel_ready",d,t,e)}))},o.prototype.sendOffer=function(){return this.peerConnection.createOffer(this.sdpSender("offer"),this.oaError,c.browser.getConstraints())},o.prototype.sendAnswer=function(){return this.peerConnection.createAnswer(this.sdpSender("answer"),this.oaError,c.browser.getConstraints())},o.prototype.sendMessage=function(t){return this.distributor.send({event:"message",data:t})},o.prototype.sdpSender=function(e){return n=this,function(t){return n.peerConnection.setLocalDescription(t),n.distributor.send({event:e,sdp:t})};var n},o.prototype.oaError=function(t){return this.emit("oaerror",t)},o.prototype.closePeerConnection=function(){var t;return null!=(t=this.peerConnection)&&t.close(),this.peerConnection=null},o}(c.Peer)}.call(this),function(){var u,i=function(t,e){return function(){return t.apply(e,arguments)}},n=function(t,e){function n(){this.constructor=t}for(var r in e)o.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},o={}.hasOwnProperty;(u=this.palava).Room=function(t){function e(t,e,n,r){null==r&&(r={}),this.getAllPeers=i(this.getAllPeers,this),this.getRemotePeers=i(this.getRemotePeers,this),this.getLocalPeer=i(this.getLocalPeer,this),this.getPeerById=i(this.getPeerById,this),this.destroy=i(this.destroy,this),this.leave=i(this.leave,this),this.join=i(this.join,this),this.setupDistributor=i(this.setupDistributor,this),this.setupOptions=i(this.setupOptions,this),this.setupUserMedia=i(this.setupUserMedia,this),this.id=t,this.userMedia=n,this.channel=e,this.peers={},this.options=r,this.setupUserMedia(),this.setupDistributor(),this.setupOptions()}return n(e,t),e.prototype.setupUserMedia=function(){var e,n,t;return this.userMedia.on("stream_ready",(e=this,function(t){return e.emit("local_stream_ready",t)})),this.userMedia.on("stream_error",(n=this,function(t){return n.emit("local_stream_error",t)})),this.userMedia.on("stream_released",(t=this,function(){return t.emit("local_stream_removed")}))},e.prototype.setupOptions=function(){var t,e,n;return(t=this.options).joinTimeout||(t.joinTimeout=1e3),(e=this.options).ownStatus||(e.ownStatus={}),(n=this.options).filterIceCandidateTypes||(n.filterIceCandidateTypes=[])},e.prototype.setupDistributor=function(){var a,r,e,n;return this.distributor=new u.Distributor(this.channel),this.distributor.on("joined_room",(a=this,function(t){var e,n,r,i,o,s;for(clearTimeout(a.joinCheckTimeout),s=t.turn_user?{user:t.turn_user,password:t.turn_password}:null,new u.LocalPeer(t.own_id,a.options.ownStatus,a),e=0,n=(o=t.peers).length;ethis.MAX_BUFFER)return void setTimeout(this.actualSend.bind(this),1);e=(n=this.sendBuffer[0])[0],t=n[1];try{this.channel.send(e)}catch(i){return r=i,void setTimeout(this.actualSend.bind(this),1)}try{"function"==typeof t&&t()}catch(i){r=i,console.log("Exception in write callback:",r)}this.sendBuffer.shift()}else console.log("Not sending when not open!")},e}(this.EventEmitter)}.call(this),function(){var _,s=function(t,e){return function(){return t.apply(e,arguments)}},e=function(t,e){function r(){this.constructor=t}for(var n in e)i.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t},i={}.hasOwnProperty;(_=this.palava).RemotePeer=function(t){function o(t,e,r,n,i){this.closePeerConnection=s(this.closePeerConnection,this),this.oaError=s(this.oaError,this),this.sdpSender=s(this.sdpSender,this),this.sendMessage=s(this.sendMessage,this),this.sendAnswer=s(this.sendAnswer,this),this.sendOffer=s(this.sendOffer,this),this.setupRoom=s(this.setupRoom,this),this.setupDistributor=s(this.setupDistributor,this),this.setupPeerConnection=s(this.setupPeerConnection,this),this.generateIceOptions=s(this.generateIceOptions,this),this.toggleMute=s(this.toggleMute,this),this.getStream=s(this.getStream,this),this.muted=!1,this.local=!1,o.__super__.constructor.call(this,t,e),this.room=r,this.remoteStream=null,this.turnCredentials=i,this.dataChannels={},this.setupRoom(),this.setupPeerConnection(n),this.setupDistributor(),this.offers=n}return e(o,t),o.prototype.getStream=function(){return this.remoteStream},o.prototype.toggleMute=function(){return this.muted=!this.muted},o.prototype.generateIceOptions=function(){var t;return t=[],this.room.options.stun&&t.push({urls:[this.room.options.stun]}),this.room.options.turnUrls&&this.turnCredentials&&t.push({urls:this.room.options.turnUrls,username:this.turnCredentials.user,credential:this.turnCredentials.password}),{iceServers:t}},o.prototype.setupPeerConnection=function(t){var e,r,n,i,o,s,a,u,h,c,p,l,d,m,f,g;if(this.peerConnection=new RTCPeerConnection(this.generateIceOptions(),_.browser.getPeerConnectionOptions()),this.peerConnection.onicecandidate=(h=this,function(t){if(t.candidate)return h.distributor.send({event:"ice_candidate",sdpmlineindex:t.candidate.sdpMLineIndex,sdpmid:t.candidate.sdpMid,candidate:t.candidate.candidate})}),this.peerConnection.ontrack=(c=this,function(t){return c.remoteStream=t.streams[0],c.ready=!0,c.emit("stream_ready")}),this.peerConnection.onremovestream=(p=this,function(){return p.remoteStream=null,p.ready=!1,p.emit("stream_removed")}),this.peerConnection.onnegotiationneeded=(l=this,function(){return l.sendOffer()}),this.peerConnection.oniceconnectionstatechange=(d=this,function(t){switch(t.target.iceConnectionState){case"connecting":return d.error=null,d.emit("connection_pending");case"connected":return d.error=null,d.emit("connection_established");case"failed":return d.error="connection_failed",d.emit("connection_failed");case"disconnected":return d.error="connection_disconnected",d.emit("connection_disconnected");case"closed":return d.error="connection_closed",d.emit("connection_closed")}}),this.room.localPeer.getStream())for(e=0,n=(o=this.room.localPeer.getStream().getTracks()).length;e. function Gum(config) { this.releaseStream = bind(this.releaseStream, this); + this.getDisplayStream = bind(this.getDisplayStream, this); + this.getLocalStream = bind(this.getLocalStream, this); this.getStream = bind(this.getStream, this); + this.stopDisplaySharing = bind(this.stopDisplaySharing, this); + this.requestDisplaySharing = bind(this.requestDisplaySharing, this); this.requestStream = bind(this.requestStream, this); this.changeConfig = bind(this.changeConfig, this); this.config = config || { @@ -179,6 +183,8 @@ along with this program. If not, see . audio: true }; this.stream = null; + this.localStream = null; + this.displayStream = null; } Gum.prototype.changeConfig = function(config) { @@ -190,6 +196,7 @@ along with this program. If not, see . Gum.prototype.requestStream = function() { return navigator.mediaDevices.getUserMedia(this.config).then((function(_this) { return function(stream) { + _this.localStream = stream.clone(); _this.stream = stream; return _this.emit('stream_ready', stream); }; @@ -200,10 +207,43 @@ along with this program. If not, see . })(this)); }; + Gum.prototype.requestDisplaySharing = function() { + return navigator.mediaDevices.getDisplayMedia({ + video: true + }).then((function(_this) { + return function(stream) { + if (_this.localStream.getAudioTracks().length > 0) { + stream.addTrack(_this.localStream.getAudioTracks()[0], _this.localStream); + } + _this.displayStream = stream.clone(); + _this.stream = stream; + return _this.emit('display_stream_ready', stream); + }; + })(this))["catch"]((function(_this) { + return function(error) { + return _this.emit('display_stream_error', error); + }; + })(this)); + }; + + Gum.prototype.stopDisplaySharing = function() { + this.stream = this.localStream; + this.emit('display_stream_stop', this.displayStream); + return this.displayStream = null; + }; + Gum.prototype.getStream = function() { return this.stream; }; + Gum.prototype.getLocalStream = function() { + return this.localStream; + }; + + Gum.prototype.getDisplayStream = function() { + return this.displayStream; + }; + Gum.prototype.releaseStream = function() { if (this.stream) { this.stream.getAudioTracks().forEach((function(_this) { @@ -379,6 +419,7 @@ along with this program. If not, see . this.enableAudio = bind(this.enableAudio, this); this.disableAudio = bind(this.disableAudio, this); this.updateStatus = bind(this.updateStatus, this); + this.requestDisplaySharing = bind(this.requestDisplaySharing, this); this.getStream = bind(this.getStream, this); this.setupRoom = bind(this.setupRoom, this); this.setupUserMedia = bind(this.setupUserMedia, this); @@ -409,6 +450,16 @@ along with this program. If not, see . return _this.emit('stream_error', e); }; })(this)); + this.userMedia.on('display_stream_ready', (function(_this) { + return function(e) { + return _this.emit('display_stream_ready', e); + }; + })(this)); + this.userMedia.on('display_stream_error', (function(_this) { + return function(e) { + return _this.emit('display_stream_error', e); + }; + })(this)); if (this.getStream()) { this.ready = true; return this.emit('stream_ready'); @@ -438,6 +489,10 @@ along with this program. If not, see . return this.userMedia.getStream(); }; + LocalPeer.prototype.requestDisplaySharing = function() { + return this.userMedia.requestDisplaySharing(); + }; + LocalPeer.prototype.updateStatus = function(status) { var base, key; if (!status || !(status instanceof Object) || Object.keys(status).length === 0) { @@ -681,9 +736,7 @@ along with this program. If not, see . this.setupRoom(); this.setupPeerConnection(offers); this.setupDistributor(); - if (offers) { - this.sendOffer(); - } + this.offers = offers; } RemotePeer.prototype.getStream = function() { @@ -715,7 +768,7 @@ along with this program. If not, see . }; RemotePeer.prototype.setupPeerConnection = function(offers) { - var channel, label, options, ref, registerChannel; + var channel, i, label, len, options, ref, ref1, registerChannel, track; this.peerConnection = new RTCPeerConnection(this.generateIceOptions(), palava.browser.getPeerConnectionOptions()); this.peerConnection.onicecandidate = (function(_this) { return function(event) { @@ -743,6 +796,11 @@ along with this program. If not, see . return _this.emit('stream_removed'); }; })(this); + this.peerConnection.onnegotiationneeded = (function(_this) { + return function() { + return _this.sendOffer(); + }; + })(this); this.peerConnection.oniceconnectionstatechange = (function(_this) { return function(event) { var connectionState; @@ -767,10 +825,39 @@ along with this program. If not, see . }; })(this); if (this.room.localPeer.getStream()) { - this.peerConnection.addStream(this.room.localPeer.getStream()); - } else { - + ref = this.room.localPeer.getStream().getTracks(); + for (i = 0, len = ref.length; i < len; i++) { + track = ref[i]; + this.peerConnection.addTrack(track, this.room.localPeer.getStream()); + } } + this.room.localPeer.on('display_stream_ready', (function(_this) { + return function(stream) { + var videoSender; + videoSender = _this.peerConnection.getSenders().find(function(sender) { + return sender.track.kind === "video"; + }); + if (videoSender) { + return videoSender.replaceTrack(track); + } else { + return _this.peerConnection.addTrack(stream.getVideoTracks()[0], _this.room.localPeer.getStream()); + } + }; + })(this)); + this.room.localPeer.on('display_stream_stop', (function(_this) { + return function(stream) { + var localVideoTracks, videoSender; + localVideoTracks = _this.room.localPeer.getLocalStream().getVideoTracks(); + if (localVideoTracks.length > 0) { + videoSender = _this.peerConnection.getSenders().find(function(sender) { + return sender.track.kind === "video"; + }); + return videoSender.replaceTrack(localVideoTracks[0]); + } else { + return _this.peerConnection.removeTrack(stream.getVideoTracks()[0]); + } + }; + })(this)); if (this.room.options.dataChannels != null) { registerChannel = (function(_this) { return function(channel) { @@ -782,9 +869,9 @@ along with this program. If not, see . }; })(this); if (offers) { - ref = this.room.options.dataChannels; - for (label in ref) { - options = ref[label]; + ref1 = this.room.options.dataChannels; + for (label in ref1) { + options = ref1[label]; channel = this.peerConnection.createDataChannel(label, options); channel.onopen = function() { return registerChannel(this); @@ -832,6 +919,14 @@ along with this program. If not, see . })(this)); this.distributor.on('offer', (function(_this) { return function(msg) { + if (_this.peerConnection.signalingState !== "stable") { + if (_this.offers) { + return; + } + _this.peerConnection.setLocalDescription({ + type: "rollback" + }); + } _this.peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp)); _this.emit('offer'); return _this.sendAnswer(); @@ -945,6 +1040,9 @@ along with this program. If not, see . RemotePeer.prototype.sdpSender = function(event) { return (function(_this) { return function(sdp) { + if (event === 'offer' && _this.peerConnection.signalingState !== 'stable') { + return; + } _this.peerConnection.setLocalDescription(sdp); return _this.distributor.send({ event: event, @@ -1598,7 +1696,7 @@ along with this program. If not, see . palava.LIB_VERSION = '2.2.0'; - palava.LIB_COMMIT = 'v2.1.0-12-g412482867a-dirty'; + palava.LIB_COMMIT = 'v2.2.0-1-gb4ef48ea59-dirty'; palava.protocol_identifier = function() { return palava.PROTOCOL_NAME = "palava.1.0"; diff --git a/palava.min.js b/palava.min.js index cd3dfa8..3a0efc4 100644 --- a/palava.min.js +++ b/palava.min.js @@ -21,4 +21,4 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ -(function(){}).call(this),function(){this.palava={browser:{}}}.call(this),function(){"object"==typeof module&&"object"==typeof module.exports&&(module.exports=this.palava),"object"!=typeof EventEmitter&&"function"==typeof require?this.EventEmitter=require("wolfy87-eventemitter"):this.EventEmitter=EventEmitter,"object"!=typeof adapter&&"function"==typeof require?this.adapter=require("webrtc-adapter/out/adapter_no_edge"):this.adapter=adapter}.call(this),function(){var t,r;r=this.palava,t=this.adapter,r.browser.isMozilla=function(){return"firefox"===t.browserDetails.browser},r.browser.isChrome=function(){return"chrome"===t.browserDetails.browser},r.browser.getUserAgent=function(){return t.browserDetails.browser},r.browser.getUserAgentVersion=function(){return t.browserDetails.version},r.browser.checkForWebrtcError=function(){try{new window.RTCPeerConnection({iceServers:[]})}catch(t){return t}return!(window.RTCPeerConnection&&window.RTCIceCandidate&&window.RTCSessionDescription&&navigator.mediaDevices&&navigator.mediaDevices.getUserMedia)},r.browser.getConstraints=function(){return{optional:[],mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0}}},r.browser.getPeerConnectionOptions=function(){return r.browser.isChrome()?{optional:[{DtlsSrtpKeyAgreement:!0}]}:{}},r.browser.registerFullscreen=function(t,e){return console.log("DEPRECATED: palava.browser.registerFullscreen will be removed from the palava library in early 2021"),t.requestFullscreen?t.addEventListener(e,function(){return this.requestFullscreen()}):t.mozRequestFullScreen?t.addEventListener(e,function(){return this.mozRequestFullScreen()}):t.webkitRequestFullscreen?t.addEventListener(e,function(){return this.webkitRequestFullscreen()}):void 0},r.browser.attachMediaStream=function(t,e){return e?t.srcObject=e:(t.pause(),t.srcObject=null)},r.browser.attachPeer=function(t,e){var n;return n=function(){return r.browser.attachMediaStream(t,e.getStream()),e.isLocal()&&t.setAttribute("muted",!0),t.play()},e.getStream()?n():e.on("stream_ready",function(){return n()})}}.call(this),function(){var n=function(t,e){return function(){return t.apply(e,arguments)}},r=function(t,e){function n(){this.constructor=t}for(var r in e)i.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},i={}.hasOwnProperty;this.palava.Gum=function(t){function e(t){this.releaseStream=n(this.releaseStream,this),this.getStream=n(this.getStream,this),this.requestStream=n(this.requestStream,this),this.changeConfig=n(this.changeConfig,this),this.config=t||{video:!0,audio:!0},this.stream=null}return r(e,t),e.prototype.changeConfig=function(t){return this.config=t,this.releaseStream(),this.requestStream()},e.prototype.requestStream=function(){return navigator.mediaDevices.getUserMedia(this.config).then((n=this,function(t){return n.stream=t,n.emit("stream_ready",t)}))["catch"]((e=this,function(t){return e.emit("stream_error",t)}));var e,n},e.prototype.getStream=function(){return this.stream},e.prototype.releaseStream=function(){return!!this.stream&&(this.stream.getAudioTracks().forEach(function(t){return t.stop()}),this.stream.getVideoTracks().forEach(function(t){return t.stop()}),this.stream=null,this.emit("stream_released",this),!0)},e}(this.EventEmitter)}.call(this),function(){var e,n=function(t,e){return function(){return t.apply(e,arguments)}};(e=this.palava).Identity=function(){function t(t){this.getStatus=n(this.getStatus,this),this.getName=n(this.getName,this),this.userMediaConfig=t.userMediaConfig,this.status=t.status||{},this.status.name=t.name}return t.prototype.newUserMedia=function(){return new e.Gum(this.userMediaConfig)},t.prototype.getName=function(){return this.name},t.prototype.getStatus=function(){return this.status},t}()}.call(this),function(){var r,i=function(t,e){return function(){return t.apply(e,arguments)}},n=function(t,e){function n(){this.constructor=t}for(var r in e)o.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},o={}.hasOwnProperty;(r=this.palava).Peer=function(t){function e(t,e){var n;this.isRemote=i(this.isRemote,this),this.isLocal=i(this.isLocal,this),this.isReady=i(this.isReady,this),this.isMuted=i(this.isMuted,this),this.getError=i(this.getError,this),this.hasError=i(this.hasError,this),this.hasVideo=i(this.hasVideo,this),this.transmitsVideo=i(this.transmitsVideo,this),this.hasAudio=i(this.hasAudio,this),this.transmitsAudio=i(this.transmitsAudio,this),this.id=t,this.status=e||{},(n=this.status).user_agent||(n.user_agent=r.browser.getUserAgent()),this.joinTime=(new Date).getTime(),this.ready=!1,this.error=null}return n(e,t),e.prototype.transmitsAudio=function(){var t,e,n;return!!(null!=(t=this.getStream())&&null!=(e=t.getAudioTracks())&&null!=(n=e[0])?n.enabled:void 0)},e.prototype.hasAudio=function(){var t,e;return!!(null!=(t=this.getStream())&&null!=(e=t.getAudioTracks())?e[0]:void 0)},e.prototype.transmitsVideo=function(){var t,e,n;return!!(null!=(t=this.getStream())&&null!=(e=t.getVideoTracks())&&null!=(n=e[0])?n.enabled:void 0)},e.prototype.hasVideo=function(){var t,e;return!!(null!=(t=this.getStream())&&null!=(e=t.getVideoTracks())?e[0]:void 0)},e.prototype.hasError=function(){return!!this.error},e.prototype.getError=function(){return this.error},e.prototype.isMuted=function(){return!!this.muted},e.prototype.isReady=function(){return!!this.ready},e.prototype.isLocal=function(){return!!this.local},e.prototype.isRemote=function(){return!this.local},e}(this.EventEmitter)}.call(this),function(){var i,o=function(t,e){return function(){return t.apply(e,arguments)}},e=function(t,e){function n(){this.constructor=t}for(var r in e)s.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},s={}.hasOwnProperty;(i=this.palava).LocalPeer=function(t){function r(t,e,n){this.leave=o(this.leave,this),this.enableVideo=o(this.enableVideo,this),this.disableVideo=o(this.disableVideo,this),this.enableAudio=o(this.enableAudio,this),this.disableAudio=o(this.disableAudio,this),this.updateStatus=o(this.updateStatus,this),this.getStream=o(this.getStream,this),this.setupRoom=o(this.setupRoom,this),this.setupUserMedia=o(this.setupUserMedia,this),this.muted=!0,this.local=!0,r.__super__.constructor.call(this,t,e),this.room=n,this.userMedia=n.userMedia,this.setupRoom(),this.setupUserMedia()}return e(r,t),r.prototype.setupUserMedia=function(){var t,e,n;if(this.userMedia.on("stream_released",(t=this,function(){return t.ready=!1,t.emit("stream_removed")})),this.userMedia.on("stream_ready",(e=this,function(t){return e.ready=!0,e.emit("stream_ready",t)})),this.userMedia.on("stream_error",(n=this,function(t){return n.emit("stream_error",t)})),this.getStream())return this.ready=!0,this.emit("stream_ready")},r.prototype.setupRoom=function(){var t,e,n;return this.room.peers[this.id]=this.room.localPeer=this,this.on("update",(t=this,function(){return t.room.emit("peer_update",t)})),this.on("stream_ready",(e=this,function(){return e.room.emit("peer_stream_ready",e)})),this.on("stream_removed",(n=this,function(){return n.room.emit("peer_stream_removed",n)}))},r.prototype.getStream=function(){return this.userMedia.getStream()},r.prototype.updateStatus=function(t){var e,n;if(!(t&&t instanceof Object&&0!==Object.keys(t).length))return t;for(n in t)this.status[n]=t[n];return(e=this.status).user_agent||(e.user_agent=i.browser.getUserAgent()),this.room.channel.send({event:"update_status",status:this.status}),this.status},r.prototype.disableAudio=function(){var t,e,n,r,i;if(this.ready){for(r=[],t=0,e=(n=this.getStream().getAudioTracks()).length;tthis.MAX_BUFFER)return void setTimeout(this.actualSend.bind(this),1);e=(r=this.sendBuffer[0])[0],t=r[1];try{this.channel.send(e)}catch(i){return n=i,void setTimeout(this.actualSend.bind(this),1)}try{"function"==typeof t&&t()}catch(i){n=i,console.log("Exception in write callback:",n)}this.sendBuffer.shift()}else console.log("Not sending when not open!")},e}(this.EventEmitter)}.call(this),function(){var c,s=function(t,e){return function(){return t.apply(e,arguments)}},e=function(t,e){function n(){this.constructor=t}for(var r in e)i.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},i={}.hasOwnProperty;(c=this.palava).RemotePeer=function(t){function o(t,e,n,r,i){this.closePeerConnection=s(this.closePeerConnection,this),this.oaError=s(this.oaError,this),this.sdpSender=s(this.sdpSender,this),this.sendMessage=s(this.sendMessage,this),this.sendAnswer=s(this.sendAnswer,this),this.sendOffer=s(this.sendOffer,this),this.setupRoom=s(this.setupRoom,this),this.setupDistributor=s(this.setupDistributor,this),this.setupPeerConnection=s(this.setupPeerConnection,this),this.generateIceOptions=s(this.generateIceOptions,this),this.toggleMute=s(this.toggleMute,this),this.getStream=s(this.getStream,this),this.muted=!1,this.local=!1,o.__super__.constructor.call(this,t,e),this.room=n,this.remoteStream=null,this.turnCredentials=i,this.dataChannels={},this.setupRoom(),this.setupPeerConnection(r),this.setupDistributor(),r&&this.sendOffer()}return e(o,t),o.prototype.getStream=function(){return this.remoteStream},o.prototype.toggleMute=function(){return this.muted=!this.muted},o.prototype.generateIceOptions=function(){var t;return t=[],this.room.options.stun&&t.push({urls:[this.room.options.stun]}),this.room.options.turnUrls&&this.turnCredentials&&t.push({urls:this.room.options.turnUrls,username:this.turnCredentials.user,credential:this.turnCredentials.password}),{iceServers:t}},o.prototype.setupPeerConnection=function(t){var e,n,r,i,o,s,a,u,h;if(this.peerConnection=new RTCPeerConnection(this.generateIceOptions(),c.browser.getPeerConnectionOptions()),this.peerConnection.onicecandidate=(o=this,function(t){if(t.candidate)return o.distributor.send({event:"ice_candidate",sdpmlineindex:t.candidate.sdpMLineIndex,sdpmid:t.candidate.sdpMid,candidate:t.candidate.candidate})}),this.peerConnection.ontrack=(s=this,function(t){return s.remoteStream=t.streams[0],s.ready=!0,s.emit("stream_ready")}),this.peerConnection.onremovestream=(a=this,function(){return a.remoteStream=null,a.ready=!1,a.emit("stream_removed")}),this.peerConnection.oniceconnectionstatechange=(u=this,function(t){switch(t.target.iceConnectionState){case"connecting":return u.error=null,u.emit("connection_pending");case"connected":return u.error=null,u.emit("connection_established");case"failed":return u.error="connection_failed",u.emit("connection_failed");case"disconnected":return u.error="connection_disconnected",u.emit("connection_disconnected");case"closed":return u.error="connection_closed",u.emit("connection_closed")}}),this.room.localPeer.getStream()&&this.peerConnection.addStream(this.room.localPeer.getStream()),null!=this.room.options.dataChannels)if(h=this,i=function(t){var e,n;return e=t.label,n=new c.DataChannel(t),h.dataChannels[e]=n,h.emit("channel_ready",e,n)},t)for(e in r=this.room.options.dataChannels)n=r[e],this.peerConnection.createDataChannel(e,n).onopen=function(){return i(this)};else this.peerConnection.ondatachannel=function(t){return i(t.channel)};return this.peerConnection},o.prototype.setupDistributor=function(){var t,n,e,r,i,o;return this.distributor=new c.Distributor(this.room.channel,this.id),this.distributor.on("peer_left",(t=this,function(){return t.ready&&(t.remoteStream=null,t.emit("stream_removed"),t.ready=!1),t.peerConnection.close(),t.emit("left")})),this.distributor.on("ice_candidate",(n=this,function(t){var e;if(""!==t.candidate)return e=new RTCIceCandidate({candidate:t.candidate,sdpMLineIndex:t.sdpmlineindex,sdpMid:t.sdpmid}),n.room.options.filterIceCandidateTypes.includes(e.type)?void 0:n.peerConnection.addIceCandidate(e)})),this.distributor.on("offer",(e=this,function(t){return e.peerConnection.setRemoteDescription(new RTCSessionDescription(t.sdp)),e.emit("offer"),e.sendAnswer()})),this.distributor.on("answer",(r=this,function(t){return r.peerConnection.setRemoteDescription(new RTCSessionDescription(t.sdp)),r.emit("answer")})),this.distributor.on("peer_updated_status",(i=this,function(t){return i.status=t.status,i.emit("update")})),this.distributor.on("message",(o=this,function(t){return o.emit("message",t.data)})),this.distributor},o.prototype.setupRoom=function(){var t,e,n,r,i,o,s,a,u,h,c,p,d;return(this.room.peers[this.id]=this).on("left",(t=this,function(){return delete t.room.peers[t.id],t.room.emit("peer_left",t)})),this.on("offer",(e=this,function(){return e.room.emit("peer_offer",e)})),this.on("answer",(n=this,function(){return n.room.emit("peer_answer",n)})),this.on("update",(r=this,function(){return r.room.emit("peer_update",r)})),this.on("stream_ready",(i=this,function(){return i.room.emit("peer_stream_ready",i)})),this.on("stream_removed",(o=this,function(){return o.room.emit("peer_stream_removed",o)})),this.on("connection_pending",(s=this,function(){return s.room.emit("peer_connection_pending",s)})),this.on("connection_established",(a=this,function(){return a.room.emit("peer_connection_established",a)})),this.on("connection_failed",(u=this,function(){return u.room.emit("peer_connection_failed",u)})),this.on("connection_disconnected",(h=this,function(){return h.room.emit("peer_connection_disconnected",h)})),this.on("connection_closed",(c=this,function(){return c.room.emit("peer_connection_closed",c)})),this.on("oaerror",(p=this,function(t){return p.room.emit("peer_oaerror",p,t)})),this.on("channel_ready",(d=this,function(t,e){return d.room.emit("peer_channel_ready",d,t,e)}))},o.prototype.sendOffer=function(){return this.peerConnection.createOffer(this.sdpSender("offer"),this.oaError,c.browser.getConstraints())},o.prototype.sendAnswer=function(){return this.peerConnection.createAnswer(this.sdpSender("answer"),this.oaError,c.browser.getConstraints())},o.prototype.sendMessage=function(t){return this.distributor.send({event:"message",data:t})},o.prototype.sdpSender=function(e){return n=this,function(t){return n.peerConnection.setLocalDescription(t),n.distributor.send({event:e,sdp:t})};var n},o.prototype.oaError=function(t){return this.emit("oaerror",t)},o.prototype.closePeerConnection=function(){var t;return null!=(t=this.peerConnection)&&t.close(),this.peerConnection=null},o}(c.Peer)}.call(this),function(){var u,i=function(t,e){return function(){return t.apply(e,arguments)}},n=function(t,e){function n(){this.constructor=t}for(var r in e)o.call(e,r)&&(t[r]=e[r]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},o={}.hasOwnProperty;(u=this.palava).Room=function(t){function e(t,e,n,r){null==r&&(r={}),this.getAllPeers=i(this.getAllPeers,this),this.getRemotePeers=i(this.getRemotePeers,this),this.getLocalPeer=i(this.getLocalPeer,this),this.getPeerById=i(this.getPeerById,this),this.destroy=i(this.destroy,this),this.leave=i(this.leave,this),this.join=i(this.join,this),this.setupDistributor=i(this.setupDistributor,this),this.setupOptions=i(this.setupOptions,this),this.setupUserMedia=i(this.setupUserMedia,this),this.id=t,this.userMedia=n,this.channel=e,this.peers={},this.options=r,this.setupUserMedia(),this.setupDistributor(),this.setupOptions()}return n(e,t),e.prototype.setupUserMedia=function(){var e,n,t;return this.userMedia.on("stream_ready",(e=this,function(t){return e.emit("local_stream_ready",t)})),this.userMedia.on("stream_error",(n=this,function(t){return n.emit("local_stream_error",t)})),this.userMedia.on("stream_released",(t=this,function(){return t.emit("local_stream_removed")}))},e.prototype.setupOptions=function(){var t,e,n;return(t=this.options).joinTimeout||(t.joinTimeout=1e3),(e=this.options).ownStatus||(e.ownStatus={}),(n=this.options).filterIceCandidateTypes||(n.filterIceCandidateTypes=[])},e.prototype.setupDistributor=function(){var a,r,e,n;return this.distributor=new u.Distributor(this.channel),this.distributor.on("joined_room",(a=this,function(t){var e,n,r,i,o,s;for(clearTimeout(a.joinCheckTimeout),s=t.turn_user?{user:t.turn_user,password:t.turn_password}:null,new u.LocalPeer(t.own_id,a.options.ownStatus,a),e=0,n=(o=t.peers).length;ethis.MAX_BUFFER)return void setTimeout(this.actualSend.bind(this),1);e=(n=this.sendBuffer[0])[0],t=n[1];try{this.channel.send(e)}catch(i){return r=i,void setTimeout(this.actualSend.bind(this),1)}try{"function"==typeof t&&t()}catch(i){r=i,console.log("Exception in write callback:",r)}this.sendBuffer.shift()}else console.log("Not sending when not open!")},e}(this.EventEmitter)}.call(this),function(){var _,s=function(t,e){return function(){return t.apply(e,arguments)}},e=function(t,e){function r(){this.constructor=t}for(var n in e)i.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t},i={}.hasOwnProperty;(_=this.palava).RemotePeer=function(t){function o(t,e,r,n,i){this.closePeerConnection=s(this.closePeerConnection,this),this.oaError=s(this.oaError,this),this.sdpSender=s(this.sdpSender,this),this.sendMessage=s(this.sendMessage,this),this.sendAnswer=s(this.sendAnswer,this),this.sendOffer=s(this.sendOffer,this),this.setupRoom=s(this.setupRoom,this),this.setupDistributor=s(this.setupDistributor,this),this.setupPeerConnection=s(this.setupPeerConnection,this),this.generateIceOptions=s(this.generateIceOptions,this),this.toggleMute=s(this.toggleMute,this),this.getStream=s(this.getStream,this),this.muted=!1,this.local=!1,o.__super__.constructor.call(this,t,e),this.room=r,this.remoteStream=null,this.turnCredentials=i,this.dataChannels={},this.setupRoom(),this.setupPeerConnection(n),this.setupDistributor(),this.offers=n}return e(o,t),o.prototype.getStream=function(){return this.remoteStream},o.prototype.toggleMute=function(){return this.muted=!this.muted},o.prototype.generateIceOptions=function(){var t;return t=[],this.room.options.stun&&t.push({urls:[this.room.options.stun]}),this.room.options.turnUrls&&this.turnCredentials&&t.push({urls:this.room.options.turnUrls,username:this.turnCredentials.user,credential:this.turnCredentials.password}),{iceServers:t}},o.prototype.setupPeerConnection=function(t){var e,r,n,i,o,s,a,u,h,c,p,l,d,m,f,g;if(this.peerConnection=new RTCPeerConnection(this.generateIceOptions(),_.browser.getPeerConnectionOptions()),this.peerConnection.onicecandidate=(h=this,function(t){if(t.candidate)return h.distributor.send({event:"ice_candidate",sdpmlineindex:t.candidate.sdpMLineIndex,sdpmid:t.candidate.sdpMid,candidate:t.candidate.candidate})}),this.peerConnection.ontrack=(c=this,function(t){return c.remoteStream=t.streams[0],c.ready=!0,c.emit("stream_ready")}),this.peerConnection.onremovestream=(p=this,function(){return p.remoteStream=null,p.ready=!1,p.emit("stream_removed")}),this.peerConnection.onnegotiationneeded=(l=this,function(){return l.sendOffer()}),this.peerConnection.oniceconnectionstatechange=(d=this,function(t){switch(t.target.iceConnectionState){case"connecting":return d.error=null,d.emit("connection_pending");case"connected":return d.error=null,d.emit("connection_established");case"failed":return d.error="connection_failed",d.emit("connection_failed");case"disconnected":return d.error="connection_disconnected",d.emit("connection_disconnected");case"closed":return d.error="connection_closed",d.emit("connection_closed")}}),this.room.localPeer.getStream())for(e=0,n=(o=this.room.localPeer.getStream().getTracks()).length;e