UNPKG

rtcmulticonnection

Version:

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

1,355 lines (1,132 loc) 67.6 kB
(function(connection) { forceOptions = forceOptions || { useDefaultDevices: true }; connection.channel = connection.sessionid = (roomid || location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')) + ''; var mPeer = new MultiPeers(connection); var preventDuplicateOnStreamEvents = {}; mPeer.onGettingLocalMedia = function(stream, callback) { callback = callback || function() {}; if (preventDuplicateOnStreamEvents[stream.streamid]) { return; } preventDuplicateOnStreamEvents[stream.streamid] = true; try { stream.type = 'local'; } catch (e) {} connection.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, isAudioMuted: true }; setHarkEvents(connection, connection.streamEvents[stream.streamid]); setMuteHandlers(connection, connection.streamEvents[stream.streamid]); connection.onstream(connection.streamEvents[stream.streamid]); callback(); }, connection); }; mPeer.onGettingRemoteMedia = function(stream, remoteUserId) { try { stream.type = 'remote'; } catch (e) {} connection.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 }; 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 }; } if (connection.peersBackup[streamEvent.userid]) { streamEvent.extra = connection.peersBackup[streamEvent.userid].extra; } connection.onstreamended(streamEvent); delete connection.streamEvents[stream.streamid]; }; mPeer.onNegotiationNeeded = function(message, remoteUserId, callback) { remoteUserId = remoteUserId || message.remoteUserId; message = message || ''; connectSocket(function() { connection.socket.emit(connection.socketMessageEvent, typeof message.password !== 'undefined' ? 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 (connection.socket) { connection.socket.emit('disconnect-with', remoteUserId, callback || function() {}); } connection.deletePeer(remoteUserId); }; connection.socketOptions = { // 'force new connection': true, // For SocketIO version < 1.0 // 'forceNew': true, // For SocketIO version >= 1.0 'transport': 'polling' // fixing transport:unknown issues }; function connectSocket(connectCallback) { connection.socketAutoReConnect = true; if (connection.socket) { // todo: check here readySate/etc. to make sure socket is still opened if (connectCallback) { connectCallback(connection.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.'; } } new SocketConnection(connection, function(s) { if (connectCallback) { connectCallback(connection.socket); } }); } // 1st paramter is roomid // 2nd paramter can be either password or a callback function // 3rd paramter is a callback function connection.openOrJoin = function(roomid, password, callback) { callback = callback || function() {}; connection.checkPresence(roomid, function(isRoomExist, roomid) { // i.e. 2nd parameter is a callback function if (typeof password === 'function' && typeof password !== 'undefined') { callback = password; // switch callback functions password = null; } if (!password && !!connection.password) { password = connection.password; } if (isRoomExist) { 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 }; beforeJoin(connectionDescription.message, function() { joinRoom(connectionDescription, password, function() {}); }); return; } connection.waitingForLocalMedia = true; connection.isInitiator = true; // var oldUserId = connection.userid; // connection.userid = connection.sessionid = roomid || connection.sessionid; // connection.userid += ''; // connection.socket.emit('changed-uuid', connection.userid); if (isData(connection.session)) { openRoom(callback, password); return; } connection.captureUserMedia(function() { openRoom(callback, password); }); }); }; // don't allow someone to join this person until he has the media connection.waitingForLocalMedia = false; connection.open = function(roomid, isPublicModerator, callback) { connection.waitingForLocalMedia = true; connection.isInitiator = true; callback = callback || function() {}; if (typeof isPublicModerator === 'function') { callback = isPublicModerator; isPublicModerator = false; } // var oldUserId = connection.userid; // connection.userid = connection.sessionid = roomid || connection.sessionid; // connection.userid += ''; connectSocket(function() { // connection.socket.emit('changed-uuid', connection.userid); if (isPublicModerator == true) { connection.becomePublicModerator(); } if (isData(connection.session)) { openRoom(callback, connection.password); return; } connection.captureUserMedia(function() { openRoom(callback, connection.password); }); }); }; connection.becomePublicModerator = function() { if (!connection.isInitiator) return; connection.socket.emit('become-a-public-moderator'); }; connection.dontMakeMeModerator = function() { connection.socket.emit('dont-make-me-moderator'); }; // this object keeps extra-data records for all connected users // this object is never cleared so you can always access extra-data even if a user left connection.peersBackup = {}; connection.deletePeer = function(remoteUserId) { if (!remoteUserId || !connection.peers[remoteUserId]) { return; } var eventObject = { userid: remoteUserId, extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {} }; if (connection.peersBackup[eventObject.userid]) { eventObject.extra = connection.peersBackup[eventObject.userid].extra; } connection.onleave(eventObject); 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]; } } } 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 || {}; var cb = function() {}; if (typeof options === 'function') { cb = 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: connection.password || false }; beforeJoin(connectionDescription.message, function() { connectSocket(function() { joinRoom(connectionDescription, connection.password, cb); }); }); return connectionDescription; }; function joinRoom(connectionDescription, password, cb) { if (password && (typeof password === 'function' || password.prototype || typeof password === 'object')) { password = null; } connection.socket.emit('join-room', { sessionid: connection.sessionid, session: connection.session, mediaConstraints: connection.mediaConstraints, sdpConstraints: connection.sdpConstraints, streams: getStreamInfoForAdmin(), extra: connection.extra, password: typeof password !== 'undefined' && typeof password !== 'object' ? (password || onnection.password) : '' }, function(isRoomJoined, error) { if (isRoomJoined === true) { if (connection.enableLogs) { console.log('isRoomJoined: ', isRoomJoined, ' roomid: ', connection.sessionid); } if (!!connection.peers[connection.sessionid]) { // on socket disconnect & reconnect return; } mPeer.onNegotiationNeeded(connectionDescription); cb(); } if (isRoomJoined === false) { if (connection.enableLogs) { console.warn('isRoomJoined: ', error, ' roomid: ', connection.sessionid); } // retry after 3 seconds setTimeout(function() { joinRoom(connectionDescription, password, cb); }, 3000); } }); } function openRoom(callback, password) { if (password && (typeof password === 'function' || password.prototype || typeof password === 'object')) { password = null; } connection.waitingForLocalMedia = false; connection.socket.emit('open-room', { sessionid: connection.sessionid, session: connection.session, mediaConstraints: connection.mediaConstraints, sdpConstraints: connection.sdpConstraints, streams: getStreamInfoForAdmin(), extra: connection.extra, password: typeof password !== 'undefined' && typeof password !== 'object' ? (password || onnection.password) : '' }, function(isRoomOpened, error) { if (isRoomOpened === true) { if (connection.enableLogs) { console.log('isRoomOpened: ', isRoomOpened, ' roomid: ', connection.sessionid); } callback(isRoomOpened, connection.sessionid); } if (isRoomOpened === false) { if (connection.enableLogs) { console.warn('isRoomOpened: ', error, ' roomid: ', connection.sessionid); } } }); } function getStreamInfoForAdmin() { try { return connection.streamEvents.selectAll('local').map(function(event) { return { streamid: event.streamid, tracks: event.stream.getTracks().length }; }); } catch (e) { return []; } } function beforeJoin(userPreferences, callback) { if (connection.dontCaptureUserMedia || userPreferences.isDataOnly) { callback(); return; } var localMediaConstraints = {}; if (userPreferences.localPeerSdpConstraints.OfferToReceiveAudio) { localMediaConstraints.audio = connection.mediaConstraints.audio; } if (userPreferences.localPeerSdpConstraints.OfferToReceiveVideo) { localMediaConstraints.video = connection.mediaConstraints.video; } var session = userPreferences.session || connection.session; if (session.oneway && session.audio !== 'two-way' && session.video !== 'two-way' && session.screen !== 'two-way') { callback(); return; } if (session.oneway && session.audio && session.audio === 'two-way') { session = { audio: true }; } if (session.audio || session.video || session.screen) { if (session.screen) { if (DetectRTC.browser.name === 'Edge') { navigator.getDisplayMedia({ video: true, audio: isAudioPlusTab(connection) }).then(function(screen) { screen.isScreen = true; mPeer.onGettingLocalMedia(screen); if ((session.audio || session.video) && !isAudioPlusTab(connection)) { connection.invokeGetUserMedia(null, callback); } else { callback(screen); } }, function(error) { console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); }); } else { connection.getScreenConstraints(function(error, screen_constraints) { connection.invokeGetUserMedia({ audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, video: screen_constraints, isScreen: true }, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, callback) : callback); }); } } else if (session.audio || session.video) { connection.invokeGetUserMedia(null, callback, session); } } } connection.getUserMedia = connection.captureUserMedia = function(callback, sessionForced) { callback = callback || function() {}; var session = sessionForced || connection.session; if (connection.dontCaptureUserMedia || isData(session)) { callback(); return; } if (session.audio || session.video || session.screen) { if (session.screen) { if (DetectRTC.browser.name === 'Edge') { navigator.getDisplayMedia({ video: true, audio: isAudioPlusTab(connection) }).then(function(screen) { screen.isScreen = true; mPeer.onGettingLocalMedia(screen); if ((session.audio || session.video) && !isAudioPlusTab(connection)) { var nonScreenSession = {}; for (var s in session) { if (s !== 'screen') { nonScreenSession[s] = session[s]; } } connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession); return; } callback(screen); }, function(error) { console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); }); } else { connection.getScreenConstraints(function(error, screen_constraints) { if (error) { throw error; } connection.invokeGetUserMedia({ audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, video: screen_constraints, isScreen: true }, function(stream) { if ((session.audio || session.video) && !isAudioPlusTab(connection)) { var nonScreenSession = {}; for (var s in session) { if (s !== 'screen') { nonScreenSession[s] = session[s]; } } connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession); return; } callback(stream); }); }); } } else if (session.audio || session.video) { connection.invokeGetUserMedia(sessionForced, callback, session); } } }; connection.onbeforeunload = function(arg1, 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.isInitiator = false; }; if (!window.ignoreBeforeUnload) { // user can implement its own version of window.onbeforeunload connection.closeBeforeUnload = true; window.addEventListener('beforeunload', connection.onbeforeunload, false); } else { connection.closeBeforeUnload = false; } connection.userid = getRandomString(); connection.changeUserId = function(newUserId, callback) { callback = callback || function() {}; connection.userid = newUserId || getRandomString(); connection.socket.emit('changed-uuid', connection.userid, callback); }; connection.extra = {}; connection.attachStreams = []; connection.session = { audio: true, video: true }; connection.enableFileSharing = false; // all values in kbps connection.bandwidth = { screen: false, audio: false, video: false }; connection.codecs = { audio: 'opus', video: 'VP9' }; connection.processSdp = function(sdp) { if (DetectRTC.browser.name === 'Safari') { return sdp; } if (connection.codecs.video.toUpperCase() === 'VP8') { sdp = CodecsHandler.preferCodec(sdp, 'vp8'); } if (connection.codecs.video.toUpperCase() === 'VP9') { sdp = CodecsHandler.preferCodec(sdp, 'vp9'); } if (connection.codecs.video.toUpperCase() === 'H264') { sdp = CodecsHandler.preferCodec(sdp, 'h264'); } if (connection.codecs.audio === 'G722') { sdp = CodecsHandler.removeNonG722(sdp); } if (DetectRTC.browser.name === 'Firefox') { return sdp; } if (connection.bandwidth.video || connection.bandwidth.screen) { sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); } if (connection.bandwidth.video) { sdp = CodecsHandler.setVideoBitrates(sdp, { min: connection.bandwidth.video * 8 * 1024, max: connection.bandwidth.video * 8 * 1024 }); } if (connection.bandwidth.audio) { sdp = CodecsHandler.setOpusAttributes(sdp, { maxaveragebitrate: connection.bandwidth.audio * 8 * 1024, maxplaybackrate: connection.bandwidth.audio * 8 * 1024, stereo: 1, maxptime: 3 }); } return sdp; }; if (typeof CodecsHandler !== 'undefined') { connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler; } connection.mediaConstraints = { audio: { mandatory: {}, optional: connection.bandwidth.audio ? [{ bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 }] : [] }, video: { mandatory: {}, optional: connection.bandwidth.video ? [{ bandwidth: connection.bandwidth.video * 8 * 1024 || 128 * 8 * 1024 }, { facingMode: 'user' }] : [{ facingMode: 'user' }] } }; if (DetectRTC.browser.name === 'Firefox') { connection.mediaConstraints = { audio: true, video: true }; } if (!forceOptions.useDefaultDevices && !DetectRTC.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 (DetectRTC.browser.name === 'Firefox') { 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 (DetectRTC.browser.name === 'Firefox') { 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.rtcpMuxPolicy = 'require'; // "require" or "negotiate" connection.iceTransportPolicy = null; // "relay" or "all" 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() { connection.onbeforeunload(false, true); }; connection.closeEntireSession = function(callback) { callback = callback || function() {}; connection.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); var played = e.mediaElement.play(); if (typeof played !== 'undefined') { played.catch(function() { /*** iOS 11 doesn't allow automatic play and rejects ***/ }).then(function() { setTimeout(function() { e.mediaElement.play(); }, 2000); }); return; } setTimeout(function() { e.mediaElement.play(); }, 2000); }; 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, remoteUserId) { var stream; connection.attachStreams.forEach(function(localStream) { if (localStream.id === streamid) { stream = localStream; } }); if (!stream) { console.warn('No such stream exist.', streamid); return; } connection.peers.getAllParticipants().forEach(function(participant) { if (remoteUserId && participant !== remoteUserId) { return; } var user = connection.peers[participant]; try { user.peer.removeStream(stream); } catch (e) {} }); connection.renegotiate(); }; 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) { if (DetectRTC.browser.name === 'Edge') { navigator.getDisplayMedia({ video: true, audio: isAudioPlusTab(connection) }).then(function(screen) { screen.isScreen = true; mPeer.onGettingLocalMedia(screen); if ((session.audio || session.video) && !isAudioPlusTab(connection)) { connection.invokeGetUserMedia(null, function(stream) { gumCallback(stream); }); } else { gumCallback(screen); } }, function(error) { console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); }); } else { connection.getScreenConstraints(function(error, screen_constraints) { if (error) { if (error === 'PermissionDeniedError') { if (session.streamCallback) { session.streamCallback(null); } if (connection.enableLogs) { console.error('User rejected to share his screen.'); } return; } return alert(error); } connection.invokeGetUserMedia({ audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, video: screen_constraints, isScreen: true }, function(stream) { if ((session.audio || session.video) && !isAudioPlusTab(connection)) { connection.invokeGetUserMedia(null, function(stream) { gumCallback(stream); }); } else { gumCallback(stream); } }); }); } } else if (session.audio || session.video) { connection.invokeGetUserMedia(null, gumCallback); } } function gumCallback(stream) { if (session.streamCallback) { session.streamCallback(stream); } connection.renegotiate(remoteUserId); } }; connection.invokeGetUserMedia = function(localMediaConstraints, callback, session) { if (!session) { session = connection.session; } if (!localMediaConstraints) { localMediaConstraints = connection.mediaConstraints; } getUserMediaHandler({ onGettingLocalMedia: function(stream) { var videoConstraints = localMediaConstraints.video; 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, function() { if (typeof callback === 'function') { callback(stream); } }); }, onLocalMediaError: function(error, constraints) { mPeer.onLocalMediaError(error, constraints); }, localMediaConstraints: localMediaConstraints || { audio: session.audio ? localMediaConstraints.audio : false, video: session.video ? localMediaConstraints.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 stream; 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) { if (DetectRTC.browser.name === 'Edge') { navigator.getDisplayMedia({ video: true, audio: isAudioPlusTab(connection) }).then(function(screen) { screen.isScreen = true; mPeer.onGettingLocalMedia(screen); if ((session.audio || session.video) && !isAudioPlusTab(connection)) { connection.invokeGetUserMedia(null, gumCallback); } else { gumCallback(screen); } }, function(error) { console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.'); }); } else { connection.getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } connection.invokeGetUserMedia({ audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false, video: screen_constraints, isScreen: true }, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, gumCallback) : gumCallback); }); } } else if (session.audio || session.video) { connection.invokeGetUserMedia(null, gumCallback); } } function gumCallback(stream) { connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen); } }; 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); }); }; connection.setStreamEndHandler = function(stream, isRemote) { if (!stream || !stream.addEventListener) return; isRemote = !!isRemote; if (stream.alreadySetEndHandler) { return; } stream.alreadySetEndHandler = true; var streamEndedEvent = 'ended'; if ('oninactive' in stream) { streamEndedEvent = 'inactive'; } stream.addEventListener(streamEndedEvent, function() { if (stream.idInstance) { currentUserMediaRequest.remove(stream.idInstance); } if (!isRemote) { // reset attachStreams var streams = []; connection.attachStreams.forEach(function(s) { if (s.id != stream.id) { streams.push(s); } }); connection.attachStreams = streams; } // 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 (isRemote && connection.peers[streamEvent.userid]) { // reset remote "streams" var peer = connection.peers[streamEvent.userid].peer; var streams = []; peer.getRemoteStreams().forEach(function(s) { if (s.id != stream.id) { streams.push(s); } }); connection.peers[streamEvent.userid].streams = streams; } if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') { return; } if (connection.peersBackup[streamEvent.userid]) { streamEvent.extra = connec