UNPKG

rtc-multi-connection

Version:

WebRTC JavaScript library for peer-to-peer applications (screen sharing, audio/video conferencing, file sharing, media streaming etc.)

1,307 lines (1,071 loc) 211 kB
'use strict'; // Last time updated: 2017-04-29 7:08:10 AM UTC // _________________________ // RTCMultiConnection v3.4.4 (patched for NPM.) // Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection // -------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- (function (mod) { if (typeof module === 'object' && typeof module.exports === 'object') { // CommonJS. // Won't work on Node.js as it calls `navigator` object. module.exports = mod; } else { // Traditional frontend style window.RTCMultiConnection = mod; } }) (function(roomid, forceOptions) { function SocketConnection(connection, connectCallback) { var parameters = ''; parameters += '?userid=' + connection.userid; parameters += '&sessionid=' + connection.sessionid; parameters += '&msgEvent=' + connection.socketMessageEvent; parameters += '&socketCustomEvent=' + connection.socketCustomEvent; parameters += '&autoCloseEntireSession=' + !!connection.autoCloseEntireSession; parameters += '&maxParticipantsAllowed=' + connection.maxParticipantsAllowed; if (connection.enableScalableBroadcast) { parameters += '&enableScalableBroadcast=true'; parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2); } if (connection.socketCustomParameters) { parameters += connection.socketCustomParameters; } try { io.sockets = {}; } catch (e) {}; if (!connection.socketURL) { connection.socketURL = '/'; } if (connection.socketURL.substr(connection.socketURL.length - 1, 1) != '/') { // connection.socketURL = 'https://domain.com:9001/'; throw '"socketURL" MUST end with a slash.'; } if (connection.enableLogs) { if (connection.socketURL == '/') { console.info('socket.io is connected at: ', location.origin + '/'); } else { console.info('socket.io is connected at: ', connection.socketURL); } } try { connection.socket = io(connection.socketURL + parameters); } catch (e) { connection.socket = io.connect(connection.socketURL + parameters, connection.socketOptions); } // detect signaling medium connection.socket.isIO = true; var mPeer = connection.multiPeersHandler; connection.socket.on('extra-data-updated', function(remoteUserId, extra) { if (!connection.peers[remoteUserId]) return; connection.peers[remoteUserId].extra = extra; connection.onExtraDataUpdated({ userid: remoteUserId, extra: extra }); if (!connection.peersBackup[remoteUserId]) { connection.peersBackup[remoteUserId] = { userid: remoteUserId, extra: {} }; } connection.peersBackup[remoteUserId].extra = extra; }); connection.socket.on(connection.socketMessageEvent, function(message) { if (message.remoteUserId != connection.userid) return; if (connection.peers[message.sender] && connection.peers[message.sender].extra != message.message.extra) { connection.peers[message.sender].extra = message.extra; connection.onExtraDataUpdated({ userid: message.sender, extra: message.extra }); } if (message.message.streamSyncNeeded && connection.peers[message.sender]) { var stream = connection.streamEvents[message.message.streamid]; if (!stream || !stream.stream) { return; } var action = message.message.action; if (action === 'ended' || action === 'inactive' || action === 'stream-removed') { if (connection.peersBackup[stream.userid]) { stream.extra = connection.peersBackup[stream.userid].extra; } connection.onstreamended(stream); return; } var type = message.message.type != 'both' ? message.message.type : null; if (typeof stream.stream[action] == 'function') { stream.stream[action](type); } return; } if (message.message === 'connectWithAllParticipants') { if (connection.broadcasters.indexOf(message.sender) === -1) { connection.broadcasters.push(message.sender); } mPeer.onNegotiationNeeded({ allParticipants: connection.getAllParticipants(message.sender) }, message.sender); return; } if (message.message === 'removeFromBroadcastersList') { if (connection.broadcasters.indexOf(message.sender) !== -1) { delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)]; connection.broadcasters = removeNullEntries(connection.broadcasters); } return; } if (message.message === 'dropPeerConnection') { connection.deletePeer(message.sender); return; } if (message.message.allParticipants) { if (message.message.allParticipants.indexOf(message.sender) === -1) { message.message.allParticipants.push(message.sender); } message.message.allParticipants.forEach(function(participant) { mPeer[!connection.peers[participant] ? 'createNewPeer' : 'renegotiatePeer'](participant, { localPeerSdpConstraints: { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: isData(connection.session) }); }); return; } if (message.message.newParticipant) { if (message.message.newParticipant == connection.userid) return; if (!!connection.peers[message.message.newParticipant]) return; mPeer.createNewPeer(message.message.newParticipant, message.message.userPreferences || { localPeerSdpConstraints: { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: isData(connection.session) }); return; } if (message.message.readyForOffer || message.message.addMeAsBroadcaster) { connection.addNewBroadcaster(message.sender); } if (message.message.newParticipationRequest && message.sender !== connection.userid) { if (connection.peers[message.sender]) { connection.deletePeer(message.sender); } var userPreferences = { extra: message.extra || {}, localPeerSdpConstraints: message.message.remotePeerSdpConstraints || { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: message.message.localPeerSdpConstraints || { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: typeof message.message.isDataOnly !== 'undefined' ? message.message.isDataOnly : isData(connection.session), dontGetRemoteStream: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', dontAttachLocalStream: !!message.message.dontGetRemoteStream, connectionDescription: message, successCallback: function() { // if its oneway----- todo: THIS SEEMS NOT IMPORTANT. if (typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way') { connection.addNewBroadcaster(message.sender, userPreferences); } if (!!connection.session.oneway || connection.direction === 'one-way' || isData(connection.session)) { connection.addNewBroadcaster(message.sender, userPreferences); } } }; connection.onNewParticipant(message.sender, userPreferences); return; } if (message.message.shiftedModerationControl) { connection.onShiftedModerationControl(message.sender, message.message.broadcasters); return; } if (message.message.changedUUID) { if (connection.peers[message.message.oldUUID]) { connection.peers[message.message.newUUID] = connection.peers[message.message.oldUUID]; delete connection.peers[message.message.oldUUID]; } } if (message.message.userLeft) { mPeer.onUserLeft(message.sender); if (!!message.message.autoCloseEntireSession) { connection.leave(); } return; } mPeer.addNegotiatedMessage(message.message, message.sender); }); connection.socket.on('user-left', function(userid) { onUserLeft(userid); connection.onUserStatusChanged({ userid: userid, status: 'offline', extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} }); var eventObject = { userid: userid, extra: {} }; if (connection.peersBackup[eventObject.userid]) { eventObject.extra = connection.peersBackup[eventObject.userid].extra; } connection.onleave(eventObject); }); var alreadyConnected = false; connection.socket.resetProps = function() { alreadyConnected = false; }; connection.socket.on('connect', function() { if (alreadyConnected) { return; } alreadyConnected = true; if (connection.enableLogs) { console.info('socket.io connection is opened.'); } setTimeout(function() { connection.socket.emit('extra-data-updated', connection.extra); if (connectCallback) { connectCallback(connection.socket); } }, 1000); }); connection.socket.on('disconnect', function() { if (connection.enableLogs) { console.warn('socket.io connection is closed'); } }); connection.socket.on('join-with-password', function(remoteUserId) { connection.onJoinWithPassword(remoteUserId); }); connection.socket.on('invalid-password', function(remoteUserId, oldPassword) { connection.onInvalidPassword(remoteUserId, oldPassword); }); connection.socket.on('password-max-tries-over', function(remoteUserId) { connection.onPasswordMaxTriesOver(remoteUserId); }); connection.socket.on('user-disconnected', function(remoteUserId) { if (remoteUserId === connection.userid) { return; } connection.onUserStatusChanged({ userid: remoteUserId, status: 'offline', extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra || {} : {} }); connection.deletePeer(remoteUserId); }); connection.socket.on('user-connected', function(userid) { if (userid === connection.userid) { return; } connection.onUserStatusChanged({ userid: userid, status: 'online', extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} }); }); connection.socket.on('closed-entire-session', function(sessionid, extra) { connection.leave(); connection.onEntireSessionClosed({ sessionid: sessionid, userid: sessionid, extra: extra }); }); connection.socket.on('userid-already-taken', function(useridAlreadyTaken, yourNewUserId) { connection.isInitiator = false; connection.userid = yourNewUserId; connection.onUserIdAlreadyTaken(useridAlreadyTaken, yourNewUserId); }) connection.socket.on('logs', function(log) { if (!connection.enableLogs) return; console.debug('server-logs', log); }); connection.socket.on('number-of-broadcast-viewers-updated', function(data) { connection.onNumberOfBroadcastViewersUpdated(data); }); connection.socket.on('room-full', function(roomid) { connection.onRoomFull(roomid); }); connection.socket.on('become-next-modrator', function(sessionid) { if (sessionid != connection.sessionid) return; setTimeout(function() { connection.open(sessionid); connection.socket.emit('shift-moderator-control-on-disconnect'); }, 1000); }); } function MultiPeers(connection) { var self = this; var skipPeers = ['getAllParticipants', 'getLength', 'selectFirst', 'streams', 'send', 'forEach']; connection.peersBackup = {}; connection.peers = { getLength: function() { var numberOfPeers = 0; for (var peer in this) { if (skipPeers.indexOf(peer) == -1) { numberOfPeers++; } } return numberOfPeers; }, selectFirst: function() { var firstPeer; for (var peer in this) { if (skipPeers.indexOf(peer) == -1) { firstPeer = this[peer]; } } return firstPeer; }, getAllParticipants: function(sender) { var allPeers = []; for (var peer in this) { if (skipPeers.indexOf(peer) == -1 && peer != sender) { allPeers.push(peer); } } return allPeers; }, forEach: function(callbcak) { this.getAllParticipants().forEach(function(participant) { callbcak(connection.peers[participant]); }); }, send: function(data, remoteUserId) { var that = this; if (!isNull(data.size) && !isNull(data.type)) { self.shareFile(data, remoteUserId); return; } if (data.type !== 'text' && !(data instanceof ArrayBuffer) && !(data instanceof DataView)) { TextSender.send({ text: data, channel: this, connection: connection, remoteUserId: remoteUserId }); return; } if (data.type === 'text') { data = JSON.stringify(data); } if (remoteUserId) { var remoteUser = connection.peers[remoteUserId]; if (remoteUser) { if (!remoteUser.channels.length) { connection.peers[remoteUserId].createDataChannel(); connection.renegotiate(remoteUserId); setTimeout(function() { that.send(data, remoteUserId); }, 3000); return; } remoteUser.channels.forEach(function(channel) { channel.send(data); }); return; } } this.getAllParticipants().forEach(function(participant) { if (!that[participant].channels.length) { connection.peers[participant].createDataChannel(); connection.renegotiate(participant); setTimeout(function() { that[participant].channels.forEach(function(channel) { channel.send(data); }); }, 3000); return; } that[participant].channels.forEach(function(channel) { channel.send(data); }); }); } }; this.uuid = connection.userid; this.getLocalConfig = function(remoteSdp, remoteUserId, userPreferences) { if (!userPreferences) { userPreferences = {}; } return { streamsToShare: userPreferences.streamsToShare || {}, rtcMultiConnection: connection, connectionDescription: userPreferences.connectionDescription, userid: remoteUserId, localPeerSdpConstraints: userPreferences.localPeerSdpConstraints, remotePeerSdpConstraints: userPreferences.remotePeerSdpConstraints, dontGetRemoteStream: !!userPreferences.dontGetRemoteStream, dontAttachLocalStream: !!userPreferences.dontAttachLocalStream, renegotiatingPeer: !!userPreferences.renegotiatingPeer, peerRef: userPreferences.peerRef, channels: userPreferences.channels || [], onLocalSdp: function(localSdp) { self.onNegotiationNeeded(localSdp, remoteUserId); }, onLocalCandidate: function(localCandidate) { localCandidate = OnIceCandidateHandler.processCandidates(connection, localCandidate) if (localCandidate) { self.onNegotiationNeeded(localCandidate, remoteUserId); } }, remoteSdp: remoteSdp, onDataChannelMessage: function(message) { if (!connection.fbr && connection.enableFileSharing) initFileBufferReader(); if (typeof message == 'string' || !connection.enableFileSharing) { self.onDataChannelMessage(message, remoteUserId); return; } var that = this; if (message instanceof ArrayBuffer || message instanceof DataView) { connection.fbr.convertToObject(message, function(object) { that.onDataChannelMessage(object); }); return; } if (message.readyForNextChunk) { connection.fbr.getNextChunk(message, function(nextChunk, isLastChunk) { connection.peers[remoteUserId].channels.forEach(function(channel) { channel.send(nextChunk); }); }, remoteUserId); return; } if (message.chunkMissing) { connection.fbr.chunkMissing(message); return; } connection.fbr.addChunk(message, function(promptNextChunk) { connection.peers[remoteUserId].peer.channel.send(promptNextChunk); }); }, onDataChannelError: function(error) { self.onDataChannelError(error, remoteUserId); }, onDataChannelOpened: function(channel) { self.onDataChannelOpened(channel, remoteUserId); }, onDataChannelClosed: function(event) { self.onDataChannelClosed(event, remoteUserId); }, onRemoteStream: function(stream) { if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].streams.push(stream); } if (isPluginRTC && window.PluginRTC) { var mediaElement = document.createElement('video'); var body = connection.videosContainer; body.insertBefore(mediaElement, body.firstChild); setTimeout(function() { window.PluginRTC.attachMediaStream(mediaElement, stream); }, 3000); return; } self.onGettingRemoteMedia(stream, remoteUserId); }, onRemoteStreamRemoved: function(stream) { self.onRemovingRemoteMedia(stream, remoteUserId); }, onPeerStateChanged: function(states) { self.onPeerStateChanged(states); if (states.iceConnectionState === 'new') { self.onNegotiationStarted(remoteUserId, states); } if (states.iceConnectionState === 'connected') { self.onNegotiationCompleted(remoteUserId, states); } if (states.iceConnectionState.search(/closed|failed/gi) !== -1) { self.onUserLeft(remoteUserId); self.disconnectWith(remoteUserId); } } }; }; this.createNewPeer = function(remoteUserId, userPreferences) { if (connection.maxParticipantsAllowed <= connection.getAllParticipants().length) { return; } userPreferences = userPreferences || {}; if (connection.isInitiator && !!connection.session.audio && connection.session.audio === 'two-way' && !userPreferences.streamsToShare) { userPreferences.isOneWay = false; userPreferences.isDataOnly = false; userPreferences.session = connection.session; } if (!userPreferences.isOneWay && !userPreferences.isDataOnly) { userPreferences.isOneWay = true; this.onNegotiationNeeded({ enableMedia: true, userPreferences: userPreferences }, remoteUserId); return; } userPreferences = connection.setUserPreferences(userPreferences, remoteUserId); var localConfig = this.getLocalConfig(null, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.createAnsweringPeer = function(remoteSdp, remoteUserId, userPreferences) { userPreferences = connection.setUserPreferences(userPreferences || {}, remoteUserId); var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.renegotiatePeer = function(remoteUserId, userPreferences, remoteSdp) { if (!connection.peers[remoteUserId]) { if (connection.enableLogs) { console.error('This peer (' + remoteUserId + ') does not exist. Renegotiation skipped.'); } return; } if (!userPreferences) { userPreferences = {}; } userPreferences.renegotiatingPeer = true; userPreferences.peerRef = connection.peers[remoteUserId].peer; userPreferences.channels = connection.peers[remoteUserId].channels; var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.replaceTrack = function(track, remoteUserId, isVideoTrack) { if (!connection.peers[remoteUserId]) { throw 'This peer (' + remoteUserId + ') does not exist.'; } var peer = connection.peers[remoteUserId].peer; if (!!peer.getSenders && typeof peer.getSenders === 'function' && peer.getSenders().length) { peer.getSenders().forEach(function(rtpSender) { if (isVideoTrack && rtpSender.track instanceof VideoStreamTrack) { connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track; rtpSender.replaceTrack(track); } if (!isVideoTrack && rtpSender.track instanceof AudioStreamTrack) { connection.peers[remoteUserId].peer.lastAudioTrack = rtpSender.track; rtpSender.replaceTrack(track); } }); return; } console.warn('RTPSender.replaceTrack is NOT supported.'); this.renegotiatePeer(remoteUserId); }; this.onNegotiationNeeded = function(message, remoteUserId) {}; this.addNegotiatedMessage = function(message, remoteUserId) { if (message.type && message.sdp) { if (message.type == 'answer') { if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].addRemoteSdp(message); } } if (message.type == 'offer') { if (message.renegotiatingPeer) { this.renegotiatePeer(remoteUserId, null, message); } else { this.createAnsweringPeer(message, remoteUserId); } } if (connection.enableLogs) { console.log('Remote peer\'s sdp:', message.sdp); } return; } if (message.candidate) { if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].addRemoteCandidate(message); } if (connection.enableLogs) { console.log('Remote peer\'s candidate pairs:', message.candidate); } return; } if (message.enableMedia) { connection.session = message.userPreferences.session || connection.session; if (connection.session.oneway && connection.attachStreams.length) { connection.attachStreams = []; } if (message.userPreferences.isDataOnly && connection.attachStreams.length) { connection.attachStreams.length = []; } var streamsToShare = {}; connection.attachStreams.forEach(function(stream) { streamsToShare[stream.streamid] = { isAudio: !!stream.isAudio, isVideo: !!stream.isVideo, isScreen: !!stream.isScreen }; }); message.userPreferences.streamsToShare = streamsToShare; self.onNegotiationNeeded({ readyForOffer: true, userPreferences: message.userPreferences }, remoteUserId); } if (message.readyForOffer) { connection.onReadyForOffer(remoteUserId, message.userPreferences); } function cb(stream) { gumCallback(stream, message, remoteUserId); } }; function gumCallback(stream, message, remoteUserId) { var streamsToShare = {}; connection.attachStreams.forEach(function(stream) { streamsToShare[stream.streamid] = { isAudio: !!stream.isAudio, isVideo: !!stream.isVideo, isScreen: !!stream.isScreen }; }); message.userPreferences.streamsToShare = streamsToShare; self.onNegotiationNeeded({ readyForOffer: true, userPreferences: message.userPreferences }, remoteUserId); } this.connectNewParticipantWithAllBroadcasters = function(newParticipantId, userPreferences, broadcastersList) { if (connection.socket.isIO) { return; } broadcastersList = (broadcastersList || '').split('|-,-|'); if (!broadcastersList.length) { return; } var firstBroadcaster; var remainingBroadcasters = []; broadcastersList.forEach(function(list) { list = (list || '').replace(/ /g, ''); if (list.length) { if (!firstBroadcaster) { firstBroadcaster = list; } else { remainingBroadcasters.push(list); } } }); if (!firstBroadcaster) { return; } self.onNegotiationNeeded({ newParticipant: newParticipantId, userPreferences: userPreferences || false }, firstBroadcaster); if (!remainingBroadcasters.length) { return; } setTimeout(function() { self.connectNewParticipantWithAllBroadcasters(newParticipantId, userPreferences, remainingBroadcasters.join('|-,-|')); }, 3 * 1000); }; this.onGettingRemoteMedia = function(stream, remoteUserId) {}; this.onRemovingRemoteMedia = function(stream, remoteUserId) {}; this.onGettingLocalMedia = function(localStream) {}; this.onLocalMediaError = function(error, constraints) { connection.onMediaError(error, constraints); }; function initFileBufferReader() { connection.fbr = new FileBufferReader(); connection.fbr.onProgress = function(chunk) { connection.onFileProgress(chunk); }; connection.fbr.onBegin = function(file) { connection.onFileStart(file); }; connection.fbr.onEnd = function(file) { connection.onFileEnd(file); }; } this.shareFile = function(file, remoteUserId) { if (!connection.enableFileSharing) { throw '"connection.enableFileSharing" is false.'; } initFileBufferReader(); connection.fbr.readAsArrayBuffer(file, function(uuid) { var arrayOfUsers = connection.getAllParticipants(); if (remoteUserId) { arrayOfUsers = [remoteUserId]; } arrayOfUsers.forEach(function(participant) { connection.fbr.getNextChunk(uuid, function(nextChunk) { connection.peers[participant].channels.forEach(function(channel) { channel.send(nextChunk); }); }, participant); }); }, { userid: connection.userid, // extra: connection.extra, chunkSize: isFirefox ? 15 * 1000 : connection.chunkSize || 0 }); }; if (typeof 'TextReceiver' !== 'undefined') { var textReceiver = new TextReceiver(connection); } this.onDataChannelMessage = function(message, remoteUserId) { textReceiver.receive(JSON.parse(message), remoteUserId, connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}); }; this.onDataChannelClosed = function(event, remoteUserId) { event.userid = remoteUserId; event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; connection.onclose(event); }; this.onDataChannelError = function(error, remoteUserId) { error.userid = remoteUserId; event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; connection.onerror(error); }; this.onDataChannelOpened = function(channel, remoteUserId) { // keep last channel only; we are not expecting parallel/channels channels if (connection.peers[remoteUserId].channels.length) { connection.peers[remoteUserId].channels = [channel]; return; } connection.peers[remoteUserId].channels.push(channel); connection.onopen({ userid: remoteUserId, extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, channel: channel }); }; this.onPeerStateChanged = function(state) { connection.onPeerStateChanged(state); }; this.onNegotiationStarted = function(remoteUserId, states) {}; this.onNegotiationCompleted = function(remoteUserId, states) {}; this.getRemoteStreams = function(remoteUserId) { remoteUserId = remoteUserId || connection.peers.getAllParticipants()[0]; return connection.peers[remoteUserId] ? connection.peers[remoteUserId].streams : []; }; this.isPluginRTC = connection.isPluginRTC = isPluginRTC; } // globals.js var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; var isFirefox = typeof window.InstallTrigger !== 'undefined'; var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; var isChrome = !!window.chrome && !isOpera; var isIE = !!document.documentMode; var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); if (typeof cordova !== 'undefined') { isMobileDevice = true; isChrome = true; } if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { isMobileDevice = true; isChrome = true; } var isPluginRTC = !isMobileDevice && (isSafari || isIE); if (isPluginRTC && typeof URL !== 'undefined') { URL.createObjectURL = function() {}; } // detect node-webkit var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']); var chromeVersion = 50; var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); if (isChrome && matchArray && matchArray[2]) { chromeVersion = parseInt(matchArray[2], 10); } var firefoxVersion = 50; matchArray = navigator.userAgent.match(/Firefox\/(.*)/); if (isFirefox && matchArray && matchArray[1]) { firefoxVersion = parseInt(matchArray[1], 10); } function fireEvent(obj, eventName, args) { if (typeof CustomEvent === 'undefined') { return; } var eventDetail = { arguments: args, __exposedProps__: args }; var event = new CustomEvent(eventName, eventDetail); obj.dispatchEvent(event); } function setHarkEvents(connection, streamEvent) { if (!connection || !streamEvent) { throw 'Both arguments are required.'; } if (!connection.onspeaking || !connection.onsilence) { return; } if (typeof hark === 'undefined') { throw 'hark.js not found.'; } hark(streamEvent.stream, { onspeaking: function() { connection.onspeaking(streamEvent); }, onsilence: function() { connection.onsilence(streamEvent); }, onvolumechange: function(volume, threshold) { if (!connection.onvolumechange) { return; } connection.onvolumechange(merge({ volume: volume, threshold: threshold }, streamEvent)); } }); } function setMuteHandlers(connection, streamEvent) { if (!streamEvent.stream || !streamEvent.stream || !streamEvent.stream.addEventListener) return; streamEvent.stream.addEventListener('mute', function(event) { event = connection.streamEvents[streamEvent.streamid]; event.session = { audio: event.muteType === 'audio', video: event.muteType === 'video' }; connection.onmute(event); }, false); streamEvent.stream.addEventListener('unmute', function(event) { event = connection.streamEvents[streamEvent.streamid]; event.session = { audio: event.unmuteType === 'audio', video: event.unmuteType === 'video' }; connection.onunmute(event); }, false); } function getRandomString() { if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) { var a = window.crypto.getRandomValues(new Uint32Array(3)), token = ''; for (var i = 0, l = a.length; i < l; i++) { token += a[i].toString(36); } return token; } else { return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, ''); } } // Get HTMLAudioElement/HTMLVideoElement accordingly function getRMCMediaElement(stream, callback, connection) { var isAudioOnly = false; if (!!stream.getVideoTracks && !stream.getVideoTracks().length) { isAudioOnly = true; } var mediaElement = document.createElement(isAudioOnly ? 'audio' : 'video'); if (isPluginRTC && window.PluginRTC) { connection.videosContainer.insertBefore(mediaElement, connection.videosContainer.firstChild); setTimeout(function() { window.PluginRTC.attachMediaStream(mediaElement, stream); callback(mediaElement); }, 1000); return; } // "mozSrcObject" is always preferred over "src"!! mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.URL.createObjectURL(stream); mediaElement.controls = true; // http://goo.gl/WZ5nFl // Firefox don't yet support onended for any stream (remote/local) if (isFirefox) { var streamEndedEvent = 'ended'; if ('oninactive' in mediaElement) { streamEndedEvent = 'inactive'; } mediaElement.addEventListener(streamEndedEvent, function() { // fireEvent(stream, streamEndedEvent, stream); currentUserMediaRequest.remove(stream.idInstance); if (stream.type === 'local') { streamEndedEvent = 'ended'; if ('oninactive' in stream) { streamEndedEvent = 'inactive'; } StreamsHandler.onSyncNeeded(stream.streamid, streamEndedEvent); connection.attachStreams.forEach(function(aStream, idx) { if (stream.streamid === aStream.streamid) { delete connection.attachStreams[idx]; } }); var newStreamsArray = []; connection.attachStreams.forEach(function(aStream) { if (aStream) { newStreamsArray.push(aStream); } }); connection.attachStreams = newStreamsArray; var streamEvent = connection.streamEvents[stream.streamid]; if (streamEvent) { connection.onstreamended(streamEvent); return; } if (this.parentNode) { this.parentNode.removeChild(this); } } }, false); } mediaElement.play(); callback(mediaElement); } // if IE if (!window.addEventListener) { window.addEventListener = function(el, eventName, eventHandler) { if (!el.attachEvent) { return; } el.attachEvent('on' + eventName, eventHandler); }; } function listenEventHandler(eventName, eventHandler) { window.removeEventListener(eventName, eventHandler); window.addEventListener(eventName, eventHandler, false); } window.attachEventListener = function(video, type, listener, useCapture) { video.addEventListener(type, listener, useCapture); }; function removeNullEntries(array) { var newArray = []; array.forEach(function(item) { if (item) { newArray.push(item); } }); return newArray; } function isData(session) { return !session.audio && !session.video && !session.screen && session.data; } function isNull(obj) { return typeof obj === 'undefined'; } function isString(obj) { return typeof obj === 'string'; } var MediaStream = window.MediaStream; if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { MediaStream = webkitMediaStream; } /*global MediaStream:true */ if (typeof MediaStream !== 'undefined') { if (!('getVideoTracks' in MediaStream.prototype)) { MediaStream.prototype.getVideoTracks = function() { if (!this.getTracks) { return []; } var tracks = []; this.getTracks.forEach(function(track) { if (track.kind.toString().indexOf('video') !== -1) { tracks.push(track); } }); return tracks; }; MediaStream.prototype.getAudioTracks = function() { if (!this.getTracks) { return []; } var tracks = []; this.getTracks.forEach(function(track) { if (track.kind.toString().indexOf('audio') !== -1) { tracks.push(track); } }); return tracks; }; } if (!('stop' in MediaStream.prototype)) { MediaStream.prototype.stop = function() { this.getAudioTracks().forEach(function(track) { if (!!track.stop) { track.stop(); } }); this.getVideoTracks().forEach(function(track) { if (!!track.stop) { track.stop(); } }); }; } } function isAudioPlusTab(connection, audioPlusTab) { if (connection.session.audio && connection.session.audio === 'two-way') { return false; } if (isFirefox && audioPlusTab !== false) { return true; } if (!isChrome || chromeVersion < 50) return false; if (typeof audioPlusTab === true) { return true; } if (typeof audioPlusTab === 'undefined' && connection.session.audio && connection.session.screen && !connection.session.video) { audioPlusTab = true; return true; } return false; } function getAudioScreenConstraints(screen_constraints) { if (isFirefox) { return true; } if (!isChrome) return false; return { mandatory: { chromeMediaSource: screen_constraints.mandatory.chromeMediaSource, chromeMediaSourceId: screen_constraints.mandatory.chromeMediaSourceId } }; } window.iOSDefaultAudioOutputDevice = window.iOSDefaultAudioOutputDevice || 'speaker'; // earpiece or speaker // Last time updated: 2017-04-29 7:05:22 AM UTC // Latest file can be found here: https://cdn.webrtc-experiment.com/DetectRTC.js // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence // Documentation - github.com/muaz-khan/DetectRTC // ____________ // DetectRTC.js // DetectRTC.hasWebcam (has webcam device!) // DetectRTC.hasMicrophone (has microphone device!) // DetectRTC.hasSpeakers (has speakers!) (function() { 'use strict'; var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; var isNodejs = typeof process === 'object' && typeof process.versions === 'object' && process.versions.node; if (isNodejs) { var version = process.versions.node.toString().replace('v', ''); browserFakeUserAgent = 'Nodejs/' + version + ' (NodeOS) AppleWebKit/' + version + ' (KHTML, like Gecko) Nodejs/' + version + ' Nodejs/' + version } (function(that) { if (typeof window !== 'undefined') { return; } if (typeof window === 'undefined' && typeof global !== 'undefined') { global.navigator = { userAgent: browserFakeUserAgent, getUserMedia: function() {} }; /*global window:true */ that.window = global; } else if (typeof window === 'undefined') { // window = this; } if (typeof document === 'undefined') { /*global document:true */ that.document = {}; document.createElement = document.captureStream = document.mozCaptureStream = function() { return {}; }; } if (typeof location === 'undefined') { /*global location:true */ that.location = { protocol: 'file:', href: '', hash: '' };