webrtc-scalable-broadcast
Version:
Scalable WebRTC peer-to-peer broadcasting demo.
1,179 lines (972 loc) • 244 kB
JavaScript
// Last time updated at Sep 21, 2015, 08:32:23
// Quick-Demo for newbies: http://jsfiddle.net/c46de0L8/
// Another simple demo: http://jsfiddle.net/zar6fg60/
// Latest file can be found here: https://cdn.webrtc-experiment.com/RTCMultiConnection.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - www.RTCMultiConnection.org/docs
// FAQ - www.RTCMultiConnection.org/FAQ
// Changes log - www.RTCMultiConnection.org/changes-log/
// Demos - www.WebRTC-Experiment.com/RTCMultiConnection
// _________________________
// RTCMultiConnection-v2.2.2
(function() {
// RMC == RTCMultiConnection
// usually page-URL is used as channel-id
// you can always override it!
// www.RTCMultiConnection.org/docs/channel-id/
window.RMCDefaultChannel = location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('');
// www.RTCMultiConnection.org/docs/constructor/
window.RTCMultiConnection = function(channel) {
// an instance of constructor
var connection = this;
// a reference to RTCMultiSession
var rtcMultiSession;
// setting default channel or channel passed through constructor
connection.channel = channel || RMCDefaultChannel;
// to allow single user to join multiple rooms;
// you can change this property at runtime!
connection.isAcceptNewSession = true;
// www.RTCMultiConnection.org/docs/open/
connection.open = function(args) {
connection.isAcceptNewSession = false;
// www.RTCMultiConnection.org/docs/session-initiator/
// you can always use this property to determine room owner!
connection.isInitiator = true;
var dontTransmit = false;
// a channel can contain multiple rooms i.e. sessions
if (args) {
if (isString(args)) {
connection.sessionid = args;
} else {
if (!isNull(args.transmitRoomOnce)) {
connection.transmitRoomOnce = args.transmitRoomOnce;
}
if (!isNull(args.dontTransmit)) {
dontTransmit = args.dontTransmit;
}
if (!isNull(args.sessionid)) {
connection.sessionid = args.sessionid;
}
}
}
// if firebase && if session initiator
if (connection.socket && connection.socket.remove) {
connection.socket.remove();
}
if (!connection.sessionid) connection.sessionid = connection.channel;
connection.sessionDescription = {
sessionid: connection.sessionid,
userid: connection.userid,
session: connection.session,
extra: connection.extra
};
if (!connection.sessionDescriptions[connection.sessionDescription.sessionid]) {
connection.numberOfSessions++;
connection.sessionDescriptions[connection.sessionDescription.sessionid] = connection.sessionDescription;
}
// connect with signaling channel
initRTCMultiSession(function() {
// "captureUserMediaOnDemand" is disabled by default.
// invoke "getUserMedia" only when first participant found.
rtcMultiSession.captureUserMediaOnDemand = args ? !!args.captureUserMediaOnDemand : false;
if (args && args.onMediaCaptured) {
connection.onMediaCaptured = args.onMediaCaptured;
}
// for session-initiator, user-media is captured as soon as "open" is invoked.
if (!rtcMultiSession.captureUserMediaOnDemand) captureUserMedia(function() {
rtcMultiSession.initSession({
sessionDescription: connection.sessionDescription,
dontTransmit: dontTransmit
});
invokeMediaCaptured(connection);
});
if (rtcMultiSession.captureUserMediaOnDemand) {
rtcMultiSession.initSession({
sessionDescription: connection.sessionDescription,
dontTransmit: dontTransmit
});
}
});
return connection.sessionDescription;
};
// www.RTCMultiConnection.org/docs/connect/
connection.connect = function(sessionid) {
// a channel can contain multiple rooms i.e. sessions
if (sessionid) {
connection.sessionid = sessionid;
}
// connect with signaling channel
initRTCMultiSession(function() {
log('Signaling channel is ready.');
});
return this;
};
// www.RTCMultiConnection.org/docs/join/
connection.join = joinSession;
// www.RTCMultiConnection.org/docs/send/
connection.send = function(data, _channel) {
if (connection.numberOfConnectedUsers <= 0) {
// no connections
setTimeout(function() {
// try again
connection.send(data, _channel);
}, 1000);
return;
}
// send file/data or /text
if (!data)
throw 'No file, data or text message to share.';
// connection.send([file1, file2, file3])
// you can share multiple files, strings or data objects using "send" method!
if (data instanceof Array && !isNull(data[0].size) && !isNull(data[0].type)) {
// this mechanism can cause failure for subsequent packets/data
// on Firefox especially; and on chrome as well!
// todo: need to use setTimeout instead.
for (var i = 0; i < data.length; i++) {
data[i].size && data[i].type && connection.send(data[i], _channel);
}
return;
}
// File or Blob object MUST have "type" and "size" properties
if (!isNull(data.size) && !isNull(data.type)) {
if (!connection.enableFileSharing) {
throw '"enableFileSharing" boolean MUST be "true" to support file sharing.';
}
if (!rtcMultiSession.fileBufferReader) {
initFileBufferReader(connection, function(fbr) {
rtcMultiSession.fileBufferReader = fbr;
connection.send(data, _channel);
});
return;
}
var extra = merge({
userid: connection.userid
}, data.extra || connection.extra);
rtcMultiSession.fileBufferReader.readAsArrayBuffer(data, function(uuid) {
rtcMultiSession.fileBufferReader.getNextChunk(uuid, function(nextChunk, isLastChunk, extra) {
if (_channel) _channel.send(nextChunk);
else rtcMultiSession.send(nextChunk);
});
}, extra);
} else {
// to allow longest string messages
// and largest data objects
// or anything of any size!
// to send multiple data objects concurrently!
TextSender.send({
text: data,
channel: rtcMultiSession,
_channel: _channel,
connection: connection
});
}
};
function initRTCMultiSession(onSignalingReady) {
if (screenFrame) {
loadScreenFrame();
}
// RTCMultiSession is the backbone object;
// this object MUST be initialized once!
if (rtcMultiSession) return onSignalingReady();
// your everything is passed over RTCMultiSession constructor!
rtcMultiSession = new RTCMultiSession(connection, onSignalingReady);
}
connection.disconnect = function() {
if (rtcMultiSession) rtcMultiSession.disconnect();
rtcMultiSession = null;
};
function joinSession(session, joinAs) {
if (isString(session)) {
connection.skipOnNewSession = true;
}
if (!rtcMultiSession) {
log('Signaling channel is not ready. Connecting...');
// connect with signaling channel
initRTCMultiSession(function() {
log('Signaling channel is connected. Joining the session again...');
setTimeout(function() {
joinSession(session, joinAs);
}, 1000);
});
return;
}
// connection.join('sessionid');
if (isString(session)) {
if (connection.sessionDescriptions[session]) {
session = connection.sessionDescriptions[session];
} else
return setTimeout(function() {
log('Session-Descriptions not found. Rechecking..');
joinSession(session, joinAs);
}, 1000);
}
// connection.join('sessionid', { audio: true });
if (joinAs) {
return captureUserMedia(function() {
session.oneway = true;
joinSession(session);
}, joinAs);
}
if (!session || !session.userid || !session.sessionid) {
error('missing arguments', arguments);
var error = 'Invalid data passed over "connection.join" method.';
connection.onstatechange({
userid: 'browser',
extra: {},
name: 'Unexpected data detected.',
reason: error
});
throw error;
}
if (!connection.dontOverrideSession) {
connection.session = session.session;
}
var extra = connection.extra || session.extra || {};
// todo: need to verify that if-block statement works as expected.
// expectations: if it is oneway streaming; or if it is data-only connection
// then, it shouldn't capture user-media on participant's side.
if (session.oneway || isData(session)) {
rtcMultiSession.joinSession(session, extra);
} else {
captureUserMedia(function() {
rtcMultiSession.joinSession(session, extra);
});
}
}
var isFirstSession = true;
// www.RTCMultiConnection.org/docs/captureUserMedia/
function captureUserMedia(callback, _session, dontCheckChromExtension) {
// capture user's media resources
var session = _session || connection.session;
if (isEmpty(session)) {
if (callback) callback();
return;
}
// you can force to skip media capturing!
if (connection.dontCaptureUserMedia) {
return callback();
}
// if it is data-only connection
// if it is one-way connection and current user is participant
if (isData(session) || (!connection.isInitiator && session.oneway)) {
// www.RTCMultiConnection.org/docs/attachStreams/
connection.attachStreams = [];
return callback();
}
var constraints = {
audio: !!session.audio ? {
mandatory: {},
optional: [{
chromeRenderToAssociatedSink: true
}]
} : false,
video: !!session.video
};
// if custom audio device is selected
if (connection._mediaSources.audio) {
constraints.audio.optional.push({
sourceId: connection._mediaSources.audio
});
}
// if custom video device is selected
if (connection._mediaSources.video) {
constraints.video = {
optional: [{
sourceId: connection._mediaSources.video
}]
};
}
// for connection.session = {};
if (!session.screen && !constraints.audio && !constraints.video) {
return callback();
}
var screen_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: DetectRTC.screen.chromeMediaSource,
maxWidth: screen.width > 1920 ? screen.width : 1920,
maxHeight: screen.height > 1080 ? screen.height : 1080
},
optional: []
}
};
if (isFirefox && session.screen) {
if (location.protocol !== 'https:') {
return error(SCREEN_COMMON_FAILURE);
}
warn(Firefox_Screen_Capturing_Warning);
screen_constraints.video = merge(screen_constraints.video.mandatory, {
mozMediaSource: 'window', // mozMediaSource is redundant here
mediaSource: 'window' // 'screen' || 'window'
});
// Firefox is supporting audio+screen from single getUserMedia request
// audio+video+screen will become audio+screen for Firefox
// because Firefox isn't supporting multi-streams feature
if (constraints.audio /* && !session.video */ ) {
screen_constraints.audio = true;
constraints = {};
}
delete screen_constraints.video.chromeMediaSource;
}
// if screen is prompted
if (session.screen) {
if (isChrome && DetectRTC.screen.extensionid != ReservedExtensionID) {
useCustomChromeExtensionForScreenCapturing = true;
}
if (isChrome && !useCustomChromeExtensionForScreenCapturing && !dontCheckChromExtension && !DetectRTC.screen.sourceId) {
listenEventHandler('message', onIFrameCallback);
function onIFrameCallback(event) {
if (event.data && event.data.chromeMediaSourceId) {
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
var sourceId = event.data.chromeMediaSourceId;
DetectRTC.screen.sourceId = sourceId;
DetectRTC.screen.chromeMediaSource = 'desktop';
if (sourceId == 'PermissionDeniedError') {
var mediaStreamError = {
message: location.protocol == 'https:' ? 'User denied to share content of his screen.' : SCREEN_COMMON_FAILURE,
name: 'PermissionDeniedError',
constraintName: screen_constraints,
session: session
};
currentUserMediaRequest.mutex = false;
DetectRTC.screen.sourceId = null;
return connection.onMediaError(mediaStreamError);
}
captureUserMedia(callback, _session);
}
if (event.data && event.data.chromeExtensionStatus) {
warn('Screen capturing extension status is:', event.data.chromeExtensionStatus);
DetectRTC.screen.chromeMediaSource = 'screen';
captureUserMedia(callback, _session, true);
}
}
if (!screenFrame) {
loadScreenFrame();
}
screenFrame.postMessage();
return;
}
// check if screen capturing extension is installed.
if (isChrome && useCustomChromeExtensionForScreenCapturing && !dontCheckChromExtension && DetectRTC.screen.chromeMediaSource == 'screen' && DetectRTC.screen.extensionid) {
if (DetectRTC.screen.extensionid == ReservedExtensionID && document.domain.indexOf('webrtc-experiment.com') == -1) {
return captureUserMedia(callback, _session, true);
}
log('checking if chrome extension is installed.');
DetectRTC.screen.getChromeExtensionStatus(function(status) {
if (status == 'installed-enabled') {
DetectRTC.screen.chromeMediaSource = 'desktop';
}
captureUserMedia(callback, _session, true);
log('chrome extension is installed?', DetectRTC.screen.chromeMediaSource == 'desktop');
});
return;
}
if (isChrome && useCustomChromeExtensionForScreenCapturing && DetectRTC.screen.chromeMediaSource == 'desktop' && !DetectRTC.screen.sourceId) {
DetectRTC.screen.getSourceId(function(sourceId) {
if (sourceId == 'PermissionDeniedError') {
var mediaStreamError = {
message: 'User denied to share content of his screen.',
name: 'PermissionDeniedError',
constraintName: screen_constraints,
session: session
};
currentUserMediaRequest.mutex = false;
DetectRTC.screen.chromeMediaSource = 'desktop';
return connection.onMediaError(mediaStreamError);
}
if (sourceId == 'No-Response') {
error('Chrome extension seems not available. Make sure that manifest.json#L16 has valid content-script matches pointing to your URL.');
DetectRTC.screen.chromeMediaSource = 'screen';
return captureUserMedia(callback, _session, true);
}
captureUserMedia(callback, _session, true);
});
return;
}
if (isChrome && DetectRTC.screen.chromeMediaSource == 'desktop') {
screen_constraints.video.mandatory.chromeMediaSourceId = DetectRTC.screen.sourceId;
}
var _isFirstSession = isFirstSession;
_captureUserMedia(screen_constraints, constraints.audio || constraints.video ? function() {
if (_isFirstSession) isFirstSession = true;
_captureUserMedia(constraints, callback);
} : callback);
} else _captureUserMedia(constraints, callback, session.audio && !session.video);
function _captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks, dontPreventSSLAutoAllowed) {
connection.onstatechange({
userid: 'browser',
extra: {},
name: 'fetching-usermedia',
reason: 'About to capture user-media with constraints: ' + toStr(forcedConstraints)
});
if (connection.preventSSLAutoAllowed && !dontPreventSSLAutoAllowed && isChrome) {
// if navigator.customGetUserMediaBar.js is missing
if (!navigator.customGetUserMediaBar) {
loadScript(connection.resources.customGetUserMediaBar, function() {
_captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks, dontPreventSSLAutoAllowed);
});
return;
}
navigator.customGetUserMediaBar(forcedConstraints, function() {
_captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks, true);
}, function() {
connection.onMediaError({
name: 'PermissionDeniedError',
message: 'User denied permission.',
constraintName: forcedConstraints,
session: session
});
});
return;
}
var mediaConfig = {
onsuccess: function(stream, returnBack, idInstance, streamid) {
onStreamSuccessCallback(stream, returnBack, idInstance, streamid, forcedConstraints, forcedCallback, isRemoveVideoTracks, screen_constraints, constraints, session);
},
onerror: function(e, constraintUsed) {
// http://goo.gl/hrwF1a
if (isFirefox) {
if (e == 'PERMISSION_DENIED') {
e = {
message: '',
name: 'PermissionDeniedError',
constraintName: constraintUsed,
session: session
};
}
}
if (isFirefox && constraintUsed.video && constraintUsed.video.mozMediaSource) {
mediaStreamError = {
message: Firefox_Screen_Capturing_Warning,
name: e.name || 'PermissionDeniedError',
constraintName: constraintUsed,
session: session
};
connection.onMediaError(mediaStreamError);
return;
}
if (isString(e)) {
return connection.onMediaError({
message: 'Unknown Error',
name: e,
constraintName: constraintUsed,
session: session
});
}
// it seems that chrome 35+ throws "DevicesNotFoundError" exception
// when any of the requested media is either denied or absent
if (e.name && (e.name == 'PermissionDeniedError' || e.name == 'DevicesNotFoundError')) {
var mediaStreamError = 'Either: ';
mediaStreamError += '\n Media resolutions are not permitted.';
mediaStreamError += '\n Another application is using same media device.';
mediaStreamError += '\n Media device is not attached or drivers not installed.';
mediaStreamError += '\n You denied access once and it is still denied.';
if (e.message && e.message.length) {
mediaStreamError += '\n ' + e.message;
}
mediaStreamError = {
message: mediaStreamError,
name: e.name,
constraintName: constraintUsed,
session: session
};
connection.onMediaError(mediaStreamError);
if (isChrome && (session.audio || session.video)) {
// todo: this snippet fails if user has two or more
// microphone/webcam attached.
DetectRTC.load(function() {
// it is possible to check presence of the microphone before using it!
if (session.audio && !DetectRTC.hasMicrophone) {
warn('It seems that you have no microphone attached to your device/system.');
session.audio = session.audio = false;
if (!session.video) {
alert('It seems that you are capturing microphone and there is no device available or access is denied. Reloading...');
location.reload();
}
}
// it is possible to check presence of the webcam before using it!
if (session.video && !DetectRTC.hasWebcam) {
warn('It seems that you have no webcam attached to your device/system.');
session.video = session.video = false;
if (!session.audio) {
alert('It seems that you are capturing webcam and there is no device available or access is denied. Reloading...');
location.reload();
}
}
if (!DetectRTC.hasMicrophone && !DetectRTC.hasWebcam) {
alert('It seems that either both microphone/webcam are not available or access is denied. Reloading...');
location.reload();
} else if (!connection.getUserMediaPromptedOnce) {
// make maximum two tries!
connection.getUserMediaPromptedOnce = true;
captureUserMedia(callback, session);
}
});
}
}
if (e.name && e.name == 'ConstraintNotSatisfiedError') {
var mediaStreamError = 'Either: ';
mediaStreamError += '\n You are prompting unknown media resolutions.';
mediaStreamError += '\n You are using invalid media constraints.';
if (e.message && e.message.length) {
mediaStreamError += '\n ' + e.message;
}
mediaStreamError = {
message: mediaStreamError,
name: e.name,
constraintName: constraintUsed,
session: session
};
connection.onMediaError(mediaStreamError);
}
if (session.screen) {
if (isFirefox) {
error(Firefox_Screen_Capturing_Warning);
} else if (location.protocol !== 'https:') {
if (!isNodeWebkit && (location.protocol == 'file:' || location.protocol == 'http:')) {
error('You cannot use HTTP or file protocol for screen capturing. You must either use HTTPs or chrome extension page or Node-Webkit page.');
}
} else {
error('Unable to detect actual issue. Maybe "deprecated" screen capturing flag was not set using command line or maybe you clicked "No" button or maybe chrome extension returned invalid "sourceId". Please install chrome-extension: http://bit.ly/webrtc-screen-extension');
}
}
currentUserMediaRequest.mutex = false;
// to make sure same stream can be captured again!
var idInstance = JSON.stringify(constraintUsed);
if (currentUserMediaRequest.streams[idInstance]) {
delete currentUserMediaRequest.streams[idInstance];
}
},
mediaConstraints: connection.mediaConstraints || {}
};
mediaConfig.constraints = forcedConstraints || constraints;
mediaConfig.connection = connection;
getUserMedia(mediaConfig);
}
}
function onStreamSuccessCallback(stream, returnBack, idInstance, streamid, forcedConstraints, forcedCallback, isRemoveVideoTracks, screen_constraints, constraints, session) {
if (!streamid) streamid = getRandomString();
connection.onstatechange({
userid: 'browser',
extra: {},
name: 'usermedia-fetched',
reason: 'Captured user media using constraints: ' + toStr(forcedConstraints)
});
if (isRemoveVideoTracks) {
stream = convertToAudioStream(stream);
}
connection.localStreamids.push(streamid);
stream.onended = function() {
if (streamedObject.mediaElement && !streamedObject.mediaElement.parentNode && document.getElementById(stream.streamid)) {
streamedObject.mediaElement = document.getElementById(stream.streamid);
}
// when a stream is stopped; it must be removed from "attachStreams" array
connection.attachStreams.forEach(function(_stream, index) {
if (_stream == stream) {
delete connection.attachStreams[index];
connection.attachStreams = swap(connection.attachStreams);
}
});
onStreamEndedHandler(streamedObject, connection);
if (connection.streams[streamid]) {
connection.removeStream(streamid);
}
// if user clicks "stop" button to close screen sharing
var _stream = connection.streams[streamid];
if (_stream && _stream.sockets.length) {
_stream.sockets.forEach(function(socket) {
socket.send({
streamid: _stream.streamid,
stopped: true
});
});
}
currentUserMediaRequest.mutex = false;
// to make sure same stream can be captured again!
if (currentUserMediaRequest.streams[idInstance]) {
delete currentUserMediaRequest.streams[idInstance];
}
// to allow re-capturing of the screen
DetectRTC.screen.sourceId = null;
};
if (!isIE) {
stream.streamid = streamid;
stream.isScreen = forcedConstraints == screen_constraints;
stream.isVideo = forcedConstraints == constraints && !!constraints.video;
stream.isAudio = forcedConstraints == constraints && !!constraints.audio && !constraints.video;
// if muted stream is negotiated
stream.preMuted = {
audio: stream.getAudioTracks().length && !stream.getAudioTracks()[0].enabled,
video: stream.getVideoTracks().length && !stream.getVideoTracks()[0].enabled
};
}
var mediaElement = createMediaElement(stream, session);
mediaElement.muted = true;
var streamedObject = {
stream: stream,
streamid: streamid,
mediaElement: mediaElement,
blobURL: mediaElement.mozSrcObject ? URL.createObjectURL(stream) : mediaElement.src,
type: 'local',
userid: connection.userid,
extra: connection.extra,
session: session,
isVideo: !!stream.isVideo,
isAudio: !!stream.isAudio,
isScreen: !!stream.isScreen,
isInitiator: !!connection.isInitiator,
rtcMultiConnection: connection
};
if (isFirstSession) {
connection.attachStreams.push(stream);
}
isFirstSession = false;
connection.streams[streamid] = connection._getStream(streamedObject);
if (!returnBack) {
connection.onstream(streamedObject);
}
if (connection.setDefaultEventsForMediaElement) {
connection.setDefaultEventsForMediaElement(mediaElement, streamid);
}
if (forcedCallback) forcedCallback(stream, streamedObject);
if (connection.onspeaking) {
initHark({
stream: stream,
streamedObject: streamedObject,
connection: connection
});
}
}
// www.RTCMultiConnection.org/docs/captureUserMedia/
connection.captureUserMedia = captureUserMedia;
// www.RTCMultiConnection.org/docs/leave/
connection.leave = function(userid) {
if (!rtcMultiSession) return;
isFirstSession = true;
if (userid) {
connection.eject(userid);
return;
}
rtcMultiSession.leave();
};
// www.RTCMultiConnection.org/docs/eject/
connection.eject = function(userid) {
if (!connection.isInitiator) throw 'Only session-initiator can eject a user.';
if (!connection.peers[userid]) throw 'You ejected invalid user.';
connection.peers[userid].sendCustomMessage({
ejected: true
});
};
// www.RTCMultiConnection.org/docs/close/
connection.close = function() {
// close entire session
connection.autoCloseEntireSession = true;
connection.leave();
};
// www.RTCMultiConnection.org/docs/renegotiate/
connection.renegotiate = function(stream, session) {
if (connection.numberOfConnectedUsers <= 0) {
// no connections
setTimeout(function() {
// try again
connection.renegotiate(stream, session);
}, 1000);
return;
}
rtcMultiSession.addStream({
renegotiate: session || merge({
oneway: true
}, connection.session),
stream: stream
});
};
connection.attachExternalStream = function(stream, isScreen) {
var constraints = {};
if (stream.getAudioTracks && stream.getAudioTracks().length) {
constraints.audio = true;
}
if (stream.getVideoTracks && stream.getVideoTracks().length) {
constraints.video = true;
}
var screen_constraints = {
video: {
chromeMediaSource: 'fake'
}
};
var forcedConstraints = isScreen ? screen_constraints : constraints;
onStreamSuccessCallback(stream, false, '', null, forcedConstraints, false, false, screen_constraints, constraints, constraints);
};
// www.RTCMultiConnection.org/docs/addStream/
connection.addStream = function(session, socket) {
// www.RTCMultiConnection.org/docs/renegotiation/
if (connection.numberOfConnectedUsers <= 0) {
// no connections
setTimeout(function() {
// try again
connection.addStream(session, socket);
}, 1000);
return;
}
// renegotiate new media stream
if (session) {
var isOneWayStreamFromParticipant;
if (!connection.isInitiator && session.oneway) {
session.oneway = false;
isOneWayStreamFromParticipant = true;
}
captureUserMedia(function(stream) {
if (isOneWayStreamFromParticipant) {
session.oneway = true;
}
addStream(stream);
}, session);
} else addStream();
function addStream(stream) {
rtcMultiSession.addStream({
stream: stream,
renegotiate: session || connection.session,
socket: socket
});
}
};
// www.RTCMultiConnection.org/docs/removeStream/
connection.removeStream = function(streamid, dontRenegotiate) {
if (connection.numberOfConnectedUsers <= 0) {
// no connections
setTimeout(function() {
// try again
connection.removeStream(streamid, dontRenegotiate);
}, 1000);
return;
}
if (!streamid) streamid = 'all';
if (!isString(streamid) || streamid.search(/all|audio|video|screen/gi) != -1) {
for (var stream in connection.streams) {
if (connection._skip.indexOf(stream) == -1) {
_stream = connection.streams[stream];
if (streamid == 'all') _detachStream(_stream, {
audio: true,
video: true,
screen: true
});
else if (isString(streamid)) {
// connection.removeStream('screen');
var config = {};
config[streamid] = true;
_detachStream(_stream, config);
} else _detachStream(_stream, streamid);
}
}
if (!dontRenegotiate && connection.detachStreams.length) {
connection.renegotiate();
}
function _detachStream(_stream, config) {
if (config.local && _stream.type != 'local') return;
if (config.remote && _stream.type != 'remote') return;
// connection.removeStream({screen:true});
if (config.screen && !!_stream.isScreen) {
connection.detachStreams.push(_stream.streamid);
}
// connection.removeStream({audio:true});
if (config.audio && !!_stream.isAudio) {
connection.detachStreams.push(_stream.streamid);
}
// connection.removeStream({video:true});
if (config.video && !!_stream.isVideo) {
connection.detachStreams.push(_stream.streamid);
}
// connection.removeStream({});
if (!config.audio && !config.video && !config.screen) {
connection.detachStreams.push(_stream.streamid);
}
if (connection.detachStreams.indexOf(_stream.streamid) != -1) {
log('removing stream', _stream.streamid);
onStreamEndedHandler(_stream, connection);
if (config.stop) {
connection.stopMediaStream(_stream.stream);
}
}
}
return;
}
var stream = connection.streams[streamid];
// detach pre-attached streams
if (!stream) return warn('No such stream exists. Stream-id:', streamid);
// www.RTCMultiConnection.org/docs/detachStreams/
connection.detachStreams.push(stream.streamid);
log('removing stream', stream.streamid);
onStreamEndedHandler(stream, connection);
// todo: how to allow "stop" function?
// connection.stopMediaStream(stream.stream)
if (!dontRenegotiate) {
connection.renegotiate();
}
};
connection.switchStream = function(session) {
if (connection.numberOfConnectedUsers <= 0) {
// no connections
setTimeout(function() {
// try again
connection.switchStream(session);
}, 1000);
return;
}
connection.removeStream('all', true);
connection.addStream(session);
};
// www.RTCMultiConnection.org/docs/sendCustomMessage/
connection.sendCustomMessage = function(message) {
if (!rtcMultiSession || !rtcMultiSession.defaultSocket) {
return setTimeout(function() {
connection.sendCustomMessage(message);
}, 1000);
}
rtcMultiSession.defaultSocket.send({
customMessage: true,
message: message
});
};
// set RTCMultiConnection defaults on constructor invocation
setDefaults(connection);
};
function RTCMultiSession(connection, callbackForSignalingReady) {
var socketObjects = {};
var sockets = [];
var rtcMultiSession = this;
var participants = {};
if (!rtcMultiSession.fileBufferReader && connection.session.data && connection.enableFileSharing) {
initFileBufferReader(connection, function(fbr) {
rtcMultiSession.fileBufferReader = fbr;
});
}
var textReceiver = new TextReceiver(connection);
function onDataChannelMessage(e) {
if (e.data.checkingPresence && connection.channels[e.userid]) {
connection.channels[e.userid].send({
presenceDetected: true
});
return;
}
if (e.data.presenceDetected && connection.peers[e.userid]) {
connection.peers[e.userid].connected = true;
return;
}
if (e.data.type === 'text') {
textReceiver.receive(e.data, e.userid, e.extra);
} else {
if (connection.autoTranslateText) {
e.original = e.data;
connection.Translator.TranslateText(e.data, function(translatedText) {
e.data = translatedText;
connection.onmessage(e);
});
} else connection.onmessage(e);
}
}
function onNewSession(session) {
if (connection.skipOnNewSession) return;
if (!session.session) session.session = {};
if (!session.extra) session.extra = {};
// todo: make sure this works as expected.
// i.e. "onNewSession" should be fired only for
// sessionid that is passed over "connect" method.
if (connection.sessionid && session.sessionid != connection.sessionid) return;
if (connection.onNewSession) {
session.join = function(forceSession) {
if (!forceSession) return connection.join(session);
for (var f in forceSession) {
session.session[f] = forceSession[f];
}
// keeping previous state
var isDontCaptureUserMedia = connection.dontCaptureUserMedia;
connection.dontCaptureUserMedia = false;
connection.captureUserMedia(function() {
connection.dontCaptureUserMedia = true;
connection.join(session);
// returning back previous state
connection.dontCaptureUserMedia = isDontCaptureUserMedia;
}, forceSession);
};
if (!session.extra) session.extra = {};
return connection.onNewSession(session);
}
connection.join(session);
}
function updateSocketForLocalStreams(socket) {
for (var i = 0; i < connection.localStreamids.length; i++) {
var streamid = connection.localStreamids[i];
if (connection.streams[streamid]) {
// using "sockets" array to keep references of all sockets using
// this media stream; so we can fire "onStreamEndedHandler" among all users.
connection.streams[streamid].sockets.push(socket);
}
}
}
function newPrivateSocket(_config) {
var socketConfig = {
channel: _config.channel,
onmessage: socketResponse,
onopen: function(_socket) {
if (_socket) socket = _socket;
if (isofferer && !peer) {
peerConfig.session = connection.session;
if (!peer) peer = new PeerConnection();
peer.create('offer', peerConfig);
}
_config.socketIndex = socket.index = sockets.length;
socketObjects[socketConfig.channel] = socket;
sockets[_config.socketIndex] = socket;
updateSocketForLocalStreams(socket);
if (!socket.__push) {
socket.__push = socket.send;
socket.send = function(message) {
message.userid = message.userid || connection.userid;
message.extra = message.extra || connection.extra || {};
socket.__push(message);
};
}
}
};
socketConfig.callback = function(_socket) {
socket = _socket;
socketConfig.onopen();
};
var socket = connection.openSignalingChannel(socketConfig);
if (socket) socketConfig.onopen(socket);
var isofferer = _config.isofferer,
peer;
var peerConfig = {
onopen: onChannelOpened,
onicecandidate: function(candidate) {
if (!connection.candidates) throw 'ICE candidates are mandatory.';
if (!connection.iceProtocols) throw 'At least one must be true; UDP or TCP.';
var iceCandidates = connection.candidates;
var stun = iceCandidates.stun;
var turn = iceCandidates.turn;
if (!isNull(iceCandidates.reflexive)) stun = iceCandidates.reflexive;
if (!isNull(iceCandidates.relay)) turn = iceCandidates.relay;
if (!iceCandidates.host && !!candidate.candidate.match(/a=candidate.*typ host/g)) return;
if (!turn && !!candidate.candidate.match(/a=candidate.*typ relay/g)) return;
if (!stun && !!candidate.candidate.match(/a=candidate.*typ srflx/g)) return;
var protocol = connection.iceProtocols;
if (!protocol.udp && !!candidate.candidate.match(/a=candidate.* udp/g)) return;
if (!protocol.tcp && !!candidate.candidate.match(/a=candidate.* tcp/g)) return;
if (!window.selfNPObject) window.selfNPObject = candidate;
socket && socket.send({
candidate: JSON.stringify({
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
})
});
},
onmessage: function(data) {
if (!data) return;
var abToStr = ab2str(data);
if (abToStr.indexOf('"userid":') != -1) {
abToStr = JSON.parse(abToStr);
onDataChannelMessage(abToStr);
} else if (data instanceof ArrayBuffer || data instanceof DataView) {