diff --git a/openvidu-node-client/src/Connection.ts b/openvidu-node-client/src/Connection.ts index 397ba8c4..3ed8adab 100644 --- a/openvidu-node-client/src/Connection.ts +++ b/openvidu-node-client/src/Connection.ts @@ -92,4 +92,36 @@ export class Connection { this.publishers = publishers; this.subscribers = subscribers; } + + /** + * @hidden + */ + equalTo(other: Connection): boolean { + let equals: boolean = ( + this.connectionId === other.connectionId && + this.createdAt === other.createdAt && + this.role === other.role && + this.token === other.token && + this.location === other.location && + this.platform === other.platform && + this.serverData === other.serverData && + this.clientData === other.clientData && + this.subscribers.length === other.subscribers.length && + this.publishers.length === other.publishers.length); + if (equals) { + equals = JSON.stringify(this.subscribers) === JSON.stringify(other.subscribers); + if (equals) { + let i = 0; + while (equals && i < this.publishers.length) { + equals = this.publishers[i].equalTo(other.publishers[i]); + i++; + } + return equals; + } else { + return false; + } + } else { + return false; + } + } } \ No newline at end of file diff --git a/openvidu-node-client/src/OpenVidu.ts b/openvidu-node-client/src/OpenVidu.ts index 27206b23..c3c70784 100644 --- a/openvidu-node-client/src/OpenVidu.ts +++ b/openvidu-node-client/src/OpenVidu.ts @@ -15,12 +15,13 @@ * */ -import { Session } from './Session'; -import { SessionProperties } from './SessionProperties'; +import axios from 'axios'; +import { Connection } from './Connection'; import { Recording } from './Recording'; import { RecordingProperties } from './RecordingProperties'; +import { Session } from './Session'; +import { SessionProperties } from './SessionProperties'; -import axios from 'axios'; export class OpenVidu { @@ -415,12 +416,22 @@ export class OpenVidu { res.data.content.forEach(session => { fetchedSessionIds.push(session.sessionId); - let storedSession = this.activeSessions.find(s => s.sessionId === session.sessionId); + let sessionIndex = -1; + let storedSession = this.activeSessions.find((s, index) => { + if (s.sessionId === session.sessionId) { + sessionIndex = index; + return true; + } else { + return false; + } + }); if (!!storedSession) { - const beforeJSON: string = JSON.stringify(storedSession); - storedSession = storedSession.resetSessionWithJson(session); - const afterJSON: string = JSON.stringify(storedSession); - const changed: boolean = !(beforeJSON === afterJSON); + const fetchedSession: Session = new Session().resetSessionWithJson(session); + const changed: boolean = !storedSession.equalTo(fetchedSession); + if (changed) { + storedSession = fetchedSession; + this.activeSessions[sessionIndex] = storedSession; + } console.log("Available session '" + storedSession.sessionId + "' info fetched. Any change: " + changed); hasChanged = hasChanged || changed; } else { @@ -462,6 +473,164 @@ export class OpenVidu { }); } + /** + * @hidden + */ + fetchWebRtc(): Promise { + + // tslint:disable:no-string-literal + const addWebRtcStatsToConnections = (connection: Connection, connectionsExtendedInfo: any) => { + const connectionExtended = connectionsExtendedInfo.find(c => c.connectionId === connection.connectionId); + if (!!connectionExtended) { + connection['publishersWebRtc'] = []; + connection.publishers.forEach(pub => { + const publisherExtended = connectionExtended.publishers.find(p => p.streamId === pub.streamId); + const pubAux = {}; + // Standard properties + pubAux['streamId'] = pub.streamId; + pubAux['createdAt'] = pub.createdAt; + pubAux['audioActive'] = pub.audioActive; + pubAux['videoActive'] = pub.videoActive; + pubAux['hasAudio'] = pub.hasAudio; + pubAux['hasVideo'] = pub.hasVideo; + pubAux['typeOfVideo'] = pub.typeOfVideo; + pubAux['frameRate'] = pub.frameRate; + pubAux['videoDimensions'] = pub.videoDimensions; + // WebRtc properties + pubAux['webRtc'] = { + kms: { + events: publisherExtended.events, + localCandidate: publisherExtended.localCandidate, + remoteCandidate: publisherExtended.remoteCandidate, + receivedCandidates: publisherExtended.receivedCandidates, + webrtcTagName: publisherExtended.webrtcTagName + } + }; + if (!!publisherExtended.serverStats) { + pubAux['webRtc'].kms.serverStats = publisherExtended.serverStats; + } + connection['publishersWebRtc'].push(pubAux); + }); + connection['subscribersWebRtc'] = []; + connection.subscribers.forEach(sub => { + const subscriberExtended = connectionExtended.subscribers.find(s => s.streamId === sub); + const subAux = {}; + // Standard properties + subAux['streamId'] = sub; + subAux['publisher'] = subscriberExtended.publisher; + // WebRtc properties + subAux['createdAt'] = subscriberExtended.createdAt; + subAux['webRtc'] = { + kms: { + events: subscriberExtended.events, + localCandidate: subscriberExtended.localCandidate, + remoteCandidate: subscriberExtended.remoteCandidate, + receivedCandidates: subscriberExtended.receivedCandidates, + webrtcTagName: subscriberExtended.webrtcTagName + } + }; + if (!!subscriberExtended.serverStats) { + subAux['webRtc'].kms.serverStats = subscriberExtended.serverStats; + } + connection['subscribersWebRtc'].push(subAux); + }); + } + }; + + return new Promise((resolve, reject) => { + axios.get( + 'https://' + OpenVidu.hostname + ':' + OpenVidu.port + OpenVidu.API_SESSIONS + '?webRtcStats=true', + { + headers: { + Authorization: OpenVidu.basicAuth + } + } + ) + .then(res => { + if (res.status === 200) { + + // Array to store fetched sessionIds and later remove closed sessions + const fetchedSessionIds: string[] = []; + // Boolean to store if any Session has changed + let hasChanged = false; + + res.data.content.forEach(session => { + fetchedSessionIds.push(session.sessionId); + let sessionIndex = -1; + let storedSession = this.activeSessions.find((s, index) => { + if (s.sessionId === session.sessionId) { + sessionIndex = index; + return true; + } else { + return false; + } + }); + if (!!storedSession) { + const fetchedSession: Session = new Session().resetSessionWithJson(session); + let changed = !storedSession.equalTo(fetchedSession); + fetchedSession.activeConnections.forEach((connection, index1) => { + addWebRtcStatsToConnections(connection, session.connections.content); + if (!changed) { // Check if server information has changed in any Publisher/Subscriber + for (let index2 = 0; (index2 < connection['publishersWebRtc'].length && !changed); index2++) { + changed = changed || JSON.stringify(connection['publishersWebRtc'][index2]['webRtc']) !== JSON.stringify(storedSession.activeConnections[index1]['publishersWebRtc'][index2]['webRtc']); + } + if (!changed) { + for (let index2 = 0; (index2 < connection['subscribersWebRtc'].length && !changed); index2++) { + changed = changed || JSON.stringify(connection['subscribersWebRtc'][index2]['webRtc']) !== JSON.stringify(storedSession.activeConnections[index1]['subscribersWebRtc'][index2]['webRtc']); + } + } + } + }); + if (changed) { + storedSession = fetchedSession; + this.activeSessions[sessionIndex] = storedSession; + } + console.log("Available session '" + storedSession.sessionId + "' info fetched. Any change: " + changed); + hasChanged = hasChanged || changed; + } else { + const newSession = new Session(session); + newSession.activeConnections.forEach(connection => { + addWebRtcStatsToConnections(connection, session.connections.content); + }); + this.activeSessions.push(newSession); + console.log("New session '" + session.sessionId + "' info fetched"); + hasChanged = true; + } + }); + // Remove closed sessions from activeSessions array + this.activeSessions = this.activeSessions.filter(session => { + if (fetchedSessionIds.includes(session.sessionId)) { + return true; + } else { + console.log("Removing closed session '" + session.sessionId + "'"); + hasChanged = true; + return false; + } + }); + console.log('Active sessions info fetched: ', fetchedSessionIds); + resolve(hasChanged); + } else { + // ERROR response from openvidu-server. Resolve HTTP status + reject(new Error(res.status.toString())); + } + }).catch(error => { + if (error.response) { + // The request was made and the server responded with a status code (not 2xx) + reject(new Error(error.response.status.toString())); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.error(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.error('Error', error.message); + } + }); + }); + } + // tslint:enable:no-string-literal + private getBasicAuth(secret: string): string { return 'Basic ' + this.Buffer('OPENVIDUAPP:' + secret).toString('base64'); } diff --git a/openvidu-node-client/src/Publisher.ts b/openvidu-node-client/src/Publisher.ts index a7233377..8602918b 100644 --- a/openvidu-node-client/src/Publisher.ts +++ b/openvidu-node-client/src/Publisher.ts @@ -15,7 +15,6 @@ * */ -import { OpenViduRole } from './OpenViduRole'; /** * See [[Connection.publishers]] @@ -31,6 +30,11 @@ export class Publisher { */ streamId: string; + /** + * Timestamp when this Publisher started publishing, in UTC milliseconds (ms since Jan 1, 1970, 00:00:00 UTC) + */ + createdAt: number; + /** * See properties of [Stream](/api/openvidu-browser/classes/stream.html) object in OpenVidu Browser library to find out more */ @@ -66,8 +70,12 @@ export class Publisher { */ videoDimensions: string; + /** + * @hidden + */ constructor(json) { this.streamId = json.streamId; + this.createdAt = json.createdAt; this.hasAudio = json.mediaOptions.hasAudio; this.hasVideo = json.mediaOptions.hasVideo; this.audioActive = json.mediaOptions.audioActive; @@ -77,4 +85,21 @@ export class Publisher { this.videoDimensions = json.mediaOptions.videoDimensions; } + /** + * @hidden + */ + equalTo(other: Publisher): boolean { + return ( + this.streamId === other.streamId && + this.createdAt === other.createdAt && + this.hasAudio === other.hasAudio && + this.hasVideo === other.hasVideo && + this.audioActive === other.audioActive && + this.videoActive === other.videoActive && + this.frameRate === other.frameRate && + this.typeOfVideo === other.typeOfVideo && + this.videoDimensions === other.videoDimensions + ); + } + } \ No newline at end of file diff --git a/openvidu-node-client/src/Session.ts b/openvidu-node-client/src/Session.ts index 18eb2590..d2882683 100644 --- a/openvidu-node-client/src/Session.ts +++ b/openvidu-node-client/src/Session.ts @@ -15,6 +15,7 @@ * */ +import axios from 'axios'; import { Connection } from './Connection'; import { MediaMode } from './MediaMode'; import { OpenVidu } from './OpenVidu'; @@ -25,7 +26,6 @@ import { RecordingMode } from './RecordingMode'; import { SessionProperties } from './SessionProperties'; import { TokenOptions } from './TokenOptions'; -import axios from 'axios'; export class Session { @@ -429,6 +429,7 @@ export class Session { */ public resetSessionWithJson(json): Session { this.sessionId = json.sessionId; + this.createdAt = json.createdAt; this.recording = json.recording; let customSessionId: string; let defaultCustomLayout: string; @@ -474,4 +475,27 @@ export class Session { return this; } + /** + * @hidden + */ + equalTo(other: Session): boolean { + let equals: boolean = ( + this.sessionId === other.sessionId && + this.createdAt === other.createdAt && + this.recording === other.recording && + this.activeConnections.length === other.activeConnections.length && + JSON.stringify(this.properties) === JSON.stringify(other.properties) + ); + if (equals) { + let i = 0; + while (equals && i < this.activeConnections.length) { + equals = this.activeConnections[i].equalTo(other.activeConnections[i]); + i++; + } + return equals; + } else { + return false; + } + } + } \ No newline at end of file