openvidu/openvidu-browser/ts/OpenViduInternal/SessionInternal.ts

571 lines
18 KiB
TypeScript

import { Stream } from './Stream';
import { OpenViduInternal } from './OpenViduInternal';
import { Connection, ConnectionOptions } from './Connection';
import EventEmitter = require('wolfy87-eventemitter');
const SECRET_PARAM = '?secret=';
export interface SessionOptions {
sessionId: string;
participantId: string;
metadata: string;
subscribeToStreams?: boolean;
updateSpeakerInterval?: number;
thresholdSpeaker?: number;
}
export class SessionInternal {
private id: string;
private sessionId: string;
private ee = new EventEmitter();
private streams = {};
private participants = {};
private participantsSpeaking: Connection[] = [];
private connected = false;
public localParticipant: Connection;
private subscribeToStreams: boolean;
private updateSpeakerInterval: number;
public thresholdSpeaker: number;
private options: SessionOptions;
constructor(private openVidu: OpenViduInternal, sessionId: string) {
this.sessionId = this.getUrlWithoutSecret(sessionId);
this.localParticipant = new Connection(this.openVidu, true, this);
if (!this.openVidu.getWsUri()) {
this.processOpenViduUrl(sessionId);
}
}
private processOpenViduUrl(url: string) {
this.openVidu.setSecret(this.getSecretFromUrl(url));
this.openVidu.setWsUri(this.getFinalUrl(url));
}
private getSecretFromUrl(url: string): string {
let secret = '';
if (url.indexOf(SECRET_PARAM) !== -1) {
secret = url.substring(url.lastIndexOf(SECRET_PARAM) + SECRET_PARAM.length, url.length);
}
return secret;
}
private getUrlWithoutSecret(url: string): string {
if (url.indexOf(SECRET_PARAM) !== -1) {
url = url.substring(0, url.lastIndexOf(SECRET_PARAM));
}
return url;
}
private getFinalUrl(url: string): string {
url = this.getUrlWithoutSecret(url).substring(0, url.lastIndexOf('/')) + '/room';
if (url.indexOf(".ngrok.io") !== -1) {
// OpenVidu server URL referes to a ngrok IP: secure wss protocol and delete port of URL
url = url.replace("ws://", "wss://");
let regex = /\.ngrok\.io:\d+/;
url = url.replace(regex, ".ngrok.io");
} else if ((url.indexOf("localhost") !== -1) || (url.indexOf("127.0.0.1") != -1)) {
// OpenVidu server URL referes to localhost IP
}
return url;
}
/* NEW METHODS */
connect(token, callback) {
this.openVidu.connect((error) => {
if (error) {
callback('ERROR CONNECTING TO OPENVIDU');
}
else {
if (!token) {
token = this.randomToken();
}
let joinParams = {
token: token,
session: this.sessionId,
metadata: this.options.metadata,
secret: this.openVidu.getSecret(),
dataChannels: false
}
if (this.localParticipant) {
if (Object.keys(this.localParticipant.getStreams()).some(streamId =>
this.streams[streamId].isDataChannelEnabled())) {
joinParams.dataChannels = true;
}
}
this.openVidu.sendRequest('joinRoom', joinParams, (error, response) => {
if (error) {
callback(error);
} else {
this.connected = true;
let exParticipants = response.value;
// IMPORTANT: Update connectionId with value send by server
this.localParticipant.connectionId = response.id;
this.participants[response.id] = this.localParticipant;
let roomEvent = {
participants: new Array<Connection>(),
streams: new Array<Stream>()
}
let length = exParticipants.length;
for (let i = 0; i < length; i++) {
let connection = new Connection(this.openVidu, false, this,
exParticipants[i]);
connection.creationTime = new Date().getTime();
this.participants[connection.connectionId] = connection;
roomEvent.participants.push(connection);
let streams = connection.getStreams();
for (let key in streams) {
roomEvent.streams.push(streams[key]);
if (this.subscribeToStreams) {
streams[key].subscribe();
}
}
}
// Update local Connection object properties with values returned by server
this.localParticipant.data = response.metadata;
this.localParticipant.creationTime = new Date().getTime();
// Updates the value of property 'connection' in Session object
this.ee.emitEvent('update-connection-object', [{ connection: this.localParticipant }]);
// Own connection created event
this.ee.emitEvent('connectionCreated', [{ connection: this.localParticipant }]);
// One connection created event for each existing connection in the session
for (let part of roomEvent.participants) {
this.ee.emitEvent('connectionCreated', [{ connection: part }]);
}
//if (this.subscribeToStreams) {
for (let stream of roomEvent.streams) {
this.ee.emitEvent('streamCreated', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
//}
callback(undefined);
}
});
}
});
}
publish() {
this.openVidu.getCamera().publish();
}
/* NEW METHODS */
configure(options: SessionOptions) {
this.options = options;
this.id = options.sessionId;
this.subscribeToStreams = options.subscribeToStreams == null ? true : options.subscribeToStreams;
this.updateSpeakerInterval = options.updateSpeakerInterval || 1500;
this.thresholdSpeaker = options.thresholdSpeaker || -50;
this.activateUpdateMainSpeaker();
}
getId() {
return this.id;
}
getSessionId() {
return this.sessionId;
}
private activateUpdateMainSpeaker() {
setInterval(() => {
if (this.participantsSpeaking.length > 0) {
this.ee.emitEvent('update-main-speaker', [{
participantId: this.participantsSpeaking[this.participantsSpeaking.length - 1]
}]);
}
}, this.updateSpeakerInterval);
}
getLocalParticipant() {
return this.localParticipant;
}
addEventListener(eventName, listener) {
this.ee.on(eventName, listener);
}
addOnceEventListener(eventName, listener) {
this.ee.once(eventName, listener);
}
removeListener(eventName, listener) {
this.ee.off(eventName, listener);
}
removeEvent(eventName) {
this.ee.removeEvent(eventName);
}
emitEvent(eventName, eventsArray) {
this.ee.emitEvent(eventName, eventsArray);
}
subscribe(stream: Stream) {
stream.subscribe();
}
unsuscribe(stream) {
console.info("Unsubscribing from " + stream.getId());
this.openVidu.sendRequest('unsubscribeFromVideo', {
sender: stream.getId()
},
function (error, response) {
if (error) {
console.error("Error unsubscribing from Subscriber", error);
} else {
console.info("Unsubscribed correctly from " + stream.getId());
}
});
}
onParticipantPublished(options) {
options.metadata = this.participants[options.id].data;
// Get the existing Connection created on 'onParticipantJoined' for
// existing participants or create a new one for new participants
let connection = this.participants[options.id];
if (connection) {
// Update existing Connection
connection.options = options;
connection.initStreams(options);
} else {
// Create new Connection
connection = new Connection(this.openVidu, false, this, options);
}
let pid = connection.connectionId;
if (!(pid in this.participants)) {
console.debug("Remote Connection not found in connections list by its id [" + pid + "]");
} else {
console.debug("Remote Connection found in connections list by its id [" + pid + "]");
}
connection.creationTime = this.participants[pid].creationTime;
this.participants[pid] = connection;
this.ee.emitEvent('participant-published', [{ connection }]);
let streams = connection.getStreams();
for (let key in streams) {
let stream = streams[key];
if (this.subscribeToStreams) {
stream.subscribe();
}
this.ee.emitEvent('streamCreated', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
}
onParticipantJoined(msg) {
let connection = new Connection(this.openVidu, false, this, msg);
connection.creationTime = new Date().getTime();
let pid = connection.connectionId;
if (!(pid in this.participants)) {
this.participants[pid] = connection;
} else {
//use existing so that we don't lose streams info
console.warn("Connection already exists in connections list with " +
"the same connectionId, old:", this.participants[pid], ", joined now:", connection);
connection = this.participants[pid];
}
this.ee.emitEvent('participant-joined', [{
connection: connection
}]);
this.ee.emitEvent('connectionCreated', [{
connection: connection
}]);
}
onParticipantLeft(msg) {
let connection = this.participants[msg.name];
if (connection !== undefined) {
delete this.participants[msg.name];
this.ee.emitEvent('participant-left', [{
connection: connection
}]);
let streams = connection.getStreams();
for (let key in streams) {
this.ee.emitEvent('streamDestroyed', [{
stream: streams[key],
preventDefault: () => { this.ee.removeEvent('stream-destroyed-default'); }
}]);
this.ee.emitEvent('stream-destroyed-default', [{
stream: streams[key]
}]);
// Deleting the removed stream from the OpenVidu object
let index = this.openVidu.getRemoteStreams().indexOf(streams[key]);
this.openVidu.getRemoteStreams().splice(index, 1);
}
connection.dispose();
this.ee.emitEvent('connectionDestroyed', [{
connection: connection
}]);
} else {
console.warn("Participant " + msg.name
+ " unknown. Participants: "
+ JSON.stringify(this.participants));
}
};
onParticipantEvicted(msg) {
this.ee.emitEvent('participant-evicted', [{
localParticipant: this.localParticipant
}]);
};
onNewMessage(msg) {
console.info("New message: " + JSON.stringify(msg));
let room = msg.room;
let user = msg.user;
let message = msg.message;
if (user !== undefined) {
this.ee.emitEvent('newMessage', [{
room: room,
user: user,
message: message
}]);
} else {
console.warn("User undefined in new message:", msg);
}
}
recvIceCandidate(msg) {
let candidate = {
candidate: msg.candidate,
sdpMid: msg.sdpMid,
sdpMLineIndex: msg.sdpMLineIndex
}
let connection = this.participants[msg.endpointName];
if (!connection) {
console.error("Participant not found for endpoint " +
msg.endpointName + ". Ice candidate will be ignored.",
candidate);
return;
}
let streams = connection.getStreams();
for (let key in streams) {
let stream = streams[key];
stream.getWebRtcPeer().addIceCandidate(candidate, function (error) {
if (error) {
console.error("Error adding candidate for " + key
+ " stream of endpoint " + msg.endpointName
+ ": " + error);
}
});
}
}
onRoomClosed(msg) {
console.info("Room closed: " + JSON.stringify(msg));
let room = msg.room;
if (room !== undefined) {
this.ee.emitEvent('room-closed', [{
room: room
}]);
} else {
console.warn("Room undefined in on room closed", msg);
}
}
onLostConnection() {
if (!this.connected) {
console.warn('Not connected to room: if you are not debugging, this is probably a certificate error');
if (window.confirm('If you are not debugging, this is probably a certificate error at \"' + this.openVidu.getOpenViduServerURL() + '\"\n\nClick OK to navigate and accept it')) {
location.assign(this.openVidu.getOpenViduServerURL() + '/accept-certificate');
};
return;
}
console.warn('Lost connection in Session ' + this.id);
let room = this.id;
if (room !== undefined) {
this.ee.emitEvent('lost-connection', [{ room }]);
} else {
console.warn('Room undefined when lost connection');
}
}
onMediaError(params) {
console.error("Media error: " + JSON.stringify(params));
let error = params.error;
if (error) {
this.ee.emitEvent('error-media', [{
error: error
}]);
} else {
console.warn("Received undefined media error. Params:", params);
}
}
/*
* forced means the user was evicted, no need to send the 'leaveRoom' request
*/
leave(forced, jsonRpcClient) {
forced = !!forced;
console.info("Leaving Session (forced=" + forced + ")");
if (this.connected && !forced) {
this.openVidu.sendRequest('leaveRoom', function (error, response) {
if (error) {
console.error(error);
}
jsonRpcClient.close();
});
} else {
jsonRpcClient.close();
}
this.connected = false;
if (this.participants) {
for (let pid in this.participants) {
this.participants[pid].dispose();
delete this.participants[pid];
}
}
}
disconnect(stream: Stream) {
let connection = stream.getParticipant();
if (!connection) {
console.error("Stream to disconnect has no participant", stream);
return;
}
delete this.participants[connection.connectionId];
connection.dispose();
if (connection === this.localParticipant) {
console.info("Unpublishing my media (I'm " + connection.connectionId + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
} else {
this.unsuscribe(stream);
}
}
unpublish(stream: Stream) {
let connection = stream.getParticipant();
if (!connection) {
console.error("Stream to disconnect has no participant", stream);
return;
}
if (connection === this.localParticipant) {
delete this.participants[connection.connectionId];
connection.dispose();
console.info("Unpublishing my media (I'm " + connection.connectionId + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
}
}
getStreams() {
return this.streams;
}
addParticipantSpeaking(participantId) {
this.participantsSpeaking.push(participantId);
}
removeParticipantSpeaking(participantId) {
let pos = -1;
for (let i = 0; i < this.participantsSpeaking.length; i++) {
if (this.participantsSpeaking[i] == participantId) {
pos = i;
break;
}
}
if (pos != -1) {
this.participantsSpeaking.splice(pos, 1);
}
}
stringClientMetadata(metadata): string {
if (!(typeof metadata === 'string')) {
return JSON.stringify(metadata);
} else {
return metadata;
}
}
private randomToken(): string {
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
}
}