UNPKG

rtcmulticonnection

Version:

RTCMultiConnection is a WebRTC JavaScript wrapper library runs top over RTCPeerConnection API to support all possible peer-to-peer features.

554 lines (453 loc) 19.1 kB
// RTCPeerConnection.js var defaults = {}; function setSdpConstraints(config) { var sdpConstraints = { OfferToReceiveAudio: !!config.OfferToReceiveAudio, OfferToReceiveVideo: !!config.OfferToReceiveVideo }; return sdpConstraints; } var RTCPeerConnection; if (typeof window.RTCPeerConnection !== 'undefined') { RTCPeerConnection = window.RTCPeerConnection; } else if (typeof mozRTCPeerConnection !== 'undefined') { RTCPeerConnection = mozRTCPeerConnection; } else if (typeof webkitRTCPeerConnection !== 'undefined') { RTCPeerConnection = webkitRTCPeerConnection; } var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; var MediaStreamTrack = window.MediaStreamTrack; function PeerInitiator(config) { if (typeof window.RTCPeerConnection !== 'undefined') { RTCPeerConnection = window.RTCPeerConnection; } else if (typeof mozRTCPeerConnection !== 'undefined') { RTCPeerConnection = mozRTCPeerConnection; } else if (typeof webkitRTCPeerConnection !== 'undefined') { RTCPeerConnection = webkitRTCPeerConnection; } RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; MediaStreamTrack = window.MediaStreamTrack; if (!RTCPeerConnection) { throw 'WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.'; } var connection = config.rtcMultiConnection; this.extra = config.remoteSdp ? config.remoteSdp.extra : connection.extra; this.userid = config.userid; this.streams = []; this.channels = config.channels || []; this.connectionDescription = config.connectionDescription; this.addStream = function(session) { connection.addStream(session, self.userid); }; this.removeStream = function(streamid) { connection.removeStream(streamid, self.userid); }; var self = this; if (config.remoteSdp) { this.connectionDescription = config.remoteSdp.connectionDescription; } var allRemoteStreams = {}; defaults.sdpConstraints = setSdpConstraints({ OfferToReceiveAudio: true, OfferToReceiveVideo: true }); var peer; var renegotiatingPeer = !!config.renegotiatingPeer; if (config.remoteSdp) { renegotiatingPeer = !!config.remoteSdp.renegotiatingPeer; } var localStreams = []; connection.attachStreams.forEach(function(stream) { if (!!stream) { localStreams.push(stream); } }); if (!renegotiatingPeer) { var iceTransports = 'all'; if (connection.candidates.turn || connection.candidates.relay) { if (!connection.candidates.stun && !connection.candidates.reflexive && !connection.candidates.host) { iceTransports = 'relay'; } } try { // ref: developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration var params = { iceServers: connection.iceServers, iceTransportPolicy: connection.iceTransportPolicy || iceTransports }; if (typeof connection.iceCandidatePoolSize !== 'undefined') { params.iceCandidatePoolSize = connection.iceCandidatePoolSize; } if (typeof connection.bundlePolicy !== 'undefined') { params.bundlePolicy = connection.bundlePolicy; } if (typeof connection.rtcpMuxPolicy !== 'undefined') { params.rtcpMuxPolicy = connection.rtcpMuxPolicy; } if (!!connection.sdpSemantics) { params.sdpSemantics = connection.sdpSemantics || 'unified-plan'; } if (!connection.iceServers || !connection.iceServers.length) { params = null; connection.optionalArgument = null; } peer = new RTCPeerConnection(params, connection.optionalArgument); } catch (e) { try { var params = { iceServers: connection.iceServers }; peer = new RTCPeerConnection(params); } catch (e) { peer = new RTCPeerConnection(); } } } else { peer = config.peerRef; } if (!peer.getRemoteStreams && peer.getReceivers) { peer.getRemoteStreams = function() { var stream = new MediaStream(); peer.getReceivers().forEach(function(receiver) { stream.addTrack(receiver.track); }); return [stream]; }; } if (!peer.getLocalStreams && peer.getSenders) { peer.getLocalStreams = function() { var stream = new MediaStream(); peer.getSenders().forEach(function(sender) { stream.addTrack(sender.track); }); return [stream]; }; } peer.onicecandidate = function(event) { if (!event.candidate) { if (!connection.trickleIce) { var localSdp = peer.localDescription; config.onLocalSdp({ type: localSdp.type, sdp: localSdp.sdp, remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, renegotiatingPeer: !!config.renegotiatingPeer || false, connectionDescription: self.connectionDescription, dontGetRemoteStream: !!config.dontGetRemoteStream, extra: connection ? connection.extra : {}, streamsToShare: streamsToShare }); } return; } if (!connection.trickleIce) return; config.onLocalCandidate({ candidate: event.candidate.candidate, sdpMid: event.candidate.sdpMid, sdpMLineIndex: event.candidate.sdpMLineIndex }); }; localStreams.forEach(function(localStream) { if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream) { return; } if (config.dontAttachLocalStream) { return; } localStream = connection.beforeAddingStream(localStream, self); if (!localStream) return; peer.getLocalStreams().forEach(function(stream) { if (localStream && stream.id == localStream.id) { localStream = null; } }); if (localStream && localStream.getTracks) { localStream.getTracks().forEach(function(track) { try { // last parameter is redundant for unified-plan // starting from chrome version 72 peer.addTrack(track, localStream); } catch (e) {} }); } }); peer.oniceconnectionstatechange = peer.onsignalingstatechange = function() { var extra = self.extra; if (connection.peers[self.userid]) { extra = connection.peers[self.userid].extra || extra; } if (!peer) { return; } config.onPeerStateChanged({ iceConnectionState: peer.iceConnectionState, iceGatheringState: peer.iceGatheringState, signalingState: peer.signalingState, extra: extra, userid: self.userid }); if (peer && peer.iceConnectionState && peer.iceConnectionState.search(/closed|failed/gi) !== -1 && self.streams instanceof Array) { self.streams.forEach(function(stream) { var streamEvent = connection.streamEvents[stream.id] || { streamid: stream.id, stream: stream, type: 'remote' }; connection.onstreamended(streamEvent); }); } }; var sdpConstraints = { OfferToReceiveAudio: !!localStreams.length, OfferToReceiveVideo: !!localStreams.length }; if (config.localPeerSdpConstraints) sdpConstraints = config.localPeerSdpConstraints; defaults.sdpConstraints = setSdpConstraints(sdpConstraints); var streamObject; var dontDuplicate = {}; peer.ontrack = function(event) { if (!event || event.type !== 'track') return; event.stream = event.streams[event.streams.length - 1]; if (!event.stream.id) { event.stream.id = event.track.id; } if (dontDuplicate[event.stream.id] && DetectRTC.browser.name !== 'Safari') { if (event.track) { event.track.onended = function() { // event.track.onmute = peer && peer.onremovestream(event); }; } return; } dontDuplicate[event.stream.id] = event.stream.id; var streamsToShare = {}; if (config.remoteSdp && config.remoteSdp.streamsToShare) { streamsToShare = config.remoteSdp.streamsToShare; } else if (config.streamsToShare) { streamsToShare = config.streamsToShare; } var streamToShare = streamsToShare[event.stream.id]; if (streamToShare) { event.stream.isAudio = streamToShare.isAudio; event.stream.isVideo = streamToShare.isVideo; event.stream.isScreen = streamToShare.isScreen; } else { event.stream.isVideo = !!getTracks(event.stream, 'video').length; event.stream.isAudio = !event.stream.isVideo; event.stream.isScreen = false; } event.stream.streamid = event.stream.id; allRemoteStreams[event.stream.id] = event.stream; config.onRemoteStream(event.stream); event.stream.getTracks().forEach(function(track) { track.onended = function() { // track.onmute = peer && peer.onremovestream(event); }; }); event.stream.onremovetrack = function() { peer && peer.onremovestream(event); }; }; peer.onremovestream = function(event) { // this event doesn't works anymore event.stream.streamid = event.stream.id; if (allRemoteStreams[event.stream.id]) { delete allRemoteStreams[event.stream.id]; } config.onRemoteStreamRemoved(event.stream); }; if (typeof peer.removeStream !== 'function') { // removeStream backward compatibility peer.removeStream = function(stream) { stream.getTracks().forEach(function(track) { peer.removeTrack(track, stream); }); }; } this.addRemoteCandidate = function(remoteCandidate) { peer.addIceCandidate(new RTCIceCandidate(remoteCandidate)); }; function oldAddRemoteSdp(remoteSdp, cb) { cb = cb || function() {}; if (DetectRTC.browser.name !== 'Safari') { remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); } peer.setRemoteDescription(new RTCSessionDescription(remoteSdp), cb, function(error) { if (!!connection.enableLogs) { console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp); } cb(); }); } this.addRemoteSdp = function(remoteSdp, cb) { cb = cb || function() {}; if (DetectRTC.browser.name !== 'Safari') { remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); } peer.setRemoteDescription(new RTCSessionDescription(remoteSdp)).then(cb, function(error) { if (!!connection.enableLogs) { console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp); } cb(); }).catch(function(error) { if (!!connection.enableLogs) { console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp); } cb(); }); }; var isOfferer = true; if (config.remoteSdp) { isOfferer = false; } this.createDataChannel = function() { var channel = peer.createDataChannel('sctp', {}); setChannelEvents(channel); }; if (connection.session.data === true && !renegotiatingPeer) { if (!isOfferer) { peer.ondatachannel = function(event) { var channel = event.channel; setChannelEvents(channel); }; } else { this.createDataChannel(); } } this.enableDisableVideoEncoding = function(enable) { var rtcp; peer.getSenders().forEach(function(sender) { if (!rtcp && sender.track.kind === 'video') { rtcp = sender; } }); if (!rtcp || !rtcp.getParameters) return; var parameters = rtcp.getParameters(); parameters.encodings[1] && (parameters.encodings[1].active = !!enable); parameters.encodings[2] && (parameters.encodings[2].active = !!enable); rtcp.setParameters(parameters); }; if (config.remoteSdp) { if (config.remoteSdp.remotePeerSdpConstraints) { sdpConstraints = config.remoteSdp.remotePeerSdpConstraints; } defaults.sdpConstraints = setSdpConstraints(sdpConstraints); this.addRemoteSdp(config.remoteSdp, function() { createOfferOrAnswer('createAnswer'); }); } function setChannelEvents(channel) { // force ArrayBuffer in Firefox; which uses "Blob" by default. channel.binaryType = 'arraybuffer'; channel.onmessage = function(event) { config.onDataChannelMessage(event.data); }; channel.onopen = function() { config.onDataChannelOpened(channel); }; channel.onerror = function(error) { config.onDataChannelError(error); }; channel.onclose = function(event) { config.onDataChannelClosed(event); }; channel.internalSend = channel.send; channel.send = function(data) { if (channel.readyState !== 'open') { return; } channel.internalSend(data); }; peer.channel = channel; } if (connection.session.audio == 'two-way' || connection.session.video == 'two-way' || connection.session.screen == 'two-way') { defaults.sdpConstraints = setSdpConstraints({ OfferToReceiveAudio: connection.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), OfferToReceiveVideo: connection.session.video == 'two-way' || connection.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) }); } var streamsToShare = {}; peer.getLocalStreams().forEach(function(stream) { streamsToShare[stream.streamid] = { isAudio: !!stream.isAudio, isVideo: !!stream.isVideo, isScreen: !!stream.isScreen }; }); function oldCreateOfferOrAnswer(_method) { peer[_method](function(localSdp) { if (DetectRTC.browser.name !== 'Safari') { localSdp.sdp = connection.processSdp(localSdp.sdp); } peer.setLocalDescription(localSdp, function() { if (!connection.trickleIce) return; config.onLocalSdp({ type: localSdp.type, sdp: localSdp.sdp, remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, renegotiatingPeer: !!config.renegotiatingPeer || false, connectionDescription: self.connectionDescription, dontGetRemoteStream: !!config.dontGetRemoteStream, extra: connection ? connection.extra : {}, streamsToShare: streamsToShare }); connection.onSettingLocalDescription(self); }, function(error) { if (!!connection.enableLogs) { console.error('setLocalDescription-error', error); } }); }, function(error) { if (!!connection.enableLogs) { console.error('sdp-' + _method + '-error', error); } }, defaults.sdpConstraints); } function createOfferOrAnswer(_method) { peer[_method](defaults.sdpConstraints).then(function(localSdp) { if (DetectRTC.browser.name !== 'Safari') { localSdp.sdp = connection.processSdp(localSdp.sdp); } peer.setLocalDescription(localSdp).then(function() { if (!connection.trickleIce) return; config.onLocalSdp({ type: localSdp.type, sdp: localSdp.sdp, remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, renegotiatingPeer: !!config.renegotiatingPeer || false, connectionDescription: self.connectionDescription, dontGetRemoteStream: !!config.dontGetRemoteStream, extra: connection ? connection.extra : {}, streamsToShare: streamsToShare }); connection.onSettingLocalDescription(self); }, function(error) { if (!connection.enableLogs) return; console.error('setLocalDescription error', error); }); }, function(error) { if (!!connection.enableLogs) { console.error('sdp-error', error); } }); } if (isOfferer) { createOfferOrAnswer('createOffer'); } peer.nativeClose = peer.close; peer.close = function() { if (!peer) { return; } try { if (peer.nativeClose !== peer.close) { peer.nativeClose(); } } catch (e) {} peer = null; self.peer = null; }; this.peer = peer; }