06fbd764 |
/**
* Copyright (C) 2021 Double Bastion LLC
*
* This file is part of Roundpin, which is licensed under the
* GNU Affero General Public License Version 3.0. The license terms
* are detailed in the "LICENSE.txt" file located in the root directory.
*
* This is a modified version of the original file "cyber_mega_phone.js",
* first modified in 2020. The copyright notice for the original
* content follows.
*/
///////////////////////////////////////////////////////////////////////////////
// Cyber Mega Phone 2K
// Copyright (C) 2017 Digium, Inc.
//
// This program is free software, distributed under the terms of the
// MIT License. See the LICENSE file at the top of the source tree.
///////////////////////////////////////////////////////////////////////////////
'use_strict';
let isFirefox = typeof InstallTrigger !== 'undefined';
let isChrome = !!window.chrome && !!window.chrome;
let currentSession = null;
function ConferencePhone(id, name, password, host, StunServer, register, audio=true, video=true) {
EasyEvent.call(this);
this.id = id;
this.name = name;
this.password = password;
this.host = host;
this.StunServer = StunServer;
this.register = register;
this.audio = audio;
this.video = video;
this._locals = new Streams();
this._locals.bubble("streamAdded", this);
this._locals.bubble("streamRemoved", this);
this._remotes = new Streams();
this._remotes.bubble("streamAdded", this);
this._remotes.bubble("streamRemoved", this);
};
ConferencePhone.prototype = Object.create(EasyEvent.prototype);
ConferencePhone.prototype.constructor = ConferencePhone;
// This was taken from the WebRTC unified transition guide located at
// https://docs.google.com/document/d/1-ZfikoUtoJa9k-GZG1daN0BU3IjIanQ_JSscHxQesvU/edit
function isUnifiedPlanDefault() {
// Safari supports addTransceiver() but not Unified Plan when
// currentDirection is not defined.
if (!('currentDirection' in RTCRtpTransceiver.prototype))
return false;
// If Unified Plan is supported, addTransceiver() should not throw.
const tempPc = new RTCPeerConnection();
let canAddTransceiver = false;
try {
tempPc.addTransceiver('audio');
canAddTransceiver = true;
} catch (e) {
}
tempPc.close();
return canAddTransceiver;
}
ConferencePhone.prototype.connect = function () {
if (this._ua) {
this._ua.start(); // Just reconnect
return;
}
let that = this;
let socket = new JsSIP.WebSocketInterface('wss://' + this.host + ':8089/ws');
let uri = 'sip:' + this.id + '@' + this.host;
let config = {
sockets: [ socket ],
uri: uri,
contact_uri: uri,
username: this.name ? this.name : this.id,
password: this.password,
register: this.register,
register_expires : 300,
sessionDescriptionHandlerFactoryOptions: {
peerConnectionOptions : {
alwaysAcquireMediaFirst: true,
iceCheckingTimeout: 500,
rtcConfiguration: {
iceServers : [
{ urls: "stun:" + this.StunServer }
]
}
}
}
};
this._unified = isUnifiedPlanDefault();
this._ua = new JsSIP.UA(config);
function bubble (obj, name) {
obj.on(name, function (data) {
that.raise(name, data);
});
};
bubble(this._ua, 'connected');
bubble(this._ua, 'disconnected');
bubble(this._ua, 'registered');
bubble(this._ua, 'unregistered');
bubble(this._ua, 'registrationFailed');
this._ua.on('newRTCSession', function (data) {
currentSession = data.session;
let rtc = data.session;
rtc.interop = new SdpInterop.InteropChrome();
console.log('new session - ' + rtc.direction + ' - ' + rtc);
rtc.on("confirmed", function () {
// ACK was received
let streams = rtc.connection.getLocalStreams();
for (let i = 0; i < streams.length; ++i) {
console.log('confirmed: adding local stream ' + streams[i].id);
streams[i].local = true;
that._locals.add(streams[i]);
}
});
rtc.on("sdp", function (data) {
if (isFirefox && data.originator === 'remote') {
data.sdp = data.sdp.replace(/actpass/g, 'active');
} else if (isChrome && !that._unified) {
let desc = new RTCSessionDescription({type:data.type, sdp:data.sdp});
if (data.originator === 'local') {
converted = rtc.interop.toUnifiedPlan(desc);
} else {
converted = rtc.interop.toPlanB(desc);
}
data.sdp = converted.sdp;
}
});
bubble(rtc, 'muted');
bubble(rtc, 'unmuted');
bubble(rtc, 'failed');
bubble(rtc, 'ended');
rtc.connection.ontrack = function (event) {
console.log('ontrack: ' + event.track.kind + ' - ' + event.track.id +
' stream ' + event.streams[0].id);
$("#video-view"+event.streams[0].id+"").show();
if (event.track.kind == 'video') {
event.track.enabled = false;
}
for (let i = 0; i < event.streams.length; ++i) {
event.streams[i].local = false;
that._remotes.add(event.streams[i]);
}
event.track.onended = function() {
$("#video-view"+event.streams[0].id+"").hide();
};
};
rtc.connection.onremovestream = function (event) {
console.log('onremovestream: ' + event.stream.id);
that._remotes.remove(event.stream);
};
if (data.originator === "remote") {
that.raise('incoming', data.request.ruri.toAor());
}
});
this._ua.start();
};
ConferencePhone.prototype.disconnect = function () {
this._locals.removeAll();
this._remotes.removeAll();
if (this._ua) {
this._ua.stop();
}
};
ConferencePhone.prototype.answer = function () {
if (!this._ua) {
return;
}
let options = {
'mediaConstraints': { 'audio': this.audio, 'video': this.video }
};
this._rtc.answer(options);
};
ConferencePhone.prototype.call = function (exten) {
if (!this._ua || !exten) {
return;
}
let options = {
'mediaConstraints': { 'audio': this.audio, 'video': this.video }
};
if (exten.startsWith('sip:')) {
this._rtc = this._ua.call(exten, options);
} else {
this._rtc = this._ua.call('sip:' + exten + '@' + this.host, options);
}
};
ConferencePhone.prototype.terminate = function () {
this._locals.removeAll();
this._remotes.removeAll();
if (this._ua) {
this._rtc.terminate();
}
};
ConferencePhone.prototype.ShareScreen = function () {
var localStream = new MediaStream();
var pc = currentSession.connection;
var screenShareConstraints = { video: true, audio: false };
navigator.mediaDevices.getDisplayMedia(screenShareConstraints).then(function(newStream) {
var newMediaTrack = newStream.getVideoTracks()[0];
pc.getSenders().forEach(function (RTCRtpSender) {
if (RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
RTCRtpSender.replaceTrack(newMediaTrack);
localStream.addTrack(newMediaTrack);
}
});
var localVideo = $('[id*="locVideo"]').get(0);
localVideo.autoplay = true;
localVideo.srcObject = localStream;
var VidView = $('[id*="video-view"]').get(0);
VidView.append(localVideo);
$('[id*="new-media-view"]').get(0).append(VidView);
}).catch(function() { console.error(e); });
}
ConferencePhone.prototype.ShareVideo = function () {
var localStream = new MediaStream();
var pc = currentSession.connection;
var videoShareConstraints = { video: true, audio: true };
navigator.mediaDevices.getUserMedia(videoShareConstraints).then(function(newStream) {
var newMediaTrack = newStream.getVideoTracks()[0];
pc.getSenders().forEach(function (RTCRtpSender) {
if (RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
RTCRtpSender.replaceTrack(newMediaTrack);
localStream.addTrack(newMediaTrack);
}
});
var localVideo = $('[id*="locVideo"]').get(0);
localVideo.autoplay = true;
localVideo.srcObject = localStream;
var VidView = $('[id*="video-view"]').get(0);
VidView.append(localVideo);
$('[id*="new-media-view"]').get(0).append(VidView);
}).catch(function() { console.error(e); });
}
ConferencePhone.prototype.dtmfSend = function (numPressed) {
$("#dialText").val(numPressed);
var pc = currentSession.connection;
var dtmfSender = pc.getSenders()[0].dtmf;
dtmfSender.insertDTMF(numPressed);
}
///////////////////////////////////////////////////////////////////////////////
function mute(stream, options) {
function setTracks(tracks, val) {
if (!tracks) {
return;
}
for (let i = 0; i < tracks.length; ++i) {
if (tracks[i].enabled == val) {
tracks[i].enabled = !val;
}
}
};
options = options || { audio: true, video: true };
if (typeof options.audio != 'undefined') {
setTracks(stream.getAudioTracks(), options.audio);
}
if (typeof options.video != 'undefined') {
setTracks(stream.getVideoTracks(), options.video);
}
}
function unmute(stream, options) {
let opts = options || { audio: false, video: false };
mute(stream, opts);
}
///////////////////////////////////////////////////////////////////////////////
function Streams () {
EasyEvent.call(this);
this._streams = [];
};
Streams.prototype = Object.create(EasyEvent.prototype);
Streams.prototype.constructor = Streams;
Streams.prototype.add = function (stream) {
if (this._streams.indexOf(stream) == -1) {
this._streams.push(stream);
console.log('Streams: added ' + stream.id);
this.raise('streamAdded', stream);
}
};
Streams.prototype.remove = function (stream) {
let index = typeof stream == 'number' ? stream : this._streams.indexOf(stream);
if (index == -1) {
return;
}
let removed = this._streams.splice(index, 1);
for (let i = 0; i < removed.length; ++i) {
console.log('Streams: removed ' + removed[i].id);
this.raise('streamRemoved', removed[i]);
}
};
Streams.prototype.removeAll = function () {
for (let i = this._streams.length - 1; i >= 0 ; --i) {
this.remove(i);
}
};
///////////////////////////////////////////////////////////////////////////////
function EasyEvent () {
this._events = {};
};
EasyEvent.prototype.handle = function (name, fun) {
if (name in this._events) {
this._events[name].push(fun);
} else {
this._events[name] = [fun];
}
};
EasyEvent.prototype.raise = function (name) {
if (name in this._events) {
for (let i = 0; i < this._events[name].length; ++i) {
this._events[name][i].apply(this,
Array.prototype.slice.call(arguments, 1));
}
}
};
EasyEvent.prototype.bubble = function (name, obj) {
this.handle(name, function (data) {
obj.raise(name, data);
});
};
EasyEvent.prototype.raiseForEach = function (name, array) {
if (name in this._events) {
for (let i = 0; i < array.length; ++i) {
this.raise(name, array[i], i);
}
}
};
|