cf74ca961c
Fixes #4900
707 lines
19 KiB
JavaScript
707 lines
19 KiB
JavaScript
Verto = function (
|
|
tag,
|
|
voiceBridge,
|
|
conferenceUsername,
|
|
userCallback,
|
|
onFail = null,
|
|
chromeExtension = null,
|
|
stunTurnInfo = null,
|
|
loadingCallback = null) {
|
|
|
|
voiceBridge += "-SCREENSHARE";
|
|
this.cur_call = null;
|
|
this.share_call = null;
|
|
this.vertoHandle;
|
|
|
|
this.vid_width = Math.max(window.screen.width, 1920);
|
|
this.vid_height = Math.max(window.screen.height, 1080);
|
|
|
|
this.outgoingBandwidth = "default";
|
|
this.incomingBandwidth = "default";
|
|
// this.sessid = $.verto.genUUID();
|
|
this.sessid = Math.random().toString();
|
|
|
|
this.renderTag = 'remote-media';
|
|
|
|
this.destination_number = voiceBridge;
|
|
this.caller_id_name = conferenceUsername;
|
|
this.caller_id_number = conferenceUsername;
|
|
|
|
this.vertoPort = "verto";
|
|
this.hostName = window.location.hostname;
|
|
this.socketUrl = 'wss://' + this.hostName + '/' + this.vertoPort;
|
|
this.login = "bbbuser";
|
|
this.password = "secret";
|
|
|
|
this.useVideo = false;
|
|
this.useCamera = false;
|
|
this.useMic = false;
|
|
|
|
this.callWasSuccessful = false;
|
|
this.shouldConnect = true;
|
|
this.iceServers = stunTurnInfo;
|
|
this.userCallback = userCallback;
|
|
|
|
if (loadingCallback != null) {
|
|
this.videoLoadingCallback = Verto.normalizeCallback(loadingCallback);
|
|
} else {
|
|
this.videoLoadingCallback = function() {};
|
|
}
|
|
|
|
if (chromeExtension != null) {
|
|
this.chromeExtension = chromeExtension;
|
|
}
|
|
|
|
if (onFail != null) {
|
|
this.onFail = Verto.normalizeCallback(onFail);
|
|
} else {
|
|
var _this = this;
|
|
this.onFail = function () {
|
|
_this.logError('Default error handler');
|
|
};
|
|
}
|
|
};
|
|
|
|
Verto.prototype.logger = function (obj) {
|
|
console.log(obj);
|
|
};
|
|
|
|
Verto.prototype.logError = function (obj) {
|
|
console.error(obj);
|
|
};
|
|
|
|
Verto.prototype.setRenderTag = function (tag) {
|
|
this.renderTag = tag;
|
|
};
|
|
|
|
// receives either a string variable holding the name of an actionscript
|
|
// registered callback, or a javascript function object.
|
|
// The function will return either the function if it is a javascript Function
|
|
// or if it is an actionscript string it will return a javascript Function
|
|
// that when invokved will invoke the actionscript registered callback
|
|
// and passes along parameters
|
|
Verto.normalizeCallback = function (callback) {
|
|
if (typeof callback == 'function') {
|
|
return callback;
|
|
} else {
|
|
return function (args) {
|
|
document.getElementById('BigBlueButton')[callback](args);
|
|
};
|
|
}
|
|
};
|
|
|
|
Verto.prototype.onWSLogin = function (v, success) {
|
|
this.cur_call = null;
|
|
if (success) {
|
|
if (!this.shouldConnect) {
|
|
return;
|
|
}
|
|
|
|
this.callWasSuccessful = true;
|
|
this.mediaCallback();
|
|
return;
|
|
} else {
|
|
// error logging verto into freeswitch
|
|
this.logError({ status: 'failed', errorcode: '10XX' });
|
|
this.callWasSuccessful = false;
|
|
this.onFail();
|
|
return;
|
|
}
|
|
};
|
|
|
|
Verto.prototype.registerCallbacks = function () {
|
|
var callbacks = {
|
|
onMessage: function (verto, dialog, msg, data) {
|
|
|
|
switch (msg) {
|
|
case $.verto.enum.message.pvtEvent:
|
|
if (data.pvtData) {
|
|
switch (data.pvtData.action) {
|
|
// This client has joined the live array for the conference.
|
|
case "conference-liveArray-join":
|
|
initLiveArray(verto, dialog, data);
|
|
break;
|
|
// This client has left the live array for the conference.
|
|
case "conference-liveArray-part":
|
|
// Some kind of client-side wrapup...
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
},
|
|
|
|
onDialogState: function (d) {},
|
|
|
|
onWSLogin: this.onWSLogin.bind(this),
|
|
|
|
onWSClose: function (v, success) {
|
|
cur_call = null;
|
|
if (this.callWasSuccessful) {
|
|
// the connection was dropped in an already established call
|
|
this.logError('websocket disconnected');
|
|
|
|
// WebSocket disconnected
|
|
this.logError({ status: 'failed', errorcode: 1001 });
|
|
toDisplayDisconnectCallback = false;
|
|
} else {
|
|
// this callback was triggered and a call was never successfully established
|
|
this.logError('websocket connection could not be established');
|
|
|
|
// Could not make a WebSocket connection
|
|
this.logError({ status: 'failed', errorcode: 1002 });
|
|
this.onFail();
|
|
return;
|
|
}
|
|
}.bind(this),
|
|
};
|
|
this.callbacks = callbacks;
|
|
};
|
|
|
|
var initLiveArray = function(verto, dialog, data) {
|
|
// Set up addtional configuration specific to the call.
|
|
window.vertoConf = new $.verto.conf(verto, {
|
|
dialog: dialog,
|
|
hasVid: true,
|
|
laData: data.pvtData,
|
|
// For subscribing to published chat messages.
|
|
chatCallback: function(verto, eventObj) {
|
|
var from = eventObj.data.fromDisplay || eventObj.data.from || 'Unknown';
|
|
var message = eventObj.data.message || '';
|
|
},
|
|
});
|
|
var config = {
|
|
subParams: {
|
|
callID: dialog ? dialog.callID : null
|
|
},
|
|
};
|
|
// Set up the live array, using the live array data received from FreeSWITCH.
|
|
window.liveArray = new $.verto.liveArray(window.vertoHandle, data.pvtData.laChannel, data.pvtData.laName, config);
|
|
// Subscribe to live array changes.
|
|
window.liveArray.onChange = function(liveArrayObj, args) {
|
|
console.log("Call UUID is: " + args.key);
|
|
console.log("Call data is: ", args.data);
|
|
|
|
console.log(liveArrayObj);
|
|
console.log(args);
|
|
|
|
try {
|
|
switch (args.action) {
|
|
|
|
// Initial list of existing conference users.
|
|
case "bootObj":
|
|
break;
|
|
|
|
// New user joined conference.
|
|
case "add":
|
|
break;
|
|
|
|
// User left conference.
|
|
case "del":
|
|
break;
|
|
|
|
// Existing user's state changed (mute/unmute, talking, floor, etc)
|
|
case "modify":
|
|
break;
|
|
}
|
|
} catch (err) {
|
|
console.error("ERROR: " + err);
|
|
}
|
|
};
|
|
// Called if the live array throws an error.
|
|
window.liveArray.onErr = function (obj, args) {
|
|
console.error("Error: ", obj, args);
|
|
};
|
|
};
|
|
|
|
Verto.prototype.hold = function () {
|
|
this.cur_call.toggleHold();
|
|
};
|
|
|
|
Verto.prototype.hangup = function () {
|
|
if (this.cur_call) {
|
|
// the duration of the call
|
|
if (this.cur_call.audioStream) {
|
|
this.logger('call ended ' + this.cur_call.audioStream.currentTime);
|
|
}
|
|
|
|
this.cur_call.hangup();
|
|
this.cur_call = null;
|
|
}
|
|
|
|
if (this.share_call) {
|
|
if (this.share_call.state == $.verto.enum.state.active) {
|
|
this.shouldConnect = false;
|
|
} else {
|
|
this.shouldConnect = true;
|
|
}
|
|
|
|
// the duration of the call
|
|
this.logger('call ended ' + this.share_call.audioStream.currentTime);
|
|
this.share_call.rtc.localStream.getTracks().forEach(track => track.stop());
|
|
this.share_call.hangup();
|
|
this.share_call = null;
|
|
}
|
|
|
|
// the user ended the call themself
|
|
// if (callPurposefullyEnded === true) {
|
|
if (true) {
|
|
this.logger({ status: 'ended' });
|
|
} else {
|
|
// Call ended unexpectedly
|
|
this.logError({ status: 'failed', errorcode: 1005 });
|
|
}
|
|
};
|
|
|
|
Verto.prototype.mute = function () {
|
|
this.cur_call.dtmf('0');
|
|
};
|
|
|
|
Verto.prototype.localmute = function () {
|
|
// var muted = cur_call.setMute('toggle');
|
|
// if (muted) {
|
|
// display('Talking to: ' + cur_call.cidString() + ' [LOCALLY MUTED]');
|
|
// } else {
|
|
// display('Talking to: ' + cur_call.cidString());
|
|
// }
|
|
};
|
|
|
|
Verto.prototype.localvidmute = function () {
|
|
// var muted = cur_call.setVideoMute('toggle');
|
|
// if (muted) {
|
|
// display('Talking to: ' + cur_call.cidString() + ' [VIDEO LOCALLY MUTED]');
|
|
// } else {
|
|
// display('Talking to: ' + cur_call.cidString());
|
|
// }
|
|
};
|
|
|
|
Verto.prototype.vmute = function () {
|
|
this.cur_call.dtmf('*0');
|
|
};
|
|
|
|
Verto.prototype.setWatchVideo = function (tag) {
|
|
this.mediaCallback = this.docall;
|
|
this.useVideo = true;
|
|
this.useCamera = 'none';
|
|
this.useMic = 'none';
|
|
this.create(tag);
|
|
};
|
|
|
|
Verto.prototype.setListenOnly = function (tag) {
|
|
this.mediaCallback = this.docall;
|
|
this.useVideo = false;
|
|
this.useCamera = 'none';
|
|
this.useMic = 'none';
|
|
this.create(tag);
|
|
};
|
|
|
|
Verto.prototype.setMicrophone = function (tag) {
|
|
this.mediaCallback = this.docall;
|
|
this.useVideo = false;
|
|
this.useCamera = 'none';
|
|
this.useMic = 'any';
|
|
this.create(tag);
|
|
};
|
|
|
|
Verto.prototype.setScreenShare = function (tag) {
|
|
// required for Verto to know we want to use video
|
|
// tell Verto we want to share webcam so it knows there will be a video stream
|
|
// but instead of a webcam we pass screen constraints
|
|
this.useCamera = 'any';
|
|
this.useMic = 'none';
|
|
this.mediaCallback = this.makeShare;
|
|
this.create(tag);
|
|
};
|
|
|
|
Verto.prototype.create = function (tag) {
|
|
this.setRenderTag(tag);
|
|
this.registerCallbacks();
|
|
|
|
// fetch ice information from server
|
|
if (this.iceServers == null) {
|
|
this.configStuns(this.init);
|
|
} else {
|
|
// already have it. proceed with init
|
|
this.init();
|
|
}
|
|
};
|
|
|
|
Verto.prototype.docall = function () {
|
|
if (this.cur_call) {
|
|
this.logger('Quitting: Call already in progress');
|
|
return;
|
|
}
|
|
|
|
this.shouldConnect = true;
|
|
|
|
this.cur_call = window.vertoHandle.newCall({
|
|
destination_number: this.destination_number,
|
|
caller_id_name: this.caller_id_name,
|
|
caller_id_number: this.caller_id_number,
|
|
outgoingBandwidth: this.outgoingBandwidth,
|
|
incomingBandwidth: this.incomingBandwidth,
|
|
useVideo: this.useVideo,
|
|
useStereo: true,
|
|
useCamera: this.useCamera,
|
|
useMic: this.useMic,
|
|
useSpeak: 'any',
|
|
dedEnc: true,
|
|
tag: this.renderTag,
|
|
});
|
|
this.logger(this.cur_call);
|
|
};
|
|
|
|
Verto.prototype.makeShare = function () {
|
|
if (this.share_call) {
|
|
this.logError('Quitting: Call already in progress');
|
|
return;
|
|
}
|
|
|
|
var screenInfo = null;
|
|
if (!!navigator.mozGetUserMedia) {
|
|
// no screen parameters for FF, just screenShare: true down below
|
|
screenInfo = {};
|
|
this.doShare(screenInfo);
|
|
} else if (!!window.chrome) {
|
|
var _this = this;
|
|
if (!_this.chromeExtension) {
|
|
_this.logError({
|
|
status: 'failed',
|
|
message: 'Missing Chrome Extension key',
|
|
});
|
|
_this.onFail();
|
|
return;
|
|
}
|
|
|
|
// bring up Chrome screen picker
|
|
getMyScreenConstraints(function (constraints) {
|
|
if (constraints == null || constraints == "" || constraints.streamId == null || constraints.streamId == "") {
|
|
_this.onFail();
|
|
return _this.logError(constraints);
|
|
}
|
|
|
|
screenInfo = {
|
|
chromeMediaSource: "desktop",
|
|
chromeMediaSourceId: constraints.streamId,
|
|
};
|
|
|
|
_this.logger(screenInfo);
|
|
_this.doShare(screenInfo);
|
|
}, _this.chromeExtension);
|
|
}
|
|
};
|
|
|
|
Verto.prototype.doShare = function (screenConstraints) {
|
|
this.shouldConnect = true;
|
|
screenConstraints.maxWidth = this.vid_width;
|
|
screenConstraints.maxHeight = this.vid_height;
|
|
|
|
this.share_call = window.vertoHandle.newCall({
|
|
destination_number: this.destination_number,
|
|
caller_id_name: this.caller_id_name,
|
|
caller_id_number: this.caller_id_number,
|
|
outgoingBandwidth: "default",
|
|
incomingBandwidth: "default",
|
|
videoParams: screenConstraints,
|
|
useVideo: true,
|
|
screenShare: true,
|
|
dedEnc: true,
|
|
mirrorInput: false,
|
|
tag: this.renderTag,
|
|
});
|
|
|
|
var stopSharing = function() {
|
|
console.log("stopSharing");
|
|
this.share_call.hangup();
|
|
this.share_call = null;
|
|
};
|
|
|
|
var _this = this;
|
|
// Override onStream callback in $.FSRTC instance
|
|
this.share_call.rtc.options.callbacks.onStream = function (rtc, stream) {
|
|
_this.videoLoadingCallback();
|
|
|
|
if (stream) {
|
|
var StreamTrack = stream.getVideoTracks()[0];
|
|
StreamTrack.addEventListener('ended', stopSharing.bind(_this));
|
|
}
|
|
};
|
|
};
|
|
|
|
Verto.prototype.init = function () {
|
|
this.cur_call = null;
|
|
|
|
if (!window.vertoHandle) {
|
|
window.vertoHandle = new $.verto({
|
|
useVideo: true,
|
|
login: this.login,
|
|
passwd: this.password,
|
|
socketUrl: this.socketUrl,
|
|
tag: this.renderTag,
|
|
ringFile: 'sounds/bell_ring2.wav',
|
|
sessid: this.sessid,
|
|
videoParams: {
|
|
minFrameRate: 15,
|
|
vertoBestFrameRate: 30,
|
|
},
|
|
|
|
deviceParams: {
|
|
useCamera: false,
|
|
useMic: false,
|
|
useSpeak: 'none',
|
|
},
|
|
|
|
audioParams: {
|
|
googAutoGainControl: false,
|
|
googNoiseSuppression: false,
|
|
googHighpassFilter: false,
|
|
},
|
|
|
|
iceServers: this.iceServers,
|
|
}, this.callbacks);
|
|
} else {
|
|
this.mediaCallback();
|
|
return;
|
|
}
|
|
};
|
|
|
|
Verto.prototype.configStuns = function (callback) {
|
|
this.logger('Fetching STUN/TURN server info for Verto initialization');
|
|
var _this = this;
|
|
var stunsConfig = {};
|
|
|
|
// flash client has api access. html5 user passes array.
|
|
// client provided no stuns and cannot make api calls
|
|
// use defaults in verto and try making a call
|
|
if (BBB.getSessionToken == undefined) {
|
|
// uses defaults
|
|
this.iceServers = true;
|
|
// run init callback
|
|
return callback();
|
|
}
|
|
|
|
// TODO: screensharing and audio use this exact same function. Should be
|
|
// moved to a shared library for retrieving stun/turn and just pass
|
|
// success/fail callbacks
|
|
BBB.getSessionToken(function(sessionToken) {
|
|
$.ajax({
|
|
dataType: 'json',
|
|
url: '/bigbluebutton/api/stuns/',
|
|
data: {sessionToken},
|
|
}).done(function (data) {
|
|
_this.logger('ajax request done');
|
|
_this.logger(data);
|
|
if (data.response && data.response.returncode == 'FAILED') {
|
|
_this.logError(data.response.message, { error: true });
|
|
_this.logError({ status: 'failed', errorcode: data.response.message });
|
|
return;
|
|
}
|
|
|
|
stunsConfig.stunServers = (data.stunServers ? data.stunServers.map(function (data) {
|
|
return { url: data.url };
|
|
}) : []);
|
|
|
|
stunsConfig.turnServers = (data.turnServers ? data.turnServers.map(function (data) {
|
|
return {
|
|
urls: data.url,
|
|
username: data.username,
|
|
credential: data.password,
|
|
};
|
|
}) : []);
|
|
|
|
stunsConfig = stunsConfig.stunServers.concat(stunsConfig.turnServers);
|
|
_this.logger('success got stun data, making verto');
|
|
_this.iceServers = stunsConfig;
|
|
callback.apply(_this);
|
|
}).fail(function (data, textStatus, errorThrown) {
|
|
_this.logError({ status: 'failed', errorcode: 1009 });
|
|
_this.onFail();
|
|
return;
|
|
});
|
|
});
|
|
};
|
|
|
|
// checks whether Google Chrome or Firefox have the WebRTCPeerConnection object
|
|
Verto.prototype.isWebRTCAvailable = function () {
|
|
return (typeof window.webkitRTCPeerConnection !== 'undefined' ||
|
|
typeof window.mozRTCPeerConnection !== 'undefined');
|
|
};
|
|
|
|
this.VertoManager = function () {
|
|
this.vertoAudio = null;
|
|
this.vertoVideo = null;
|
|
this.vertoScreenShare = null;
|
|
window.vertoHandle = null;
|
|
};
|
|
|
|
Verto.prototype.logout = function () {
|
|
this.exitAudio();
|
|
this.exitVideo();
|
|
this.exitScreenShare();
|
|
window.vertoHandle.logout();
|
|
};
|
|
|
|
VertoManager.prototype.exitAudio = function () {
|
|
if (this.vertoAudio != null) {
|
|
console.log('Hanging up vertoAudio');
|
|
this.vertoAudio.hangup();
|
|
}
|
|
};
|
|
|
|
VertoManager.prototype.exitVideo = function () {
|
|
if (this.vertoVideo != null) {
|
|
console.log('Hanging up vertoVideo');
|
|
this.vertoVideo.hangup();
|
|
}
|
|
};
|
|
|
|
VertoManager.prototype.exitScreenShare = function () {
|
|
if (this.vertoScreenShare != null) {
|
|
console.log('Hanging up vertoScreenShare');
|
|
this.vertoScreenShare.hangup();
|
|
}
|
|
};
|
|
|
|
VertoManager.prototype.joinListenOnly = function (tag) {
|
|
this.exitAudio();
|
|
|
|
if (this.vertoAudio == null) {
|
|
var obj = Object.create(Verto.prototype);
|
|
Verto.apply(obj, arguments);
|
|
this.vertoAudio = obj;
|
|
}
|
|
|
|
this.vertoAudio.setListenOnly(tag);
|
|
};
|
|
|
|
VertoManager.prototype.joinMicrophone = function (tag) {
|
|
this.exitAudio();
|
|
|
|
if (this.vertoAudio == null) {
|
|
var obj = Object.create(Verto.prototype);
|
|
Verto.apply(obj, arguments);
|
|
this.vertoAudio = obj;
|
|
}
|
|
|
|
this.vertoAudio.setMicrophone(tag);
|
|
};
|
|
|
|
VertoManager.prototype.joinWatchVideo = function (tag) {
|
|
this.exitVideo();
|
|
|
|
if (this.vertoVideo == null) {
|
|
var obj = Object.create(Verto.prototype);
|
|
Verto.apply(obj, arguments);
|
|
this.vertoVideo = obj;
|
|
}
|
|
|
|
this.vertoVideo.setWatchVideo(tag);
|
|
};
|
|
|
|
VertoManager.prototype.shareScreen = function (tag) {
|
|
this.exitScreenShare();
|
|
|
|
if (this.vertoScreenShare == null) {
|
|
var obj = Object.create(Verto.prototype);
|
|
Verto.apply(obj, arguments);
|
|
this.vertoScreenShare = obj;
|
|
}
|
|
|
|
this.vertoScreenShare.setScreenShare(tag);
|
|
};
|
|
|
|
window.vertoInitialize = function () {
|
|
if (window.vertoManager == null || window.vertoManager == undefined) {
|
|
window.vertoManager = new VertoManager();
|
|
}
|
|
};
|
|
|
|
window.vertoExitAudio = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.exitAudio();
|
|
};
|
|
|
|
window.vertoExitVideo = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.exitVideo();
|
|
};
|
|
|
|
window.vertoExitScreenShare = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.exitScreenShare();
|
|
};
|
|
|
|
window.vertoJoinListenOnly = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.joinListenOnly.apply(window.vertoManager, arguments);
|
|
};
|
|
|
|
window.vertoJoinMicrophone = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.joinMicrophone.apply(window.vertoManager, arguments);
|
|
};
|
|
|
|
window.vertoWatchVideo = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.joinWatchVideo.apply(window.vertoManager, arguments);
|
|
};
|
|
|
|
window.vertoShareScreen = function () {
|
|
window.vertoInitialize();
|
|
window.vertoManager.shareScreen.apply(window.vertoManager, arguments);
|
|
};
|
|
|
|
// a function to check whether the browser (Chrome only) is in an isIncognito
|
|
// session. Requires 1 mandatory callback that only gets called if the browser
|
|
// session is incognito. The callback for not being incognito is optional.
|
|
// Attempts to retrieve the chrome filesystem API.
|
|
window.checkIfIncognito = function(isIncognito, isNotIncognito = function () {}) {
|
|
isIncognito = Verto.normalizeCallback(isIncognito);
|
|
isNotIncognito = Verto.normalizeCallback(isNotIncognito);
|
|
|
|
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
|
|
if (!fs) {
|
|
isNotIncognito();
|
|
return;
|
|
}
|
|
fs(window.TEMPORARY, 100, function(){isNotIncognito()}, function(){isIncognito()});
|
|
};
|
|
|
|
window.checkChromeExtInstalled = function (callback, chromeExtensionId) {
|
|
callback = Verto.normalizeCallback(callback);
|
|
|
|
if (typeof chrome === "undefined" || !chrome || !chrome.runtime) {
|
|
// No API, so no extension for sure
|
|
callback(false);
|
|
return;
|
|
}
|
|
chrome.runtime.sendMessage(
|
|
chromeExtensionId,
|
|
{ getVersion: true },
|
|
function (response) {
|
|
if (!response || !response.version) {
|
|
// Communication failure - assume that no endpoint exists
|
|
callback(false);
|
|
return;
|
|
}
|
|
callback(true);
|
|
}
|
|
);
|
|
}
|
|
|
|
window.getMyScreenConstraints = function(theCallback, extensionId) {
|
|
theCallback = Verto.normalizeCallback(theCallback);
|
|
chrome.runtime.sendMessage(extensionId, {
|
|
getStream: true,
|
|
sources: [
|
|
"window",
|
|
"screen",
|
|
"tab"
|
|
]},
|
|
function(response) {
|
|
console.log(response);
|
|
theCallback(response);
|
|
});
|
|
};
|