react-native-enovawbrtc
Version:
Web Rtc
1,126 lines (996 loc) • 36 kB
JavaScript
var Janus = require('./janus.umd');
var Utils = require('../InternalUtils');
var coreConfig = require('../Config');
var EventEmitter = require('fbemitter').EventEmitter;
var mediaDevices = require('../Dependencies').mediaDevices;
var EVENT_PARTICIPANT_JOINED = "participantjoined";
var EVENT_PARTICIPANT_LEFT = "participantleft";
var EVENT_LOCAL_STREAM = "localstream";
var EVENT_REMOTE_STREAM = "remotestream";
/**
* @class
* @param {Object} configParams - a set of configuration parameters. The
* following parameters are applied:<br>
* @param {String} configParams.server - (<b>required</b>) the address of the
* gateway as a specific address (e.g., http://yourserver:8088 to use
* the plain HTTP API or ws://yourserver:8188 for WebSockets).
* @param {Boolean} configParams.debug - (<i>optional</i>) whether debug should
* be enabled on the JavaScript console (true/false). Default is true.
* @throws "'server' parameter is mandatory" error if 'server' parameter is null
* or undefined.
* @throws "missing adapter.js" error if the 'adapter.js' is not connected.
*/
function VideoConferencingClient(configParams) {
if (!Utils.getEnv().reactnative && !adapter) {
throw "Error: in order to use this library please connect adapter.js. More info https://github.com/webrtc/adapter";
}
this.token = configParams.token
delete configParams.token
this.configs = configParams;
if (!this.configs.server) {
throw "'server' parameter is mandatory.";
} else {
if (this.configs.server.includes("http")) {
this.configs.server = this.configs.server + "/janus";
}
}
if (!this.configs.debug) {
this.configs.debug = "all";
}
this.engine = null;
this.videoRoomPlugin = null;
this.isOnlyAudio = false;
//
this.currentDialogId = null;
this.remoteFeeds = [];
this.remoteJseps = [];
this.remoteFeedsAttachingInProgress = [];
//
this.currentMidiaDeviceId = null;
//
this.bitrateTimers = [];
//
this.emitter = new EventEmitter();
}
/**
* Attach media stream to HTML 'video' element
*
* @static
* @param {Object} element - HTML 'video' element
* @param {Object} stream - WebRTC media stream
*/
VideoConferencingClient.attachMediaStream = function (element, stream) {
Janus.attachMediaStream(element, stream);
};
/**
* Get plugged devices
*
* @static
* @param {function} callback - a callback to be notified about result
* (with single argument - array of all devices).
*/
VideoConferencingClient.listDevices = function (callback) {
mediaDevices.enumerateDevices().then(function (devices) {
console.debug(devices);
callback(devices);
}).catch(function (err) {
console.log('[VideoConferencingClient.listDevices][error]', err);
callback([]);
});;
};
/**
* Get plugged video input devices only
*
* @static
* @param {function} callback - a callback to be notified about result
* (with single argument - array of video input devices).
*/
VideoConferencingClient.listVideoinputDevices = function (callback) {
VideoConferencingClient.listDevices(function (devices) {
var videoSelect = [];
// code sample
// https://github.com/webrtc/samples/blob/gh-pages/src/content/devices/input-output/js/main.js#L27
for (var i = 0; i !== devices.length; ++i) {
var deviceInfo = devices[i];
if (deviceInfo.kind === 'videoinput') {
var videoinputDescription = deviceInfo.label || 'camera ' + (videoSelect.length + 1);
var videoinputId = deviceInfo.deviceId;
videoSelect.push({ "label": videoinputDescription, "deviceId": videoinputId });
}
}
callback(videoSelect);
});
};
VideoConferencingClient.prototype = {
/**
* Create video session
*
* @param {Object} callbacks - a set of callbacks to be notified about result,
* namely:<br>
* @param {function} callbacks.success - the session was successfully created
* and is ready to be used.
* @param {function} callbacks.error - the session was NOT successfully
* created. This callback passes single argument - text description of error.
* @param {function} callbacks.destroyed - the session was destroyed and
* can't be used any more.
*/
createSession: function (callbacks) {
var self = this;
Janus.init({
debug: this.configs.debug, callback: function () {
if (!Janus.isWebrtcSupported()) {
if (typeof callbacks.error === 'function') {
callbacks.error("Your browser does not support WebRTC, so you can't use this functionality.");
}
return;
}
self.engine = new Janus({
server: self.configs.server,
iceServers: coreConfig.videochat.iceServers,
token: self.token,
success: function () {
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
},
destroyed: function () {
if (typeof callbacks.destroyed === 'function') {
Utils.safeCallbackCall(callbacks.destroyed);
}
},
timeoutSessionCallback: function () {
if (typeof callbacks.timeoutSessionCallback === 'function') {
Utils.safeCallbackCall(callbacks.timeoutSessionCallback);
}
}
});
}
});
},
/**
* Returns the unique session identifier
*
* @returns {String} unique session identifier or null.
*/
getSessionId: function () {
if (this.engine) {
return this.engine.getSessionId();
}
return null;
},
/**
* Destroy video session
*
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - the session was successfully
* destroyed and no longer available.
* @param {function} callbacks.error - the session was NOT successfully
* destroyed. This callback passes single argument - text description
* of error.
*/
destroySession: function (callbacks) {
var self = this;
this.engine.destroy({});
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
/**
* Сreate a video conferencing plugin handle.
*
* @param {Boolean} isRemote To pass 'false' when you attach plugin to
* current user and pass 'true' when attach to remote user.
* @param {Number} userId To pass 'null' when you attach plugin to
* current user and pass remote user id when attach to remote user.
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - the handle was successfully
* created and is ready to be used.
* @param {function} callbacks.error - the handle was NOT successfully
* created or some error has occured. The format of error is the following:
* {"error_code": "some integer code", "error": "some text description"}.
* Possible values of 'error_code': <br>
* <ul>
* <li>428: No such feed - can happen when a user joins room and quickly
* leaves it so other user tries to subscribe to none existend feed.
* Usually, this error can be ignored.</li>
* <li>433: Unauthorized - do not have proper rights to join this room.</li>
* <li>436: User ID already exists in this room.</li>
* <li>400: Some not usual error occured, for example - no connection to
* server. </li>
* </ul>
*
* @param {function} callbacks.consentDialog - this callback is triggered
* just before <b>getUserMedia</b> is called (parameter=<b>true</b>) and
* after it is completed (parameter=<b>false</b>); this means it can be
* used to modify the UI accordingly, e.g., to prompt the user about the
* need to accept the device access consent requests.
* @param {function} callbacks.mediaState - this callback is triggered
* when server starts or stops receiving your media: for instance,
* a <b>mediaState</b> with type=audio and on=true means server started
* receiving your audio stream (or started getting them again after
* a pause of more than a second); a mediaState with type=video
* and on=false means server hasn't received any video from you in the
* last second, after a start was detected before; useful to figure out
* when server actually started handling your media, or to detect problems
* on the media path (e.g., media never started, or stopped at some time).
* @param {function} callbacks.webrtcState - this callback is triggered
* with a <b>true</b> value when the PeerConnection associated to a handle
* becomes active (so ICE, DTLS and everything else succeeded) from
* the library perspective, while <b>false</b> is triggered when
* the PeerConnection goes down instead; useful to figure out when WebRTC
* is actually up and running between you and server (e.g., to notify
* a user they're actually now active in a conference).
* @param {function} callbacks.slowLink - this callback is triggered when
* server reports trouble either sending or receiving media on the
* specified PeerConnection, typically as a consequence of too many NACKs
* received from/sent to the user in the last second: for instance,
* a slowLink with uplink=true means you notified several missing packets
* from server, while uplink=false means server is not receiving all your
* packets; useful to figure out when there are problems on the media
* path (e.g., excessive loss), in order to possibly react accordingly
* (e.g., decrease the bitrate if most of our packets are getting lost).
* @param {function} callbacks.oncleanup - the WebRTC PeerConnection with
* the plugin was closed.
*/
attachVideoConferencingPlugin: function (isRemote, userId, callbacks) {
var self = this;
var remoteFeed = null;
var localStream = callbacks.localStream
delete callbacks.localStream
var displayName = callbacks.displayName
delete callbacks.displayName
this.engine.attach({
plugin: "janus.plugin.videoroom",
success: function (pluginHandle) {
if (isRemote) {
remoteFeed = pluginHandle;
remoteFeed.userId = userId;
self.remoteFeedsAttachingInProgress[userId] = remoteFeed;
// join remote's feed (listen)
var listen = {
"request": "join", "room": self.currentDialogId,
"ptype": "listener", "feed": userId, "display": displayName
};
// If the publisher is VP8 and this is Safari, let's avoid video
// if (!Utils.getEnv().reactnative && adapter.browserDetails.browser === "safari") {
// listen["offer_video"] = true;
// }
remoteFeed.send({ "message": listen });
} else {
self.videoRoomPlugin = pluginHandle;
}
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
},
consentDialog: function (on) {
if (typeof callbacks.consentDialog === 'function') {
Utils.safeCallbackCall(callbacks.consentDialog, on);
}
},
mediaState: function (medium, on) {
if (typeof callbacks.mediaState === 'function') {
Utils.safeCallbackCall(callbacks.mediaState, medium, on);
}
},
webrtcState: function (on) {
if (typeof callbacks.webrtcState === 'function') {
Utils.safeCallbackCall(callbacks.webrtcState, on);
}
},
slowLink: function (uplink, nacks) {
if (typeof callbacks.slowLink === 'function') {
Utils.safeCallbackCall(callbacks.slowLink, uplink, nacks);
}
},
iceState: function (iceConnectionState) {
if (typeof callbacks.iceState === 'function') {
Utils.safeCallbackCall(callbacks.iceState, iceConnectionState);
}
},
onmessage: function (msg, jsep) {
var event = msg["videoroom"];
// remote feed
if (isRemote) {
if (event) {
// Remote feed attached
if (event === "attached") {
var feedId = msg["id"];
self.remoteFeeds[feedId] = self.remoteFeedsAttachingInProgress[feedId];
self.remoteFeedsAttachingInProgress[feedId] = null;
} else if (msg["error"]) {
// #define VIDEOROOM_ERROR_NO_SUCH_FEED 428
//
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, msg["error"]);
}
}
}
if (jsep) {
var feedId = msg["id"];
// ICE restart case
if (!feedId) {
}
self.remoteJseps[feedId] = jsep;
self.createAnswer({
remoteFeed: self.remoteFeeds[feedId],
jsep,
stream: localStream
}, {
success: function () {
},
error: function (error) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
}
// local feed
} else {
if (event) {
// We JOINED
if (event === "joined") {
self.createOffer({ useAudio: true, stream: localStream, useVideo: !self.isOnlyAudio }, {
success: function () {
// Any new feed to attach to?
if (msg["publishers"]) {
var publishers = msg["publishers"];
for (var f in publishers) {
var userId = publishers[f]["id"];
var userDisplayName = publishers[f]["display"];
self.emitter.emit(EVENT_PARTICIPANT_JOINED, userId, userDisplayName);
}
}
},
error: function (error) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
// We JOINED and now receiving who is online
} else if (event === "event") {
// Any new feed to attach to?
if (msg["publishers"]) {
var publishers = msg["publishers"];
for (var f in publishers) {
var userId = publishers[f]["id"];
var userDisplayName = publishers[f]["display"];
self.emitter.emit(EVENT_PARTICIPANT_JOINED, userId, userDisplayName);
}
// Someone is LEAVING
} else if (msg["leaving"]) {
// One of the publishers has gone away?
var feedId = msg["leaving"];
var success = self.detachRemoteFeed(feedId);
if (success) {
self.emitter.emit(EVENT_PARTICIPANT_LEFT, feedId, null);
}
} else if (msg["unpublished"]) {
// One of the publishers has gone away?
var feedId = msg["unpublished"];
if (feedId != 'ok') {
var success = self.detachRemoteFeed(feedId);
if (success) {
self.emitter.emit(EVENT_PARTICIPANT_LEFT, feedId, null);
}
}
} else if (msg["error"]) {
// #define VIDEOROOM_ERROR_ID_EXISTS 436
// #define VIDEOROOM_ERROR_UNAUTHORIZED 433
//
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, msg["error"]);
}
}
}
}
if (jsep) {
self.videoRoomPlugin.handleRemoteJsep({ jsep: jsep });
// TODO:
// handle wrong or unsupported codecs here...
// var video = msg["video_codec"];
// if(mystream && mystream.getVideoTracks() && mystream.getVideoTracks().length > 0 && !video) {
// "Our video stream has been rejected, viewers won't see us";
// }
}
}
},
onlocalstream: function (stream) {
self.emitter.emit(EVENT_LOCAL_STREAM, stream);
},
onremotestream: function (stream) {
remoteFeed.stream = stream;
self.emitter.emit(EVENT_REMOTE_STREAM, stream, remoteFeed.userId);
},
oncleanup: function () {
console.info("ON CLEANUP");
if (typeof callbacks.oncleanup === 'function') {
Utils.safeCallbackCall(callbacks.oncleanup);
}
},
detached: function () {
}
});
},
/**
* Returns the unique plugin identifier
*
* @returns {String} unique plugin identifier or null.
*/
getPluginId: function () {
if (this.videoRoomPlugin) {
return this.videoRoomPlugin.getId();
}
return null;
},
/**
* Detach a video conferencing plugin handle.
*
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - the handle was successfully
* destroyed.
* @param {function} callbacks.error - the handle was NOT successfully
* destroyed. This callback passes single argument - text description
* of error.
*/
detachVideoConferencingPlugin: function (callbacks) {
var self = this;
var clean = function () {
self.videoRoomPlugin = null;
// detach all remote feeds
Object.keys(self.remoteFeeds).forEach(function (userId) {
self.detachRemoteFeed(userId);
});
self.remoteFeeds = [];
self.remoteJseps = [];
self.currentMidiaDeviceId = null;
};
this.videoRoomPlugin.detach({
success: function () {
clean();
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
clean();
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
},
});
},
/**
* Join video conference room
*
* @param {String} chatDialogId - a chat dialog ID to join
* @param {Number} userId - an id of current user.
* @param {Boolean} isOnlyAudio - to join current room as audio-only.
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - the chat dialog was successfully
* joined.
* @param {function} callbacks.error - the chat dialog was NOT successfully
* joined. This callback passes single argument - text description
* of error.
*/
join: function (chatDialogId, userId, isOnlyAudio, callbacks) {
var self = this;
var displayName = callbacks.displayName
delete callbacks.displayName
if (typeof (isOnlyAudio) !== "boolean") {
throw "'isOnlyAudio' parameter can be of type 'boolean' only.";
}
self.isOnlyAudio = isOnlyAudio;
// if (!Utils.getEnv().reactnative && adapter.browserDetails.browser === "safari") {
// self.isOnlyAudio = false;
// }
console.info("isOnlyAudio: " + self.isOnlyAudio);
var joinEvent = {
"request": "join", "room": chatDialogId,
"ptype": "publisher", "id": userId, "display": displayName
}; //"display": null
this.videoRoomPlugin.send({
"message": joinEvent,
success: function (resp) {
self.currentDialogId = chatDialogId;
self.currentUserId = userId;
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
},
/**
* Leave video conference room
*
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - the chat dialog was successfully
* left.
* @param {function} callbacks.error - the chat dialog was NOT successfully
* left. This callback passes single argument - text description of error.
*/
leave: function (callbacks) {
var self = this;
console.warn("leave");
if (!self.engine.isConnected()) {
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
return;
}
var leaveEvent = { "request": "leave", "room": this.currentDialogId, "id": this.currentUserId };
if (this.videoRoomPlugin) {
this.videoRoomPlugin.send({ "message": leaveEvent });
}
this.currentDialogId = null;
this.currentUserId = null;
console.warn("resp");
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
/**
* List online participants
*
* @param {String} chatDialogId - a chat dialog ID to list online
* participants in.
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - when everything is ok and you will
* receive one argument - array of online participants.
* @param {function} callbacks.error - when an error occured. This callback
* passes single argument - text description of error.
*/
listOnlineParticipants: function (chatDialogId, callbacks) {
var listRequest = { "request": "listparticipants", "room": chatDialogId };
//
this.videoRoomPlugin.send({
"message": listRequest,
success: function (data) {
var participants = [];
if (data) {
participants = data.participants;
}
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success, participants);
}
},
error: function (error) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
},
/**
* Toggle audio mute.
*
* @returns {Boolean} true if audio is muted, otherwise - false.
*/
toggleAudioMute: function () {
var muted = this.videoRoomPlugin.isAudioMuted();
if (muted) {
this.videoRoomPlugin.unmuteAudio();
} else {
this.videoRoomPlugin.muteAudio();
}
return this.videoRoomPlugin.isAudioMuted();
},
/**
* Is audio muted.
*
* @returns {Boolean} true if audio is muted, otherwise - false.
*/
isAudioMuted: function () {
return this.videoRoomPlugin.isAudioMuted();
},
/**
* Toggle remote user audio mute.
*
* @param {Number} userId - an id of user to mute audio.
*
* @returns {Boolean} true if audio is muted, otherwise - false.
*/
toggleRemoteAudioMute: function (userId) {
var remoteFeed = this.remoteFeeds[userId];
if (!remoteFeed) {
return false;
}
var audioTracks = remoteFeed.stream.getAudioTracks();
if (audioTracks && audioTracks.length > 0) {
for (var i = 0; i < audioTracks.length; ++i) {
audioTracks[i].enabled = !audioTracks[i].enabled;
}
return !audioTracks[0].enabled;
}
return false;
},
/**
* Is remote audio muted.
*
* @param {Number} userId - an id of user to check audio mute
* state.
*
* @returns {Boolean} true if audio is muted, otherwise - false.
*/
isRemoteAudioMuted: function (userId) {
var remoteFeed = this.remoteFeeds[userId];
if (!remoteFeed) {
return false;
}
var audioTracks = remoteFeed.stream.getAudioTracks();
if (audioTracks && audioTracks.length > 0) {
return !audioTracks[0].enabled;
}
return false;
},
/**
* Toggle video mute.
*
* @returns {Boolean} true if video is muted, otherwise - false.
*/
toggleVideoMute: function () {
var muted = this.videoRoomPlugin.isVideoMuted();
if (muted) {
this.videoRoomPlugin.unmuteVideo();
} else {
this.videoRoomPlugin.muteVideo();
}
return this.videoRoomPlugin.isVideoMuted();
},
/**
* Is video muted.
*
* @returns {Boolean} true if video is muted, otherwise - false.
*/
isVideoMuted: function () {
return this.videoRoomPlugin.isVideoMuted();
},
/**
* Toggle remote user video mute.
*
* @param {Number} userId - an id of user to mute video.
*
* @returns {Boolean} true if video is muted, otherwise - false.
*/
toggleRemoteVideoMute: function (userId) {
var remoteFeed = this.remoteFeeds[userId];
if (!remoteFeed) {
return false;
}
var videoTracks = remoteFeed.stream.getVideoTracks();
if (videoTracks && videoTracks.length > 0) {
for (var i = 0; i < videoTracks.length; ++i) {
videoTracks[i].enabled = !videoTracks[i].enabled;
}
return !videoTracks[0].enabled;
}
return false;
},
/**
* Is remote video muted.
*
* @param {Number} userId - an id of user to check video mute
* state.
*
* @returns {Boolean} true if video is muted, otherwise - false.
*/
isRemoteVideoMuted: function (userId) {
var remoteFeed = this.remoteFeeds[userId];
if (!remoteFeed) {
return false;
}
var videoTracks = remoteFeed.stream.getVideoTracks();
if (videoTracks && videoTracks.length > 0) {
return !videoTracks[0].enabled;
}
return false;
},
/**
* Switch video input source.
*
* @param {String} mediaDeviceId - an id of media device (camera) to switch to.
* Can be obtained via 'VideoConferencingClient.listVideoinputDevices'.
* @param {Object} callbacks - a set of callbacks to be notified about
* result, namely:<br>
* @param {function} callbacks.success - when everything is ok.
* @param {function} callbacks.error - when an error occured. This callback
* passes single argument - text description of error.
*/
switchVideoinput: function (mediaDeviceId, callbacks) {
if (!this.videoRoomPlugin) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, "No active stream");
}
return;
}
if (this.isOnlyAudio) {
throw "Can't switch video input in audio only call.";
}
this.currentMidiaDeviceId = null;
var self = this;
this.createOffer({ video: { deviceId: mediaDeviceId }, replaceVideo: true }, {
success: function () {
console.info("switchVideoinput: success");
self.currentMidiaDeviceId = mediaDeviceId;
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
console.info("switchVideoinput: error", error);
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
},
switchAudioinput: function (mediaDeviceId, callbacks) {
if (!this.videoRoomPlugin) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, "No active stream");
}
return;
}
this.createOffer({ audio: { deviceId: mediaDeviceId }, replaceAudio: true }, {
success: function () {
console.info("switchVideoinput: success");
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
console.info("switchVideoinput: error", error);
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
},
/**
* Initiate ICE restart for remote peer.
* These are typically needed whenever something in your network changes
* (e.g., you move from WiFi to mobile or a different WiFi) but want to
* keep the conversation going: in this case, an ICE restart needs to take
* place, as the peers need to exchange the new candidates they can be
* reached on.
*
* @param {Number} userIdOrCallbacks - an id of user to initiate ICE restart with or callbacks if it's a local peer.
* @param {function} callbacks.success - when everything is ok.
* @param {function} callbacks.error - when an error occured. This callback
* passes single argument - text description of error.
*/
iceRestart: function (userIdOrCallbacks, callbacks) {
// remote ICE restart
if (callbacks) {
console.info("Performing remote ICE restart for user: ", userIdOrCallbacks);
var remoteFeed = this.remoteFeeds[userIdOrCallbacks];
if (!remoteFeed) {
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, "No such user feed");
}
return;
}
var req = { "request": "configure", "restart": true };
remoteFeed.send({ "message": req });
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
// local ICE restart
} else {
console.info("Performing local ICE restart");
this.createOffer({ iceRestart: true }, {
success: function () {
if (typeof userIdOrCallbacks.success === 'function') {
Utils.safeCallbackCall(userIdOrCallbacks.success);
}
},
error: function (error) {
if (typeof userIdOrCallbacks.error === 'function') {
Utils.safeCallbackCall(userIdOrCallbacks.error, error);
}
}
});
}
},
/**
* @private
*/
createOffer: function (inputParams, callbacks) {
console.log("createOffer, inputParams: ", inputParams);
var self = this;
var useAudio = inputParams.useAudio;
var useVideo = inputParams.useVideo;
var stream = inputParams.stream;
delete inputParams.stream;
var replaceVideo = inputParams.replaceVideo;
var iceRestart = inputParams.iceRestart;
var videoQuality = self.configs.video ? self.configs.video.quality : null;
var videoFrameRate = self.configs.video ? self.configs.video.frameRate : null;
var params;
if (stream) {
params = { stream: stream };
} else if (replaceVideo) {
params = { media: inputParams };
if (videoQuality) {
params["media"]["video"] = videoQuality;
}
if (videoFrameRate) {
params["media"]["videoFrameRate"] = { min: videoFrameRate, max: videoFrameRate }
}
} else if (iceRestart) {
params = inputParams;
} else {
params = {
media: {
audioRecv: false,
videoRecv: false,
audioSend: useAudio,
videoSend: useVideo
}
}; // Publishers are sendonly
if (videoQuality) {
params["media"]["video"] = videoQuality;
}
if (videoFrameRate) {
params["media"]["videoFrameRate"] = { min: videoFrameRate, ideal: videoFrameRate }
}
}
console.info("createOffer params: ", params);
params.success = function (jsep) {
var publish = { "request": "configure" };
if (replaceVideo || iceRestart) {
// publish["update"] = true;
} else {
publish["audio"] = useAudio;
publish["video"] = useVideo;
}
console.info("createOffer publish: ", publish);
self.videoRoomPlugin.send({ "message": publish, "jsep": jsep });
if (typeof callbacks.success === 'function') {
callbacks.success();
}
};
params.error = function (error) {
console.log("[createOffer] Error in createOffer: ", error);
if (useAudio) {
self.createOffer({ useAudio: false, useVideo: false }, callbacks);
} else {
if (typeof callbacks.error === 'function') {
callbacks.error(error);
}
}
};
this.videoRoomPlugin.createOffer(params);
},
/**
* @private
*/
createAnswer: function ({ remoteFeed, jsep, stream }, callbacks) {
var self = this;
remoteFeed.createAnswer({
jsep: jsep,
stream,
media: { audioSend: false, videoSend: false }, // We want recvonly audio/video
success: function (jsep) {
var body = { "request": "start", "room": self.currentDialogId };
remoteFeed.send({ "message": body, "jsep": jsep });
if (typeof callbacks.success === 'function') {
Utils.safeCallbackCall(callbacks.success);
}
},
error: function (error) {
console.log("[createAnswer] error: ", error);
if (typeof callbacks.error === 'function') {
Utils.safeCallbackCall(callbacks.error, error);
}
}
});
},
/**
* @private
*/
detachRemoteFeed: function (userId) {
var remoteFeed = this.remoteFeeds[userId];
if (remoteFeed) {
remoteFeed.detach();
this.remoteFeeds[userId] = null;
this.remoteJseps[userId] = null;
return true;
}
return false;
},
/**
* Start show a verbose description of the user's stream bitrate.
* Refresh it every 1 second.
*
* @param {Number} userId - an id of user to gets stream bitrate.
* @param {Object} element - DOM element to display bitrate on.
*/
getUserBitrate: function (userId) {
const remoteFeed = this.remoteFeeds[userId];
return remoteFeed.getBitrate();
},
getUserVolume: function (userId) {
const remoteFeed = this.remoteFeeds[userId];
return remoteFeed.getVolume();
},
showBitrate: function (userId, element) {
var remoteFeed = this.remoteFeeds[userId];
if (!Utils.getEnv().reactnative && (adapter.browserDetails.browser === "chrome" || adapter.browserDetails.browser === "firefox")) {
this.bitrateTimers[userId] = setInterval(function () {
var bitrate = remoteFeed.getBitrate();
element.text(bitrate);
}, 1000);
}
},
/**
* Stop show a verbose description of the user's stream bitrate.
*
* @param {Number} userId - an id of user to stop show stream
* bitrate.
* @param {Object} element - DOM element to stop display bitrate on.
*/
hideBitrate: function (userId, element) {
if (this.bitrateTimers[userId]) {
clearInterval(this.bitrateTimers[userId]);
}
this.bitrateTimers[userId] = null;
element.text = null;
},
/**
* Adds a listener to be invoked when events of the specified type are
* emitted. The data arguments emitted will be passed to the listener
* function. <br>
* Possible events:
* <ul>
* <li>'participantjoined': (userId, userDisplayName)</li>
* <li>'participantleft': (userId, userDisplayName)</li>
* <li>'localstream': (stream)</li>
* <li>'remotestream': (stream, userId)</li>
* </ul>
*
* @param {String} eventType - Name of the event to listen to
* @param {function} listener - Function to invoke when the specified
* event is emitted
*/
on: function (eventType, listener) {
var token = this.emitter.addListener(eventType, listener);
},
/**
* Removes all of the registered listeners.
*
* @param {?String} eventType - Optional name of the event whose registered
* listeners to remove.
*/
removeAllListeners: function (eventType) {
if (eventType) {
this.emitter.removeAllListeners(eventType);
} else {
this.emitter.removeAllListeners();
}
}
};
module.exports = { Client: VideoConferencingClient };