teoclits
Version:
Typescript angular Teonet Client module
610 lines • 23.6 kB
JavaScript
/*
* The MIT License
*
* Copyright 2017 Kirill Scherba <kirill@scherba.ru>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
/**
* Teonet client direct connect with WebRTC
*
* Version 0.0.2
*
*/
import { TeonetClientsTranslate } from './teocli.clients';
import Teocli from 'teocli/teocli';
import 'webrtc-adapter';
//type rtcmap = { key: { pc: any, channel: any, data: any } };
var TeocliRTCMap = /** @class */ (function () {
function TeocliRTCMap(t) {
this.t = t;
this.map = {};
this.cli = new TeonetClientsTranslate(this.t);
}
/**
* Create new key in map
*/
TeocliRTCMap.prototype.create = function (key, pc, channel, data) {
var _this = this;
if (channel === void 0) { channel = undefined; }
if (data === void 0) { data = {}; }
if (key) {
this.map[key] = { pc: pc, channel: channel, data: data, translate: '', type: '' };
var split_key = this.cli.splitName(key);
if (split_key.user) {
this.cli.getUserInfo(split_key.user, function (err, user) {
if (!err && _this.map[key])
_this.map[key].translate = user.username;
});
}
if (split_key.client) {
this.cli.getClientInfo(split_key.client, function (err, client) {
if (!err && _this.map[key])
_this.map[key].type = client.data.type;
});
}
}
};
/**
* Add connected channel to map
*/
TeocliRTCMap.prototype.add = function (key, channel, data) {
if (data === void 0) { data = {}; }
if (key && this.map[key] && this.map[key].pc && channel) {
var pc = this.map[key].pc;
console.log('!!! pc:', pc, 'cannel: ', channel);
this.map[key].channel = channel;
this.map[key].data = data;
}
};
/**
* Check if key exist (and connection with key established)
*/
TeocliRTCMap.prototype.exist = function (key) {
return key in this.map;
};
/**
* Check if key exist and connection with this peer established
*/
TeocliRTCMap.prototype.connected = function (key) {
return this.getChannel(key) ? true : false;
};
/**
* Get reference to data channel (connection)
*/
TeocliRTCMap.prototype.getChannel = function (key) {
var data = this.map[key];
return data ? data.channel : undefined;
};
/**
* Get reference to connection
*/
TeocliRTCMap.prototype.getConn = function (key) {
var data = this.map[key];
return data ? data.pc : undefined;
};
/**
* Delete keys channel
*/
TeocliRTCMap.prototype.deleteChannel = function (key) {
if (key && this.map[key] && this.map[key].channel) {
this.map[key].channel.close();
delete this.map[key].channel;
}
};
/**
* Delete key and all it services (channel) from the map
*/
TeocliRTCMap.prototype.delete = function (key) {
if (key && this.map[key]) {
this.deleteChannel(key); // Close and delete data cannel
if (this.map[key].pc) {
this.map[key].pc.close(); // Closes the peer connection.
delete this.map[key].pc; // Delete connection
}
delete this.map[key]; // Delete key
}
};
/**
* Delete all keys
*/
TeocliRTCMap.prototype.deleteAll = function () {
for (var key in this.map)
this.delete(key);
};
/**
* Get pointer to map
*/
TeocliRTCMap.prototype.getMap = function () {
return this.map;
};
return TeocliRTCMap;
}());
export { TeocliRTCMap };
var TeocliRTCSignalingChannel = /** @class */ (function (_super) {
__extends(TeocliRTCSignalingChannel, _super);
function TeocliRTCSignalingChannel() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.map = new TeocliRTCMap(_this);
_this.oncandidate = {};
_this.onrtcmessage = {};
return _this;
}
/**
* Start peer connection - Create new key in the RTC map
*/
TeocliRTCSignalingChannel.prototype.startConnection = function (peer, pc) {
console.log('TeocliRTCSignalingChannel:startConnection', peer, pc);
this.map.create(peer, pc);
};
/**
* Get existing connection from RTC map
*/
TeocliRTCSignalingChannel.prototype.getConnection = function (peer) {
var pc = this.map.getConn(peer);
return pc;
};
/**
* Set peer channel connected - Update key in the RTC map
*/
TeocliRTCSignalingChannel.prototype.setConnected = function (peer, channel) {
this.map.add(peer, channel);
};
/**
* Remove peer from RTC map
*/
TeocliRTCSignalingChannel.prototype.removeConnection = function (peer) {
this.map.delete(peer);
};
/**
* Send RTC signal command
*/
TeocliRTCSignalingChannel.prototype.sendRTC = function (peer, msg) {
if (peer)
_super.prototype.send.call(this, JSON.stringify({ cmd: 255, to: peer, data: msg }));
};
/**
* Get WebRTC map
* @return {any} Pointer to Teonet WebRTC map
*/
TeocliRTCSignalingChannel.prototype.getWebRTCMap = function () {
return this.map.getMap();
};
/**
* Is peer connected and is online
*/
TeocliRTCSignalingChannel.prototype.isWebRTConline = function (key) {
return this.map.connected(key);
};
/**
* Process RTC command (cmd = 255)
*/
TeocliRTCSignalingChannel.prototype.onrtc = function (err, p) {
if (!err) {
//console.log('TeocliRTCSignalingChannel::onrtc Got cmd 255');
this.onrtcmessage(p.from, p);
}
return 1;
};
/**
* Send teonet message by Teocli or WebRTC
*/
TeocliRTCSignalingChannel.prototype.send = function (data) {
var channel, retval = true;
var d = (typeof data == 'string') ? JSON.parse(data) : data;
if ((channel = this.map.getChannel(d.to))) {
d.from = this.client_name;
//console.log('TeocliRTCSignalingChannel::send :', d, 'channel:', channel);
channel.send(JSON.stringify(d)); // Send by WebRTC
}
else {
if (!this.map.exist(d.to))
this.oncandidate(d.to); // Create new RTC connection
retval = _super.prototype.send.call(this, data); // Send by Teocli
}
return retval;
};
/**
* Destroy all connections.
* Interception Send event to Event Subscribers
*
* @param {string} ev Event name
* @param {object[]) ...obj Objects send to subscribers
*/
TeocliRTCSignalingChannel.prototype.sendEvent = function (ev) {
var obj = [];
for (var _i = 1; _i < arguments.length; _i++) {
obj[_i - 1] = arguments[_i];
}
// Disconnect all channels when teonet disconnected
if (ev == 'teonet-close') {
console.log('TeocliRTCSignalingChannel::sendEvent ', 'teonet-close', obj);
this.map.deleteAll();
}
};
/**
* Convert WebRTCNap to Array
*/
TeocliRTCSignalingChannel.prototype.webrtcToAr = function (webrtc) {
if (!webrtc)
webrtc = this.getWebRTCMap();
return Object.keys(webrtc).map(function (key) {
return { key: key, data: webrtc[key] };
});
};
return TeocliRTCSignalingChannel;
}(Teocli));
export { TeocliRTCSignalingChannel };
var TeocliRTC = /** @class */ (function (_super) {
__extends(TeocliRTC, _super);
/**
* TeocliRTC class constructor
*/
function TeocliRTC(ws) {
var _this = _super.call(this, ws) || this;
// RTC configurations (ICE Servers)
_this.configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:turn.bistri.com:80', username: 'homeo', credential: 'homeo' }
]
};
// No dissconect flag when iceConnectionState equal to 'disconnected'
_this.nodisconnect_flg = false;
console.log('TeocliRTC::constructor');
_this.oncandidate = _this.connect; // Start new connection
_this.onrtcmessage = _this.processRemoteMessage; // Process remote signal message
return _this;
}
/**
* Connect to remote peer
*/
TeocliRTC.prototype.connect = function (peer) {
if (peer)
this.createConnection(peer);
};
/**
* Process remote peer RTC signal messages
*/
TeocliRTC.prototype.processRemoteMessage = function (peer, evt) {
var _this = this;
// Get connection or create new
var pc;
var message = evt.data;
// Send reset to remote peer and close current connection
var send_reset = function () {
_this.sendRTC(peer, { desc: { type: 'teocli-reset' } }); // Send reset commad to receiver
var ch = _this.map.getChannel(peer);
if (ch) {
console.log('send_reset and !!! Disconnected');
ch.close();
}
else
_this.removeConnection(peer);
};
// Reset if teocli-reset send or if teocli-start send and connection already exists
if (message.desc && message.desc.type == 'teocli-reset' ||
message.desc && message.desc.type == 'teocli-start' && this.getConnection(peer)) {
console.log('Got RTC \'' + message.desc.type + '\' signal from peer: ' + peer + ', remove existing connection');
//this.removeConnection(peer);
//this.map[peer].close();
//delete this.map[peer]; // Delete key (drop connection)
//pc = this.getConnection(peer);
//this.map.getChannel(peer).close();
//delete this.map.getMap()[peer];
}
// Get existing connection or create new one
pc = this.getConnection(peer); // || this.createConnection(peer, false);
// Process messages
if (message.desc) {
var desc = message.desc;
// teocli message to create connection
if (desc.type == 'teocli-start' || desc.type == 'teocli-reset') {
// Create connection
console.log('Got RTC ' + desc.type + ' signal from:', peer);
this.createConnection(peer, false);
}
else if (!pc) {
console.error('pc not created yet', peer);
}
else if (desc.type == 'teocli-call') {
// \TODO Used in WebCamera mode to add local stream to pc connection.
// What do with audioSender and videoSender
console.log('Got RTC teocli-call signal from peer: ' + peer + ', signal:', message);
//if (this.localStream) {
// this.addStream(pc, this.localStream);
//}
}
else if (desc.type == 'offer') {
console.log('Got offer', message);
this.nodisconnect_flg = false;
pc.setRemoteDescription(desc)
.then(function () {
return pc.createAnswer();
})
.then(function (answer) {
return pc.setLocalDescription(answer);
})
.then(function () {
//var str = JSON.stringify({ desc: pc.localDescription });
_this.sendRTC(peer, { desc: pc.localDescription });
})
.catch(function (err) {
send_reset();
_this.logError(err);
});
}
else {
console.log('Got ' + message.desc.type + ': ', message);
pc.setRemoteDescription(desc).catch(this.logError);
}
}
else if (pc) {
console.log('Got ice candidate', message);
pc.addIceCandidate(message.candidate).catch(function (err) {
send_reset();
_this.logError(err);
});
}
else {
console.error('pc not created yet', peer);
}
};
/**
* Create connection between this peer and remote peer
*
* @param {string} peer Remote peer name
*/
TeocliRTC.prototype.createConnection = function (peer, isInitiator) {
var _this = this;
if (isInitiator === void 0) { isInitiator = true; }
var channel;
var lastTime = 0;
var t = 0;
var WAIT_NEXT_NEGOTIATION = 50;
var pc = new RTCPeerConnection(this.configuration);
console.log('TeocliRTC::createConnection to:', peer, 'initiator:', isInitiator);
this.startConnection(peer, pc); // Add RTCPeerConnection to RTC map
if (isInitiator) {
console.log('Send RTC teocli-start signal to:', peer, 'initiator', isInitiator);
this.sendRTC(peer, { desc: { type: 'teocli-start' } }); // Send start commad to receiver
}
// send any ice candidates to the other peer
pc.onicecandidate = function (evt) {
//console.log('TeocliRTC::createConnection onicecandidate', evt);
_this.sendRTC(peer, { candidate: evt.candidate });
};
// when remote peer disocnnected
pc.oniceconnectionstatechange = function (event) {
console.log('oniceconnectionstatechange:', pc.iceConnectionState, ' event:', event);
if (pc.iceConnectionState == 'disconnected') {
//\TODO Close data channel
// This event happends when remote data channel is disconnected
// (We processed this even and close local channel).
//
// Or it happends when remote media track added to this RTC connection,
// to stop this event we used nodisconnect_flg: boolean which we set
// to true when pc.ontrack event hapends.
var ch = void 0;
if (!_this.nodisconnect_flg && (ch = _this.map.getChannel(peer))) {
console.log('oniceconnectionstatechange !!! Disconnected - close channel');
ch.close();
//this.removeConnection(peer);
}
}
if (pc.iceConnectionState == 'failed') {
console.log('oniceconnectionstatechange !!! failed');
_this.removeConnection(peer); // Close WebRTC connection
}
_this.nodisconnect_flg = false;
};
// let the "negotiationneeded" event trigger offer generation
pc.onnegotiationneeded = function () {
console.log('createConnection::onnegotiationneeded', pc);
// Generate and Send only last offer during 50 ms
// The onnegotiationneeded fire every time when we add or delete streams
// tracks. And we will wait all tracks added or deleted and than send one
// offer to remote
var currentTime = new Date().getTime();
var createOffer = function () {
console.log('createConnection::onnegotiationneeded createOffer', pc);
_this.nodisconnect_flg = true; // Set no disconnect flag
pc.createOffer()
.then(function (offer) {
return pc.setLocalDescription(offer);
})
.then(function () {
// Send the offer to the other peer
_this.sendRTC(peer, { desc: pc.localDescription });
})
.catch(_this.logError);
};
if (t && currentTime - lastTime < WAIT_NEXT_NEGOTIATION)
clearTimeout(t);
t = setTimeout(createOffer, WAIT_NEXT_NEGOTIATION);
lastTime = currentTime;
};
// When remote peer connect his media
pc.ontrack = function (e) {
console.log('pc.ontrack', e);
_this.nodisconnect_flg = true; // Set no disconnect flag
// Connect remote stream to the remoteVideo control
if (_this.callAnswer)
_this.callAnswer(peer, e.streams[0]);
};
// Process created channel
var processChannel = function (channel) {
// When channe lopen
channel.onopen = function () {
console.log('TeocliRTC::createConnection processChannel channel.onopen', 'Connected to:', peer);
_this.setConnected(peer, channel);
};
// When message received
channel.onmessage = function (event) {
//console.log('TeocliRTC::createConnection:onmesage', peer, event);
_this.process(event.data);
};
// When channel close
channel.onclose = function () {
console.log('TeocliRTC::createConnection:onclose', peer);
_this.removeConnection(peer);
//this.map.deleteChannel(peer);
if (_this.callAnswer)
_this.callAnswer(peer);
_this.nodisconnect_flg = false;
};
// When channel error
channel.onerror = function () {
console.log('TeocliRTC::createConnection:onerror', peer);
};
};
// Create chanel if this peer is initiator
if (isInitiator) {
channel = pc.createDataChannel('teocli-rtc', {
maxRetransmitTime: 3000 // milliseconds
});
//console.log('TeocliRTC::createConnection (createDataChannel) to', peer, channel);
processChannel(channel);
}
else {
pc.ondatachannel = function (event) {
channel = event.channel;
//console.log('TeocliRTC::createConnection (ondatachannel) to', peer, channel);
processChannel(channel);
};
}
return pc;
};
/**
* Add stream to RTCConnection
*
* @param {any} Reference to RTCPeerConnection
* @param {any} Stream to add to connection
* @param {any} Options with tracks type (video or audio) to add. If options
* is ommited than all strean tracks will be added
*/
TeocliRTC.prototype.addStream = function (pc, stream, options) {
// Add tracks selected in options
if (!options) {
stream.getTracks().forEach(function (track) {
pc.addTrack(track, stream);
});
}
else {
if (options.video)
pc.addTrack(stream.getVideoTracks()[0], stream);
if (options.audio)
pc.addTrack(stream.getAudioTracks()[0], stream);
}
console.log('Stream added to pc');
};
/**
* Show error
*
* @param {name: string, nessage: string} error Error to show in console
*/
TeocliRTC.prototype.logError = function (error) {
console.error('TeocliRTC::logError', error.name + ": " + error.message +
', (error: ', error, ')');
//this.removeConnection(peer);
};
/**
* Make Call to remote peer
*
* @param {string} peer Peer name
* @param {CallOptionsType} options Options:
* video - reqiest video,
* audio - request audio,
* chat - request chat channel
*
* @return {boolean} true if call send to remotepeer;
* false if peer name empty,
* or peer not connected to this host,
* or wrong options send (all: video, audio and
* channel is false)
*/
TeocliRTC.prototype.call = function (peer, localStream, options) {
var _this = this;
if (options === void 0) { options = { video: true, audio: true, chat: false }; }
// Check option and return false if all options lalse
if (!(options.video || options.audio || options.chat))
return false;
var pc = this.getConnection(peer);
if (pc) {
// Add local tracks to RTC connection
if (localStream) {
this.addStream(pc, localStream, options);
pc.onremovestream = function () {
if (_this.callAnswer)
_this.callAnswer(peer);
console.log('onremovestream', peer);
_this.removeTracks(peer);
};
}
// \TODO Create Chat data channel
if (options.chat) { }
return true;
}
return false;
};
/**
* Hangup call
*/
TeocliRTC.prototype.hangup = function (peer) {
this.removeTracks(peer);
};
/**
* Register remote call answer
*/
TeocliRTC.prototype.registerCallAnswer = function (callAnswer) {
this.callAnswer = callAnswer;
};
/**
* Remove all senders (all senders media tracks) from connection
*/
TeocliRTC.prototype.removeTracks = function (peer) {
var pc = this.getConnection(peer);
if (pc) {
[/*pc.getReceivers(), */ pc.getSenders()].forEach(function (senderss) {
senderss.forEach(function (sender) {
pc.removeTrack(sender);
});
});
}
};
/**
* Unregister remote call answer
*/
TeocliRTC.prototype.unRegisterCallAnswer = function () {
this.callAnswer = undefined;
};
return TeocliRTC;
}(TeocliRTCSignalingChannel));
export { TeocliRTC };
//# sourceMappingURL=teocli.webrtc.js.map