UNPKG

rtcmulticonnection-v3

Version:

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

1,332 lines (1,086 loc) 214 kB
// Last time updated: 2016-03-14 12:07:07 PM UTC // ______________________________ // RTCMultiConnection-v3.0 (Beta) 'use strict'; (function() { // RTCMultiConnection.js function RTCMultiConnection(roomid, forceOptions) { forceOptions = forceOptions || {}; var connection = this; connection.channel = connection.sessionid = (roomid || location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')) + ''; var mPeer = new MultiPeers(connection); mPeer.onGettingLocalMedia = function(stream) { stream.type = 'local'; setStreamEndHandler(stream); getRMCMediaElement(stream, function(mediaElement) { mediaElement.id = stream.streamid; mediaElement.muted = true; mediaElement.volume = 0; if (connection.attachStreams.indexOf(stream) === -1) { connection.attachStreams.push(stream); } if (typeof StreamsHandler !== 'undefined') { StreamsHandler.setHandlers(stream, true, connection); } connection.streamEvents[stream.streamid] = { stream: stream, type: 'local', mediaElement: mediaElement, userid: connection.userid, extra: connection.extra, streamid: stream.streamid, blobURL: mediaElement.src || URL.createObjectURL(stream), isAudioMuted: true }; setHarkEvents(connection, connection.streamEvents[stream.streamid]); setMuteHandlers(connection, connection.streamEvents[stream.streamid]); connection.onstream(connection.streamEvents[stream.streamid]); }, connection); connection.observers.all(); }; mPeer.onGettingRemoteMedia = function(stream, remoteUserId) { stream.type = 'remote'; setStreamEndHandler(stream, 'remote-stream'); getRMCMediaElement(stream, function(mediaElement) { mediaElement.id = stream.streamid; if (typeof StreamsHandler !== 'undefined') { StreamsHandler.setHandlers(stream, false, connection); } connection.streamEvents[stream.streamid] = { stream: stream, type: 'remote', userid: remoteUserId, extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, mediaElement: mediaElement, streamid: stream.streamid, blobURL: mediaElement.src || URL.createObjectURL(stream) }; setMuteHandlers(connection, connection.streamEvents[stream.streamid]); connection.onstream(connection.streamEvents[stream.streamid]); }, connection); }; mPeer.onRemovingRemoteMedia = function(stream, remoteUserId) { var streamEvent = connection.streamEvents[stream.streamid]; if (!streamEvent) { streamEvent = { stream: stream, type: 'remote', userid: remoteUserId, extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, streamid: stream.streamid, mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null }; } connection.onstreamended(streamEvent); delete connection.streamEvents[stream.streamid]; }; mPeer.onNegotiationNeeded = function(message, remoteUserId, callback) { connectSocket(function() { socket.emit(connection.socketMessageEvent, 'password' in message ? message : { remoteUserId: message.remoteUserId || remoteUserId, message: message, sender: connection.userid }, callback || function() {}); }); }; function onUserLeft(remoteUserId) { connection.deletePeer(remoteUserId); } mPeer.onUserLeft = onUserLeft; mPeer.disconnectWith = function(remoteUserId, callback) { if (socket) { socket.emit('disconnect-with', remoteUserId, callback || function() {}); } connection.deletePeer(remoteUserId); }; connection.broadcasters = []; connection.socketOptions = { // 'force new connection': true, // For SocketIO version < 1.0 // 'forceNew': true, // For SocketIO version >= 1.0 'transport': 'polling' // fixing transport:unknown issues }; var socket; function connectSocket(connectCallback) { if (socket) { // todo: check here readySate/etc. to make sure socket is still opened if (connectCallback) { connectCallback(socket); } return; } if (typeof SocketConnection === 'undefined') { if (typeof FirebaseConnection !== 'undefined') { window.SocketConnection = FirebaseConnection; } else if (typeof PubNubConnection !== 'undefined') { window.SocketConnection = PubNubConnection; } else { throw 'SocketConnection.js seems missed.'; } } socket = new SocketConnection(connection, function(s) { socket = s; if (connectCallback) { connectCallback(socket); } }); } connection.openOrJoin = function(localUserid, password) { connection.checkPresence(localUserid, function(isRoomExists, roomid) { if (typeof password === 'function') { password(isRoomExists, roomid); password = null; } if (isRoomExists) { connection.sessionid = roomid; var localPeerSdpConstraints = false; var remotePeerSdpConstraints = false; var isOneWay = !!connection.session.oneway; var isDataOnly = isData(connection.session); remotePeerSdpConstraints = { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo } localPeerSdpConstraints = { OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo } var connectionDescription = { remoteUserId: connection.sessionid, message: { newParticipationRequest: true, isOneWay: isOneWay, isDataOnly: isDataOnly, localPeerSdpConstraints: localPeerSdpConstraints, remotePeerSdpConstraints: remotePeerSdpConstraints }, sender: connection.userid, password: password || false }; mPeer.onNegotiationNeeded(connectionDescription); return; } var oldUserId = connection.userid; connection.userid = connection.sessionid = localUserid || connection.sessionid; connection.userid += ''; socket.emit('changed-uuid', connection.userid); if (password) { socket.emit('set-password', password); } connection.isInitiator = true; if (isData(connection.session)) { return; } connection.captureUserMedia(); }); }; connection.open = function(localUserid, isPublicModerator) { var oldUserId = connection.userid; connection.userid = connection.sessionid = localUserid || connection.sessionid; connection.userid += ''; connection.isInitiator = true; connectSocket(function() { socket.emit('changed-uuid', connection.userid); if (isPublicModerator == true) { connection.becomePublicModerator(); } }); if (isData(connection.session)) { if (typeof isPublicModerator === 'function') { isPublicModerator(); } return; } connection.captureUserMedia(typeof isPublicModerator === 'function' ? isPublicModerator : null); }; connection.becomePublicModerator = function() { if (!connection.isInitiator) return; socket.emit('become-a-public-moderator'); }; connection.dontMakeMeModerator = function() { socket.emit('dont-make-me-moderator'); }; connection.deletePeer = function(remoteUserId) { if (!remoteUserId) { return; } connection.onleave({ userid: remoteUserId, extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {} }); if (!!connection.peers[remoteUserId]) { connection.peers[remoteUserId].streams.forEach(function(stream) { stream.stop(); }); var peer = connection.peers[remoteUserId].peer; if (peer && peer.iceConnectionState !== 'closed') { try { peer.close(); } catch (e) {} } if (connection.peers[remoteUserId]) { connection.peers[remoteUserId].peer = null; delete connection.peers[remoteUserId]; } } if (connection.broadcasters.indexOf(remoteUserId) !== -1) { var newArray = []; connection.broadcasters.forEach(function(broadcaster) { if (broadcaster !== remoteUserId) { newArray.push(broadcaster); } }); connection.broadcasters = newArray; keepNextBroadcasterOnServer(); } } connection.rejoin = function(connectionDescription) { if (connection.isInitiator || !connectionDescription || !Object.keys(connectionDescription).length) { return; } var extra = {}; if (connection.peers[connectionDescription.remoteUserId]) { extra = connection.peers[connectionDescription.remoteUserId].extra; connection.deletePeer(connectionDescription.remoteUserId); } if (connectionDescription && connectionDescription.remoteUserId) { connection.join(connectionDescription.remoteUserId); connection.onReConnecting({ userid: connectionDescription.remoteUserId, extra: extra }); } }; connection.join = connection.connect = function(remoteUserId, options) { connection.sessionid = (remoteUserId ? remoteUserId.sessionid || remoteUserId.remoteUserId || remoteUserId : false) || connection.sessionid; connection.sessionid += ''; var localPeerSdpConstraints = false; var remotePeerSdpConstraints = false; var isOneWay = false; var isDataOnly = false; if ((remoteUserId && remoteUserId.session) || !remoteUserId || typeof remoteUserId === 'string') { var session = remoteUserId ? remoteUserId.session || connection.session : connection.session; isOneWay = !!session.oneway; isDataOnly = isData(session); remotePeerSdpConstraints = { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }; localPeerSdpConstraints = { OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }; } options = options || {}; if (typeof options.localPeerSdpConstraints !== 'undefined') { localPeerSdpConstraints = options.localPeerSdpConstraints; } if (typeof options.remotePeerSdpConstraints !== 'undefined') { remotePeerSdpConstraints = options.remotePeerSdpConstraints; } if (typeof options.isOneWay !== 'undefined') { isOneWay = options.isOneWay; } if (typeof options.isDataOnly !== 'undefined') { isDataOnly = options.isDataOnly; } var connectionDescription = { remoteUserId: connection.sessionid, message: { newParticipationRequest: true, isOneWay: isOneWay, isDataOnly: isDataOnly, localPeerSdpConstraints: localPeerSdpConstraints, remotePeerSdpConstraints: remotePeerSdpConstraints }, sender: connection.userid, password: false }; connectSocket(function() { if (!!connection.peers[connection.sessionid]) { // on socket disconnect & reconnect return; } mPeer.onNegotiationNeeded(connectionDescription); }); return connectionDescription; }; connection.connectWithAllParticipants = function(remoteUserId) { mPeer.onNegotiationNeeded('connectWithAllParticipants', remoteUserId || connection.sessionid); }; connection.removeFromBroadcastersList = function(remoteUserId) { mPeer.onNegotiationNeeded('removeFromBroadcastersList', remoteUserId || connection.sessionid); connection.peers.getAllParticipants(remoteUserId || connection.sessionid).forEach(function(participant) { mPeer.onNegotiationNeeded('dropPeerConnection', participant); connection.deletePeer(participant); }); connection.attachStreams.forEach(function(stream) { stream.stop(); }); }; connection.getUserMedia = connection.captureUserMedia = function(callback, session) { connection.observers.all(); session = session || connection.session; if (connection.dontCaptureUserMedia || isData(session)) { if (callback) { callback(); } return; } if (session.audio || session.video || session.screen) { if (session.screen) { connection.getScreenConstraints(function(error, screen_constraints) { if (error) { throw error; } invokeGetUserMedia({ video: screen_constraints, isScreen: true }, session.audio || session.video ? invokeGetUserMedia : false); }); } else if (session.audio || session.video) { invokeGetUserMedia(); } } function invokeGetUserMedia(localMediaConstraints, getUserMedia_callback) { var isScreen = false; if (localMediaConstraints) { isScreen = localMediaConstraints.isScreen; delete localMediaConstraints.isScreen; } getUserMediaHandler({ onGettingLocalMedia: function(stream) { stream.isAudio = stream.isVideo = stream.isScreen = false; if (isScreen) { stream.isScreen = true; } else if (session.audio && session.video) { stream.isVideo = true; } else if (session.audio) { stream.isAudio = true; } mPeer.onGettingLocalMedia(stream); if (getUserMedia_callback) { return getUserMedia_callback(); } if (callback) { callback(stream); } }, onLocalMediaError: function(error, constraints) { mPeer.onLocalMediaError(error, constraints); if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { constraints.video = false; invokeGetUserMedia(constraints, getUserMedia_callback); return; } if (callback) { callback(); } }, localMediaConstraints: localMediaConstraints || { audio: !!session.audio ? connection.mediaConstraints.audio : false, video: !!session.video ? connection.mediaConstraints.video : false } }); } }; function beforeUnload(shiftModerationControlOnLeave, dontCloseSocket) { if (!connection.closeBeforeUnload) { return; } if (connection.isInitiator === true) { connection.dontMakeMeModerator(); } connection.peers.getAllParticipants().forEach(function(participant) { mPeer.onNegotiationNeeded({ userLeft: true }, participant); if (connection.peers[participant] && connection.peers[participant].peer) { connection.peers[participant].peer.close(); } delete connection.peers[participant]; }); if (!dontCloseSocket) { connection.closeSocket(); } connection.broadcasters = []; connection.isInitiator = false; } connection.closeBeforeUnload = true; window.addEventListener('beforeunload', beforeUnload, false); connection.userid = getRandomString(); connection.changeUserId = function(newUserId, callback) { connection.userid = newUserId || getRandomString(); socket.emit('changed-uuid', connection.userid, callback || function() {}); }; connection.observers = { extra: function() { observeObject(connection.extra, function(changes) { socket.emit('extra-data-updated', connection.extra); }); }, attachStreams: function() { observeObject(connection.attachStreams, function(changes) { changes.forEach(function(change) { if (change.type === 'add') { setStreamEndHandler(change.object[change.name]); } if (change.type === 'remove' || change.type === 'delete') { if (connection.removeStreams.indexOf(change.object[change.name]) === -1) { connection.removeStreams.push(change.object[change.name]); } } connection.attachStreams = removeNullEntries(connection.attachStreams); connection.removeStreams = removeNullEntries(connection.removeStreams); connection.observers.all(); }); }); }, all: function() { this.extra(); this.attachStreams(); if (socket) { socket.emit('extra-data-updated', connection.extra); } } }; connection.extra = {}; connection.attachStreams = []; connection.removeStreams = []; connection.observers.all(); connection.session = { audio: true, video: true }; connection.enableFileSharing = false; // all values in kbps connection.bandwidth = { screen: 512, audio: 128, video: 512 }; connection.codecs = { audio: 'opus', video: 'VP9' }; connection.processSdp = function(sdp) { if (isMobileDevice || isFirefox) { return sdp; } sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); sdp = CodecsHandler.setVideoBitrates(sdp, { min: connection.bandwidth.video * 8 * 1024, max: connection.bandwidth.video * 8 * 1024 }); sdp = CodecsHandler.setOpusAttributes(sdp, { maxaveragebitrate: connection.bandwidth.audio * 8 * 1024, maxplaybackrate: connection.bandwidth.audio * 8 * 1024, stereo: 1, maxptime: 3 }); if (connection.codecs.video === 'VP9') { sdp = CodecsHandler.preferVP9(sdp); } if (connection.codecs.video === 'H264') { sdp = CodecsHandler.removeVPX(sdp); } if (connection.codecs.audio === 'G722') { sdp = CodecsHandler.removeNonG722(sdp); } return sdp; }; if (typeof CodecsHandler !== 'undefined') { connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler; } connection.mediaConstraints = { audio: { mandatory: {}, optional: [{ bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 }] }, video: { mandatory: {}, optional: [{ bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 }, { googLeakyBucket: true }, { facingMode: 'user' }] } }; if (isFirefox) { connection.mediaConstraints = { audio: true, video: true }; } if (!forceOptions.useDefaultDevices && !isMobileDevice) { DetectRTC.load(function() { var lastAudioDevice, lastVideoDevice; // it will force RTCMultiConnection to capture last-devices // i.e. if external microphone is attached to system, we should prefer it over built-in devices. DetectRTC.MediaDevices.forEach(function(device) { if (device.kind === 'audioinput' && connection.mediaConstraints.audio !== false) { lastAudioDevice = device; } if (device.kind === 'videoinput' && connection.mediaConstraints.video !== false) { lastVideoDevice = device; } }); if (lastAudioDevice) { if (isFirefox) { if (connection.mediaConstraints.audio !== true) { connection.mediaConstraints.audio.deviceId = lastAudioDevice.id; } else { connection.mediaConstraints.audio = { deviceId: lastAudioDevice.id } } return; } if (connection.mediaConstraints.audio == true) { connection.mediaConstraints.audio = { mandatory: {}, optional: [] } } if (!connection.mediaConstraints.audio.optional) { connection.mediaConstraints.audio.optional = []; } var optional = [{ sourceId: lastAudioDevice.id }]; connection.mediaConstraints.audio.optional = optional.concat(connection.mediaConstraints.audio.optional); } if (lastVideoDevice) { if (isFirefox) { if (connection.mediaConstraints.video !== true) { connection.mediaConstraints.video.deviceId = lastVideoDevice.id; } else { connection.mediaConstraints.video = { deviceId: lastVideoDevice.id } } return; } if (connection.mediaConstraints.video == true) { connection.mediaConstraints.video = { mandatory: {}, optional: [] } } if (!connection.mediaConstraints.video.optional) { connection.mediaConstraints.video.optional = []; } var optional = [{ sourceId: lastVideoDevice.id }]; connection.mediaConstraints.video.optional = optional.concat(connection.mediaConstraints.video.optional); } }); } connection.sdpConstraints = { mandatory: { OfferToReceiveAudio: true, OfferToReceiveVideo: true }, optional: [{ VoiceActivityDetection: false }] }; connection.optionalArgument = { optional: [{ DtlsSrtpKeyAgreement: true }, { googImprovedWifiBwe: true }, { googScreencastMinBitrate: 300 }, { googIPv6: true }, { googDscp: true }, { googCpuUnderuseThreshold: 55 }, { googCpuOveruseThreshold: 85 }, { googSuspendBelowMinBitrate: true }, { googCpuOveruseDetection: true }], mandatory: {} }; connection.iceServers = IceServersHandler.getIceServers(connection); connection.candidates = { host: true, stun: true, turn: true }; connection.iceProtocols = { tcp: true, udp: true }; // EVENTs connection.onopen = function(event) { if (!!connection.enableLogs) { console.info('Data connection has been opened between you & ', event.userid); } }; connection.onclose = function(event) { if (!!connection.enableLogs) { console.warn('Data connection has been closed between you & ', event.userid); } }; connection.onerror = function(error) { if (!!connection.enableLogs) { console.error(error.userid, 'data-error', error); } }; connection.onmessage = function(event) { if (!!connection.enableLogs) { console.debug('data-message', event.userid, event.data); } }; connection.send = function(data, remoteUserId) { connection.peers.send(data, remoteUserId); }; connection.close = connection.disconnect = connection.leave = function() { beforeUnload(false, true); }; connection.closeEntireSession = function(callback) { callback = callback || function() {}; socket.emit('close-entire-session', function looper() { if (connection.getAllParticipants().length) { setTimeout(looper, 100); return; } connection.onEntireSessionClosed({ sessionid: connection.sessionid, userid: connection.userid, extra: connection.extra }); connection.changeUserId(null, function() { connection.close(); callback(); }); }); }; connection.onEntireSessionClosed = function(event) { if (!connection.enableLogs) return; console.info('Entire session is closed: ', event.sessionid, event.extra); }; connection.onstream = function(e) { var parentNode = connection.videosContainer; parentNode.insertBefore(e.mediaElement, parentNode.firstChild); if (!isMobileDevice) return; setTimeout(function() { e.mediaElement.play(); }, 5000); }; connection.onstreamended = function(e) { if (!e.mediaElement) { e.mediaElement = document.getElementById(e.streamid); } if (!e.mediaElement || !e.mediaElement.parentNode) { return; } e.mediaElement.parentNode.removeChild(e.mediaElement); }; connection.direction = 'many-to-many'; connection.removeStream = function(streamid) { var stream; connection.attachStreams.forEach(function(localStream) { if (localStream.id === streamid) { stream = localStream; } }); if (!stream) { console.warn('No such stream exists.', streamid); return; } if (connection.removeStreams.indexOf(stream) === -1) { connection.removeStreams.push(stream); connection.peers.getAllParticipants().forEach(function(participant) { mPeer.renegotiatePeer(participant); }); } }; connection.addStream = function(session, remoteUserId) { if (!!session.getAudioTracks) { if (connection.attachStreams.indexOf(session) === -1) { if (!session.streamid) { session.streamid = session.id; } connection.attachStreams.push(session); } connection.renegotiate(remoteUserId); return; } if (isData(session)) { connection.renegotiate(remoteUserId); return; } if (!session.audio || session.video || session.screen) { if (session.screen) { connection.getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } invokeGetUserMedia({ video: screen_constraints }, session.audio || session.video ? invokeGetUserMedia : false); }); } else if (session.audio || session.video) { invokeGetUserMedia(); } } function invokeGetUserMedia(localMediaConstraints, callback) { getUserMediaHandler({ onGettingLocalMedia: function(stream) { var videoConstraints = localMediaConstraints ? localMediaConstraints.video : connection.mediaConstraints; if (videoConstraints) { if (videoConstraints.mediaSource || videoConstraints.mozMediaSource) { stream.isScreen = true; } else if (videoConstraints.mandatory && videoConstraints.mandatory.chromeMediaSource) { stream.isScreen = true; } } if (!stream.isScreen) { stream.isVideo = stream.getVideoTracks().length; stream.isAudio = !stream.isVideo && stream.getAudioTracks().length; } mPeer.onGettingLocalMedia(stream); if (session.streamCallback) { session.streamCallback(stream); } if (callback) { return callback(); } connection.renegotiate(remoteUserId); }, onLocalMediaError: function(error, constraints) { mPeer.onLocalMediaError(error, constraints); if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { constraints.video = false; invokeGetUserMedia(constraints, callback); return; } if (callback) { return callback(); } connection.renegotiate(remoteUserId); }, localMediaConstraints: localMediaConstraints || { audio: session.audio ? connection.mediaConstraints.audio : false, video: session.video ? connection.mediaConstraints.video : false } }); } }; function applyConstraints(stream, mediaConstraints) { if (!stream) { if (!!connection.enableLogs) { console.error('No stream to applyConstraints.'); } return; } if (mediaConstraints.audio) { stream.getAudioTracks().forEach(function(track) { track.applyConstraints(mediaConstraints.audio); }); } if (mediaConstraints.video) { stream.getVideoTracks().forEach(function(track) { track.applyConstraints(mediaConstraints.video); }); } } connection.applyConstraints = function(mediaConstraints, streamid) { if (!MediaStreamTrack || !MediaStreamTrack.prototype.applyConstraints) { alert('track.applyConstraints is NOT supported in your browser.'); return; } if (streamid) { var streams; if (connection.streamEvents[streamid]) { stream = connection.streamEvents[streamid].stream; } applyConstraints(stream, mediaConstraints); return; } connection.attachStreams.forEach(function(stream) { applyConstraints(stream, mediaConstraints); }); }; function replaceTrack(track, remoteUserId, isVideoTrack) { if (remoteUserId) { mPeer.replaceTrack(track, remoteUserId, isVideoTrack); return; } connection.peers.getAllParticipants().forEach(function(participant) { mPeer.replaceTrack(track, participant, isVideoTrack); }); } connection.replaceTrack = function(session, remoteUserId, isVideoTrack) { session = session || {}; if (!RTCPeerConnection.prototype.getSenders) { connection.addStream(session); return; } if (session instanceof MediaStreamTrack) { replaceTrack(session, remoteUserId, isVideoTrack); return; } if (session instanceof MediaStream) { if (session.getVideoTracks().length) { replaceTrack(session.getVideoTracks()[0], remoteUserId, true); } if (session.getAudioTracks().length) { replaceTrack(session.getAudioTracks()[0], remoteUserId, false); } return; } if (isData(session)) { throw 'connection.replaceTrack requires audio and/or video and/or screen.'; return; } if (!session.audio || session.video || session.screen) { if (session.screen) { connection.getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } invokeGetUserMedia({ video: screen_constraints }, session.audio || session.video ? invokeGetUserMedia : false); }); } else if (session.audio || session.video) { invokeGetUserMedia(); } } function invokeGetUserMedia(localMediaConstraints, callback) { getUserMediaHandler({ onGettingLocalMedia: function(stream) { mPeer.onGettingLocalMedia(stream); if (callback) { return callback(); } connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen); }, onLocalMediaError: function(error, constraints) { mPeer.onLocalMediaError(error, constraints); if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { constraints.video = false; invokeGetUserMedia(constraints, callback); return; } if (callback) { callback(); } }, localMediaConstraints: localMediaConstraints || { audio: session.audio ? connection.mediaConstraints.audio : false, video: session.video ? connection.mediaConstraints.video : false } }); } }; connection.resetTrack = function(remoteUsersIds, isVideoTrack) { if (!remoteUsersIds) { remoteUsersIds = connection.getAllParticipants(); } if (typeof remoteUsersIds == 'string') { remoteUsersIds = [remoteUsersIds]; } remoteUsersIds.forEach(function(participant) { var peer = connection.peers[participant].peer; if ((typeof isVideoTrack === 'undefined' || isVideoTrack === true) && peer.lastVideoTrack) { connection.replaceTrack(peer.lastVideoTrack, participant, true); } if ((typeof isVideoTrack === 'undefined' || isVideoTrack === false) && peer.lastAudioTrack) { connection.replaceTrack(peer.lastAudioTrack, participant, false); } }); }; connection.renegotiate = function(remoteUserId) { if (remoteUserId) { mPeer.renegotiatePeer(remoteUserId); return; } connection.peers.getAllParticipants().forEach(function(participant) { mPeer.renegotiatePeer(participant); }); }; function setStreamEndHandler(stream, isRemote) { isRemote = !!isRemote; if (stream.alreadySetEndHandler) { return; } stream.alreadySetEndHandler = true; if (!stream || !stream.addEventListener) return; stream.addEventListener('ended', function() { if (stream.idInstance) { currentUserMediaRequest.remove(stream.idInstance); } if (!isRemote) { delete connection.attachStreams[connection.attachStreams.indexOf(stream)]; if (connection.removeStreams.indexOf(stream) === -1) { connection.removeStreams.push(stream); } connection.attachStreams = removeNullEntries(connection.attachStreams); connection.removeStreams = removeNullEntries(connection.removeStreams); connection.observers.all(); } // connection.renegotiate(); var streamEvent = connection.streamEvents[stream.streamid]; if (!streamEvent) { streamEvent = { stream: stream, streamid: stream.streamid, type: isRemote ? 'remote' : 'local', userid: connection.userid, extra: connection.extra, mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null }; } if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') { return; } connection.onstreamended(streamEvent); delete connection.streamEvents[stream.streamid]; }, false); } connection.onMediaError = function(error, constraints) { if (!!connection.enableLogs) { console.error(error, constraints); } }; connection.addNewBroadcaster = function(broadcasterId, userPreferences) { if (connection.broadcasters.length) { setTimeout(function() { mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId, userPreferences, connection.broadcasters.join('|-,-|')); }, 10 * 1000); } if (!connection.session.oneway && !connection.session.broadcast && connection.direction === 'many-to-many' && connection.broadcasters.indexOf(broadcasterId) === -1) { connection.broadcasters.push(broadcasterId); keepNextBroadcasterOnServer(); } }; connection.autoCloseEntireSession = false; function keepNextBroadcasterOnServer() { if (!connection.isInitiator) return; if (connection.session.oneway || connection.session.broadcast || connection.direction !== 'many-to-many') { return; } var firstBroadcaster = connection.broadcasters[0]; var otherBroadcasters = []; connection.broadcasters.forEach(function(broadcaster) { if (broadcaster !== firstBroadcaster) { otherBroadcasters.push(broadcaster); } }); if (connection.autoCloseEntireSession) return; connection.shiftModerationControl(firstBroadcaster, otherBroadcasters, true); }; connection.filesContainer = connection.videosContainer = document.body || document.documentElement; connection.isInitiator = false; connection.shareFile = mPeer.shareFile; if (typeof FileProgressBarHandler !== 'undefined') { FileProgressBarHandler.handle(connection); } if (typeof TranslationHandler !== 'undefined') { TranslationHandler.handle(connection); } connection.token = getRandomString; connection.onNewParticipant = function(participantId, userPreferences) { connection.acceptParticipationRequest(participantId, userPreferences); }; connection.acceptParticipationRequest = function(participantId, userPreferences) { if (userPreferences.successCallback) { userPreferences.successCallback(); delete userPreferences.successCallback; } mPeer.createNewPeer(participantId, userPreferences); }; connection.onShiftedModerationControl = function(sender, existingBroadcasters) { connection.acceptModerationControl(sender, existingBroadcasters); }; connection.acceptModerationControl = function(sender, existingBroadcasters) { connection.isInitiator = true; // NEW initiator! connection.broadcasters = existingBroadcasters; connection.peers.getAllParticipants().forEach(function(participant) { mPeer.onNegotiationNeeded({ changedUUID: sender, oldUUID: connection.userid, newUUID: sender }, participant); }); connection.userid = sender; socket.emit('changed-uuid', connection.userid); }; connection.shiftModerationControl = function(remoteUserId, existingBroadcasters, firedOnLeave) { mPeer.onNegotiationNeeded({ shiftedModerationControl: true, broadcasters: existingBroadcasters, firedOnLeave: !!firedOnLeave }, remoteUserId); }; if (typeof StreamsHandler !== 'undefined') { connection.StreamsHandler = StreamsHandler; } connection.onleave = function(userid) {}; connection.invokeSelectFileDialog = function(callback) { var selector = new FileSelector(); selector.selectSingleFile(callback); }; connection.getPublicModerators = function(userIdStartsWith, callback) { if (typeof userIdStartsWith === 'function') { callback = userIdStartsWith; } connectSocket(function(socket) { socket.emit( 'get-public-moderators', typeof userIdStartsWith === 'string' ? userIdStartsWith : '', callback ); }); }; connection.onmute = function(e) { if (!e.mediaElement) { return; } if (e.muteType === 'both' || e.muteType === 'video') { e.mediaElement.src = null; e.mediaElement.pause(); e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; } else if (e.muteType === 'audio') { e.mediaElement.muted = true; } }; connection.onunmute = function(e) { if (!e.mediaElement) { return; } if (e.unmuteType === 'both' || e.unmuteType === 'video') { e.mediaElement.poster = null; e.mediaElement.src = URL.createObjectURL(e.stream); e.mediaElement.play(); } else if (e.unmuteType === 'audio') { e.mediaElement.muted = false; } }; connection.onExtraDataUpdated = function(event) { event.status = 'online'; connection.onUserStatusChanged(event, true); }; connection.onJoinWithPassword = function(remoteUserId) { console.warn(remoteUserId, 'is password protected. Please join with password.'); }; connection.onInvalidPassword = function(remoteUserId, oldPassword) { console.warn(remoteUserId, 'is password protected. Please join with valid password. Your old password', oldPassword, 'is wrong.'); }; connection.onPasswordMaxTriesOver = function(remoteUserId) { console.warn(remoteUserId, 'is password protected. Your max password tries exceeded the limit.'); }; connection.getAllParticipants = function(sender) { ret