openvidu-browser: Added common format config file

pull/750/head
csantosm 2022-08-17 18:04:05 +02:00
parent 128dd3cfed
commit 2ce54f577b
66 changed files with 3711 additions and 3328 deletions

View File

@ -0,0 +1,10 @@
{
"singleQuote": true,
"printWidth": 140,
"trailingComma": "none",
"semi": true,
"bracketSpacing": true,
"useTabs": false,
"jsxSingleQuote": true,
"tabWidth": 4
}

View File

@ -6,4 +6,4 @@ if (typeof globalThis !== 'undefined') {
} }
// Disable jsnlog when library is loaded // Disable jsnlog when library is loaded
JL.setOptions({ enabled: false }) JL.setOptions({ enabled: false });

View File

@ -29,13 +29,11 @@ import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/E
*/ */
const logger: OpenViduLogger = OpenViduLogger.getInstance(); const logger: OpenViduLogger = OpenViduLogger.getInstance();
/** /**
* Represents each one of the user's connection to the session (the local one and other user's connections). * Represents each one of the user's connection to the session (the local one and other user's connections).
* Therefore each [[Session]] and [[Stream]] object has an attribute of type Connection * Therefore each [[Session]] and [[Stream]] object has an attribute of type Connection
*/ */
export class Connection { export class Connection {
/** /**
* Unique identifier of the connection * Unique identifier of the connection
*/ */
@ -125,38 +123,46 @@ export class Connection {
logger.info(msg); logger.info(msg);
} }
/* Hidden methods */ /* Hidden methods */
/** /**
* @hidden * @hidden
*/ */
sendIceCandidate(candidate: RTCIceCandidate): void { sendIceCandidate(candidate: RTCIceCandidate): void {
logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' + this.connectionId, candidate);
logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' + this.session.openvidu.sendRequest(
this.connectionId, candidate); 'onIceCandidate',
{
this.session.openvidu.sendRequest('onIceCandidate', {
endpointName: this.connectionId, endpointName: this.connectionId,
candidate: candidate.candidate, candidate: candidate.candidate,
sdpMid: candidate.sdpMid, sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex sdpMLineIndex: candidate.sdpMLineIndex
}, (error, response) => { },
(error, response) => {
if (error) { if (error) {
logger.error('Error sending ICE candidate: ' + JSON.stringify(error)); logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
this.session.emitEvent('exception', [new ExceptionEvent(this.session, ExceptionEventName.ICE_CANDIDATE_ERROR, this.session, "There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side", error)]); this.session.emitEvent('exception', [
new ExceptionEvent(
this.session,
ExceptionEventName.ICE_CANDIDATE_ERROR,
this.session,
'There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side',
error
)
]);
} }
}); }
);
} }
/** /**
* @hidden * @hidden
*/ */
initRemoteStreams(options: StreamOptionsServer[]): void { initRemoteStreams(options: StreamOptionsServer[]): void {
// This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once // This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
// this.stream should also be replaced by a collection of streams to support multiple streams per Connection // this.stream should also be replaced by a collection of streams to support multiple streams per Connection
options.forEach(opts => { options.forEach((opts) => {
const streamOptions: InboundStreamOptions = { const streamOptions: InboundStreamOptions = {
id: opts.id, id: opts.id,
createdAt: opts.createdAt, createdAt: opts.createdAt,
@ -175,7 +181,10 @@ export class Connection {
this.addStream(stream); this.addStream(stream);
}); });
logger.info("Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ', this.stream!.inboundStreamOpts); logger.info(
"Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ',
this.stream!.inboundStreamOpts
);
} }
/** /**
@ -202,5 +211,4 @@ export class Connection {
} }
this.disposed = true; this.disposed = true;
} }
} }

View File

@ -27,7 +27,6 @@ import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
const logger: OpenViduLogger = OpenViduLogger.getInstance(); const logger: OpenViduLogger = OpenViduLogger.getInstance();
export abstract class EventDispatcher { export abstract class EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
@ -42,27 +41,27 @@ export abstract class EventDispatcher {
* *
* @returns The EventDispatcher object * @returns The EventDispatcher object
*/ */
abstract on<K extends keyof (EventMap)>(type: K, handler: (event: (EventMap)[K]) => void): this; abstract on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
/** /**
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution * Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
* *
* @returns The object that dispatched the event * @returns The object that dispatched the event
*/ */
abstract once<K extends keyof (EventMap)>(type: K, handler: (event: (EventMap)[K]) => void): this; abstract once<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
/** /**
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event * Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
* *
* @returns The object that dispatched the event * @returns The object that dispatched the event
*/ */
abstract off<K extends keyof (EventMap)>(type: K, handler?: (event: (EventMap)[K]) => void): this; abstract off<K extends keyof EventMap>(type: K, handler?: (event: EventMap[K]) => void): this;
/** /**
* @hidden * @hidden
*/ */
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher { onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
const arrowHandler = event => { const arrowHandler = (event) => {
if (event) { if (event) {
logger.info(message, event); logger.info(message, event);
} else { } else {
@ -79,7 +78,7 @@ export abstract class EventDispatcher {
* @hidden * @hidden
*/ */
onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher { onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
const arrowHandler = event => { const arrowHandler = (event) => {
if (event) { if (event) {
logger.info(message, event); logger.info(message, event);
} else { } else {
@ -110,5 +109,4 @@ export abstract class EventDispatcher {
} }
return this; return this;
} }
} }

View File

@ -32,7 +32,6 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
* Video/audio filter applied to a Stream. See [[Stream.applyFilter]] * Video/audio filter applied to a Stream. See [[Stream.applyFilter]]
*/ */
export class Filter { export class Filter {
/** /**
* Type of filter applied. This is the name of the remote class identifying the filter to apply in Kurento Media Server. * Type of filter applied. This is the name of the remote class identifying the filter to apply in Kurento Media Server.
* For example: `"FaceOverlayFilter"`, `"GStreamerFilter"`. * For example: `"FaceOverlayFilter"`, `"GStreamerFilter"`.
@ -59,7 +58,8 @@ export class Filter {
* You can use this value to know the current status of any applied filter * You can use this value to know the current status of any applied filter
*/ */
lastExecMethod?: { lastExecMethod?: {
method: string, params: Object method: string;
params: Object;
}; };
/** /**
@ -73,7 +73,6 @@ export class Filter {
stream: Stream; stream: Stream;
private logger: OpenViduLogger; private logger: OpenViduLogger;
/** /**
* @hidden * @hidden
*/ */
@ -82,7 +81,6 @@ export class Filter {
this.options = options; this.options = options;
} }
/** /**
* Executes a filter method. Available methods are specific for each filter * Executes a filter method. Available methods are specific for each filter
* *
@ -91,24 +89,40 @@ export class Filter {
*/ */
execMethod(method: string, params: Object): Promise<void> { execMethod(method: string, params: Object): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info('Executing filter method to stream ' + this.stream.streamId); logger.info('Executing filter method to stream ' + this.stream.streamId);
let finalParams; let finalParams;
const successExecMethod = triggerEvent => { const successExecMethod = (triggerEvent) => {
logger.info('Filter method successfully executed on Stream ' + this.stream.streamId); logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
const oldValue = (<any>Object).assign({}, this.stream.filter); const oldValue = (<any>Object).assign({}, this.stream.filter);
this.stream.filter!.lastExecMethod = { method, params: finalParams }; this.stream.filter!.lastExecMethod = { method, params: finalParams };
if (triggerEvent) { if (triggerEvent) {
this.stream.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.stream.session, this.stream, 'filter', this.stream.filter!, oldValue, 'execFilterMethod')]); this.stream.session.emitEvent('streamPropertyChanged', [
this.stream.streamManager.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.stream.streamManager, this.stream, 'filter', this.stream.filter!, oldValue, 'execFilterMethod')]); new StreamPropertyChangedEvent(
this.stream.session,
this.stream,
'filter',
this.stream.filter!,
oldValue,
'execFilterMethod'
)
]);
this.stream.streamManager.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(
this.stream.streamManager,
this.stream,
'filter',
this.stream.filter!,
oldValue,
'execFilterMethod'
)
]);
} }
return resolve(); return resolve();
} };
if (this.type.startsWith('VB:')) { if (this.type.startsWith('VB:')) {
if (typeof params === 'string') { if (typeof params === 'string') {
try { try {
params = JSON.parse(params); params = JSON.parse(params);
@ -121,23 +135,31 @@ export class Filter {
if (method === 'update') { if (method === 'update') {
if (!this.stream.virtualBackgroundSinkElements?.VB) { if (!this.stream.virtualBackgroundSinkElements?.VB) {
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')); return reject(
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')
);
} else { } else {
this.stream.virtualBackgroundSinkElements.VB.updateValues(params) this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
.then(() => successExecMethod(false)) .then(() => successExecMethod(false))
.catch(error => { .catch((error) => {
if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) { if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
return reject(new OpenViduError(error.name, error.message)); return reject(new OpenViduError(error.name, error.message));
} else { } else {
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'Error updating values on Virtual Background filter: ' + error)); return reject(
new OpenViduError(
OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
'Error updating values on Virtual Background filter: ' + error
)
);
} }
}); });
} }
} else { } else {
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)); return reject(
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)
);
} }
} else { } else {
let stringParams; let stringParams;
if (typeof params !== 'string') { if (typeof params !== 'string') {
try { try {
@ -160,7 +182,12 @@ export class Filter {
if (error) { if (error) {
logger.error('Error executing filter method for Stream ' + this.stream.streamId, error); logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
if (error.code === 401) { if (error.code === 401) {
return reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to execute a filter method")); return reject(
new OpenViduError(
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
"You don't have permissions to execute a filter method"
)
);
} else { } else {
return reject(error); return reject(error);
} }
@ -173,7 +200,6 @@ export class Filter {
}); });
} }
/** /**
* Subscribe to certain filter event. Available events are specific for each filter * Subscribe to certain filter event. Available events are specific for each filter
* *
@ -190,15 +216,25 @@ export class Filter {
{ streamId: this.stream.streamId, eventType }, { streamId: this.stream.streamId, eventType },
(error, response) => { (error, response) => {
if (error) { if (error) {
logger.error('Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId, error); logger.error(
'Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
error
);
if (error.code === 401) { if (error.code === 401) {
return reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to add a filter event listener")); return reject(
new OpenViduError(
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
"You don't have permissions to add a filter event listener"
)
);
} else { } else {
return reject(error); return reject(error);
} }
} else { } else {
this.handlers.set(eventType, handler); this.handlers.set(eventType, handler);
logger.info('Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId); logger.info(
'Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId
);
return resolve(); return resolve();
} }
} }
@ -206,7 +242,6 @@ export class Filter {
}); });
} }
/** /**
* Removes certain filter event listener previously set. * Removes certain filter event listener previously set.
* *
@ -222,20 +257,29 @@ export class Filter {
{ streamId: this.stream.streamId, eventType }, { streamId: this.stream.streamId, eventType },
(error, response) => { (error, response) => {
if (error) { if (error) {
logger.error('Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId, error); logger.error(
'Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
error
);
if (error.code === 401) { if (error.code === 401) {
return reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to add a filter event listener")); return reject(
new OpenViduError(
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
"You don't have permissions to add a filter event listener"
)
);
} else { } else {
return reject(error); return reject(error);
} }
} else { } else {
this.handlers.delete(eventType); this.handlers.delete(eventType);
logger.info('Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId); logger.info(
'Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId
);
return resolve(); return resolve();
} }
} }
); );
}); });
} }
} }

View File

@ -31,12 +31,10 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
*/ */
let platform: PlatformUtils; let platform: PlatformUtils;
/** /**
* Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method * Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method
*/ */
export class LocalRecorder { export class LocalRecorder {
state: LocalRecorderState; state: LocalRecorderState;
private connectionId: string; private connectionId: string;
@ -52,12 +50,11 @@ export class LocalRecorder {
*/ */
constructor(private stream: Stream) { constructor(private stream: Stream) {
platform = PlatformUtils.getInstance(); platform = PlatformUtils.getInstance();
this.connectionId = (!!this.stream.connection) ? this.stream.connection.connectionId : 'default-connection'; this.connectionId = !!this.stream.connection ? this.stream.connection.connectionId : 'default-connection';
this.id = this.stream.streamId + '_' + this.connectionId + '_localrecord'; this.id = this.stream.streamId + '_' + this.connectionId + '_localrecord';
this.state = LocalRecorderState.READY; this.state = LocalRecorderState.READY;
} }
/** /**
* Starts the recording of the Stream. [[state]] property must be `READY`. After method succeeds is set to `RECORDING` * Starts the recording of the Stream. [[state]] property must be `READY`. After method succeeds is set to `RECORDING`
* *
@ -84,14 +81,24 @@ export class LocalRecorder {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (typeof options === 'string' || options instanceof String) { if (typeof options === 'string' || options instanceof String) {
return reject(`When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`); return reject(
`When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`
);
} }
if (typeof MediaRecorder === 'undefined') { if (typeof MediaRecorder === 'undefined') {
logger.error('MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'); logger.error(
throw (Error('MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder')); 'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
);
throw Error(
'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
);
} }
if (this.state !== LocalRecorderState.READY) { if (this.state !== LocalRecorderState.READY) {
throw (Error('\'LocalRecord.record()\' needs \'LocalRecord.state\' to be \'READY\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.clean()\' or init a new LocalRecorder before')); throw Error(
"'LocalRecord.record()' needs 'LocalRecord.state' to be 'READY' (current value: '" +
this.state +
"'). Call 'LocalRecorder.clean()' or init a new LocalRecorder before"
);
} }
logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'"); logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
@ -103,7 +110,6 @@ export class LocalRecorder {
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options); this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
this.mediaRecorder.start(); this.mediaRecorder.start();
} catch (err) { } catch (err) {
return reject(err); return reject(err);
} }
@ -136,11 +142,9 @@ export class LocalRecorder {
this.state = LocalRecorderState.RECORDING; this.state = LocalRecorderState.RECORDING;
return resolve(); return resolve();
}); });
} }
/** /**
* Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED` * Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not
@ -149,7 +153,11 @@ export class LocalRecorder {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) { if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) {
throw (Error('\'LocalRecord.stop()\' needs \'LocalRecord.state\' to be \'RECORDING\' or \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' before')); throw Error(
"'LocalRecord.stop()' needs 'LocalRecord.state' to be 'RECORDING' or 'PAUSED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.start()' before"
);
} }
this.mediaRecorder.onstop = () => { this.mediaRecorder.onstop = () => {
this.onStopDefault(); this.onStopDefault();
@ -162,7 +170,6 @@ export class LocalRecorder {
}); });
} }
/** /**
* Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED` * Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not
@ -171,7 +178,13 @@ export class LocalRecorder {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (this.state !== LocalRecorderState.RECORDING) { if (this.state !== LocalRecorderState.RECORDING) {
return reject(Error('\'LocalRecord.pause()\' needs \'LocalRecord.state\' to be \'RECORDING\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' or \'LocalRecorder.resume()\' before')); return reject(
Error(
"'LocalRecord.pause()' needs 'LocalRecord.state' to be 'RECORDING' (current value: '" +
this.state +
"'). Call 'LocalRecorder.start()' or 'LocalRecorder.resume()' before"
)
);
} }
this.mediaRecorder.pause(); this.mediaRecorder.pause();
this.state = LocalRecorderState.PAUSED; this.state = LocalRecorderState.PAUSED;
@ -190,7 +203,11 @@ export class LocalRecorder {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (this.state !== LocalRecorderState.PAUSED) { if (this.state !== LocalRecorderState.PAUSED) {
throw (Error('\'LocalRecord.resume()\' needs \'LocalRecord.state\' to be \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.pause()\' before')); throw Error(
"'LocalRecord.resume()' needs 'LocalRecord.state' to be 'PAUSED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.pause()' before"
);
} }
this.mediaRecorder.resume(); this.mediaRecorder.resume();
this.state = LocalRecorderState.RECORDING; this.state = LocalRecorderState.RECORDING;
@ -201,14 +218,16 @@ export class LocalRecorder {
}); });
} }
/** /**
* Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. [[state]] property must be `FINISHED` * Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. [[state]] property must be `FINISHED`
*/ */
preview(parentElement): HTMLVideoElement { preview(parentElement): HTMLVideoElement {
if (this.state !== LocalRecorderState.FINISHED) { if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('\'LocalRecord.preview()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before')); throw Error(
"'LocalRecord.preview()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
);
} }
this.videoPreview = document.createElement('video'); this.videoPreview = document.createElement('video');
@ -234,7 +253,6 @@ export class LocalRecorder {
return this.videoPreview; return this.videoPreview;
} }
/** /**
* Gracefully stops and cleans the current recording (WARNING: it is completely dismissed). Sets [[state]] to `READY` so the recording can start again * Gracefully stops and cleans the current recording (WARNING: it is completely dismissed). Sets [[state]] to `READY` so the recording can start again
*/ */
@ -245,19 +263,24 @@ export class LocalRecorder {
this.state = LocalRecorderState.READY; this.state = LocalRecorderState.READY;
}; };
if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) { if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) {
this.stop().then(() => f()).catch(() => f()); this.stop()
.then(() => f())
.catch(() => f());
} else { } else {
f(); f();
} }
} }
/** /**
* Downloads the recorded video through the browser. [[state]] property must be `FINISHED` * Downloads the recorded video through the browser. [[state]] property must be `FINISHED`
*/ */
download(): void { download(): void {
if (this.state !== LocalRecorderState.FINISHED) { if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('\'LocalRecord.download()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before')); throw Error(
"'LocalRecord.download()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
);
} else { } else {
const a: HTMLAnchorElement = document.createElement('a'); const a: HTMLAnchorElement = document.createElement('a');
a.style.display = 'none'; a.style.display = 'none';
@ -278,13 +301,12 @@ export class LocalRecorder {
*/ */
getBlob(): Blob { getBlob(): Blob {
if (this.state !== LocalRecorderState.FINISHED) { if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('Call \'LocalRecord.stop()\' before getting Blob file')); throw Error("Call 'LocalRecord.stop()' before getting Blob file");
} else { } else {
return this.blob!; return this.blob!;
} }
} }
/** /**
* Uploads the recorded video as a binary file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example: * Uploads the recorded video as a binary file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
* ``` * ```
@ -298,7 +320,13 @@ export class LocalRecorder {
uploadAsBinary(endpoint: string, headers?: any): Promise<any> { uploadAsBinary(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.state !== LocalRecorderState.FINISHED) { if (this.state !== LocalRecorderState.FINISHED) {
return reject(Error('\'LocalRecord.uploadAsBinary()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before')); return reject(
Error(
"'LocalRecord.uploadAsBinary()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
)
);
} else { } else {
const http = new XMLHttpRequest(); const http = new XMLHttpRequest();
http.open('POST', endpoint, true); http.open('POST', endpoint, true);
@ -324,7 +352,6 @@ export class LocalRecorder {
}); });
} }
/** /**
* Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example: * Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
* ``` * ```
@ -338,7 +365,13 @@ export class LocalRecorder {
uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> { uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.state !== LocalRecorderState.FINISHED) { if (this.state !== LocalRecorderState.FINISHED) {
return reject(Error('\'LocalRecord.uploadAsMultipartfile()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before')); return reject(
Error(
"'LocalRecord.uploadAsMultipartfile()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
)
);
} else { } else {
const http = new XMLHttpRequest(); const http = new XMLHttpRequest();
http.open('POST', endpoint, true); http.open('POST', endpoint, true);
@ -368,7 +401,6 @@ export class LocalRecorder {
}); });
} }
/* Private methods */ /* Private methods */
private onStopDefault(): void { private onStopDefault(): void {
@ -381,5 +413,4 @@ export class LocalRecorder {
this.state = LocalRecorderState.FINISHED; this.state = LocalRecorderState.FINISHED;
} }
} }

View File

@ -32,7 +32,7 @@ import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto'; import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing'; import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
import { OpenViduLoggerConfiguration } from "../OpenViduInternal/Logger/OpenViduLoggerConfiguration"; import { OpenViduLoggerConfiguration } from '../OpenViduInternal/Logger/OpenViduLoggerConfiguration';
/** /**
* @hidden * @hidden
*/ */
@ -65,7 +65,6 @@ let platform: PlatformUtils;
* Use it to initialize objects of type [[Session]], [[Publisher]] and [[LocalRecorder]] * Use it to initialize objects of type [[Session]], [[Publisher]] and [[LocalRecorder]]
*/ */
export class OpenVidu { export class OpenVidu {
private jsonRpcClient: any; private jsonRpcClient: any;
private masterNodeHasCrashed = false; private masterNodeHasCrashed = false;
@ -144,19 +143,19 @@ export class OpenVidu {
/** /**
* @hidden * @hidden
*/ */
ee = new EventEmitter() ee = new EventEmitter();
constructor() { constructor() {
platform = PlatformUtils.getInstance(); platform = PlatformUtils.getInstance();
this.libraryVersion = packageJson.version; this.libraryVersion = packageJson.version;
logger.info("OpenVidu initialized"); logger.info('OpenVidu initialized');
logger.info('Platform detected: ' + platform.getDescription()); logger.info('Platform detected: ' + platform.getDescription());
logger.info('openvidu-browser version: ' + this.libraryVersion); logger.info('openvidu-browser version: ' + this.libraryVersion);
if (platform.isMobileDevice() || platform.isReactNative()) { if (platform.isMobileDevice() || platform.isReactNative()) {
// Listen to orientationchange only on mobile devices // Listen to orientationchange only on mobile devices
this.onOrientationChanged(() => { this.onOrientationChanged(() => {
this.publishers.forEach(publisher => { this.publishers.forEach((publisher) => {
if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) { if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10); this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
} }
@ -173,11 +172,14 @@ export class OpenVidu {
return this.session; return this.session;
} }
initPublisher(targetElement: string | HTMLElement | undefined): Publisher; initPublisher(targetElement: string | HTMLElement | undefined): Publisher;
initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Publisher; initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Publisher;
initPublisher(targetElement: string | HTMLElement | undefined, completionHandler: (error: Error | undefined) => void): Publisher; initPublisher(targetElement: string | HTMLElement | undefined, completionHandler: (error: Error | undefined) => void): Publisher;
initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties, completionHandler: (error: Error | undefined) => void): Publisher; initPublisher(
targetElement: string | HTMLElement | undefined,
properties: PublisherProperties,
completionHandler: (error: Error | undefined) => void
): Publisher;
/** /**
* Returns a new publisher * Returns a new publisher
@ -200,29 +202,41 @@ export class OpenVidu {
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event * `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
*/ */
initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Publisher { initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Publisher {
let properties: PublisherProperties; let properties: PublisherProperties;
if (!!param2 && (typeof param2 !== 'function')) { if (!!param2 && typeof param2 !== 'function') {
// Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)' // Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
properties = (<PublisherProperties>param2); properties = <PublisherProperties>param2;
properties = { properties = {
audioSource: (typeof properties.audioSource !== 'undefined') ? properties.audioSource : undefined, audioSource: typeof properties.audioSource !== 'undefined' ? properties.audioSource : undefined,
frameRate: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.frameRate !== 'undefined') ? properties.frameRate : undefined), frameRate:
insertMode: (typeof properties.insertMode !== 'undefined') ? ((typeof properties.insertMode === 'string') ? VideoInsertMode[properties.insertMode] : properties.insertMode) : VideoInsertMode.APPEND, typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
mirror: (typeof properties.mirror !== 'undefined') ? properties.mirror : true, ? undefined
publishAudio: (typeof properties.publishAudio !== 'undefined') ? properties.publishAudio : true, : typeof properties.frameRate !== 'undefined'
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true, ? properties.frameRate
resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'), : undefined,
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined, insertMode:
typeof properties.insertMode !== 'undefined'
? typeof properties.insertMode === 'string'
? VideoInsertMode[properties.insertMode]
: properties.insertMode
: VideoInsertMode.APPEND,
mirror: typeof properties.mirror !== 'undefined' ? properties.mirror : true,
publishAudio: typeof properties.publishAudio !== 'undefined' ? properties.publishAudio : true,
publishVideo: typeof properties.publishVideo !== 'undefined' ? properties.publishVideo : true,
resolution:
typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
? undefined
: typeof properties.resolution !== 'undefined'
? properties.resolution
: '640x480',
videoSource: typeof properties.videoSource !== 'undefined' ? properties.videoSource : undefined,
videoSimulcast: properties.videoSimulcast, videoSimulcast: properties.videoSimulcast,
filter: properties.filter filter: properties.filter
}; };
} else { } else {
// Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)' // Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
properties = { properties = {
@ -237,19 +251,21 @@ export class OpenVidu {
const publisher: Publisher = new Publisher(targetElement, properties, this); const publisher: Publisher = new Publisher(targetElement, properties, this);
let completionHandler: (error: Error | undefined) => void; let completionHandler: (error: Error | undefined) => void;
if (!!param2 && (typeof param2 === 'function')) { if (!!param2 && typeof param2 === 'function') {
completionHandler = param2; completionHandler = param2;
} else if (!!param3) { } else if (!!param3) {
completionHandler = param3; completionHandler = param3;
} }
publisher.initialize() publisher
.initialize()
.then(() => { .then(() => {
if (completionHandler !== undefined) { if (completionHandler !== undefined) {
completionHandler(undefined); completionHandler(undefined);
} }
publisher.emitEvent('accessAllowed', []); publisher.emitEvent('accessAllowed', []);
}).catch((error) => { })
.catch((error) => {
if (completionHandler !== undefined) { if (completionHandler !== undefined) {
completionHandler(error); completionHandler(error);
} }
@ -260,7 +276,6 @@ export class OpenVidu {
return publisher; return publisher;
} }
/** /**
* Promisified version of [[OpenVidu.initPublisher]] * Promisified version of [[OpenVidu.initPublisher]]
* *
@ -271,7 +286,6 @@ export class OpenVidu {
initPublisherAsync(targetElement: string | HTMLElement | undefined, properties?: PublisherProperties): Promise<Publisher> { initPublisherAsync(targetElement: string | HTMLElement | undefined, properties?: PublisherProperties): Promise<Publisher> {
return new Promise<Publisher>((resolve, reject) => { return new Promise<Publisher>((resolve, reject) => {
let publisher: Publisher; let publisher: Publisher;
const callback = (error: Error) => { const callback = (error: Error) => {
@ -290,7 +304,6 @@ export class OpenVidu {
}); });
} }
/** /**
* Returns a new local recorder for recording streams straight away from the browser * Returns a new local recorder for recording streams straight away from the browser
* @param stream Stream to record * @param stream Stream to record
@ -299,7 +312,6 @@ export class OpenVidu {
return new LocalRecorder(stream); return new LocalRecorder(stream);
} }
/** /**
* Checks if the browser supports OpenVidu * Checks if the browser supports OpenVidu
* @returns 1 if the browser supports OpenVidu, 0 otherwise * @returns 1 if the browser supports OpenVidu, 0 otherwise
@ -343,17 +355,18 @@ export class OpenVidu {
return platform.canScreenShare(); return platform.canScreenShare();
} }
/** /**
* Collects information about the media input devices available on the system. You can pass property `deviceId` of a [[Device]] object as value of `audioSource` or `videoSource` properties in [[initPublisher]] method * Collects information about the media input devices available on the system. You can pass property `deviceId` of a [[Device]] object as value of `audioSource` or `videoSource` properties in [[initPublisher]] method
*/ */
getDevices(): Promise<Device[]> { getDevices(): Promise<Device[]> {
return new Promise<Device[]>((resolve, reject) => { return new Promise<Device[]>((resolve, reject) => {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => { navigator.mediaDevices
.enumerateDevices()
.then((deviceInfos) => {
const devices: Device[] = []; const devices: Device[] = [];
// Ionic Android devices // Ionic Android devices
if (platform.isIonicAndroid() && typeof cordova != "undefined" && cordova?.plugins?.EnumerateDevicesPlugin) { if (platform.isIonicAndroid() && typeof cordova != 'undefined' && cordova?.plugins?.EnumerateDevicesPlugin) {
cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => { cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => {
let pluginAudioDevices: Device[] = []; let pluginAudioDevices: Device[] = [];
let videoDevices: Device[] = []; let videoDevices: Device[] = [];
@ -363,20 +376,19 @@ export class OpenVidu {
audioDevices = deviceInfos.filter((device: Device) => device.kind === 'audioinput'); audioDevices = deviceInfos.filter((device: Device) => device.kind === 'audioinput');
videoDevices.forEach((deviceInfo, index) => { videoDevices.forEach((deviceInfo, index) => {
if (!deviceInfo.label) { if (!deviceInfo.label) {
let label = ""; let label = '';
if (index === 0) { if (index === 0) {
label = "Front Camera"; label = 'Front Camera';
} else if (index === 1) { } else if (index === 1) {
label = "Back Camera"; label = 'Back Camera';
} else { } else {
label = "Unknown Camera"; label = 'Unknown Camera';
} }
devices.push({ devices.push({
kind: deviceInfo.kind, kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId, deviceId: deviceInfo.deviceId,
label: label label: label
}); });
} else { } else {
devices.push({ devices.push({
kind: deviceInfo.kind, kind: deviceInfo.kind,
@ -387,7 +399,7 @@ export class OpenVidu {
}); });
audioDevices.forEach((deviceInfo, index) => { audioDevices.forEach((deviceInfo, index) => {
if (!deviceInfo.label) { if (!deviceInfo.label) {
let label = ""; let label = '';
switch (index) { switch (index) {
case 0: // Default Microphone case 0: // Default Microphone
label = 'Default'; label = 'Default';
@ -409,7 +421,7 @@ export class OpenVidu {
label = wirelessMatch ? wirelessMatch.label : 'Wireless'; label = wirelessMatch ? wirelessMatch.label : 'Wireless';
break; break;
default: default:
label = "Unknown Microphone"; label = 'Unknown Microphone';
break; break;
} }
devices.push({ devices.push({
@ -417,7 +429,6 @@ export class OpenVidu {
deviceId: deviceInfo.deviceId, deviceId: deviceInfo.deviceId,
label: label label: label
}); });
} else { } else {
devices.push({ devices.push({
kind: deviceInfo.kind, kind: deviceInfo.kind,
@ -429,9 +440,8 @@ export class OpenVidu {
return resolve(devices); return resolve(devices);
}); });
} else { } else {
// Rest of platforms // Rest of platforms
deviceInfos.forEach(deviceInfo => { deviceInfos.forEach((deviceInfo) => {
if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') { if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
devices.push({ devices.push({
kind: deviceInfo.kind, kind: deviceInfo.kind,
@ -442,15 +452,14 @@ export class OpenVidu {
}); });
return resolve(devices); return resolve(devices);
} }
}).catch((error) => { })
.catch((error) => {
logger.error('Error getting devices', error); logger.error('Error getting devices', error);
return reject(error); return reject(error);
}); });
}); });
} }
/** /**
* Get a MediaStream object that you can customize before calling [[initPublisher]] (pass _MediaStreamTrack_ property of the _MediaStream_ value resolved by the Promise as `audioSource` or `videoSource` properties in [[initPublisher]]) * Get a MediaStream object that you can customize before calling [[initPublisher]] (pass _MediaStreamTrack_ property of the _MediaStream_ value resolved by the Promise as `audioSource` or `videoSource` properties in [[initPublisher]])
* *
@ -500,9 +509,8 @@ export class OpenVidu {
*/ */
getUserMedia(options: PublisherProperties): Promise<MediaStream> { getUserMedia(options: PublisherProperties): Promise<MediaStream> {
return new Promise<MediaStream>(async (resolve, reject) => { return new Promise<MediaStream>(async (resolve, reject) => {
const askForAudioStreamOnly = async (previousMediaStream: MediaStream, constraints: MediaStreamConstraints) => { const askForAudioStreamOnly = async (previousMediaStream: MediaStream, constraints: MediaStreamConstraints) => {
const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio); const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
const constraintsAux: MediaStreamConstraints = { audio: definedAudioConstraint, video: false }; const constraintsAux: MediaStreamConstraints = { audio: definedAudioConstraint, video: false };
try { try {
const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux); const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
@ -517,17 +525,17 @@ export class OpenVidu {
}); });
return reject(this.generateAudioDeviceError(error, constraintsAux)); return reject(this.generateAudioDeviceError(error, constraintsAux));
} }
} };
try { try {
const myConstraints = await this.generateMediaConstraints(options); const myConstraints = await this.generateMediaConstraints(options);
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack || if (
!!myConstraints.audioTrack && myConstraints.constraints?.video === false || (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) { (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
(!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
) {
// No need to call getUserMedia at all. Both tracks provided, or only AUDIO track provided or only VIDEO track provided // No need to call getUserMedia at all. Both tracks provided, or only AUDIO track provided or only VIDEO track provided
return resolve(this.addAlreadyProvidedTracks(myConstraints, new MediaStream())); return resolve(this.addAlreadyProvidedTracks(myConstraints, new MediaStream()));
} else { } else {
// getUserMedia must be called. AUDIO or VIDEO are requesting a new track // getUserMedia must be called. AUDIO or VIDEO are requesting a new track
@ -542,11 +550,14 @@ export class OpenVidu {
let mustAskForAudioTrackLater = false; let mustAskForAudioTrackLater = false;
if (typeof options.videoSource === 'string') { if (typeof options.videoSource === 'string') {
// Video is deviceId or screen sharing // Video is deviceId or screen sharing
if (options.videoSource === 'screen' || if (
options.videoSource === 'screen' ||
options.videoSource === 'window' || options.videoSource === 'window' ||
(platform.isElectron() && options.videoSource.startsWith('screen:'))) { (platform.isElectron() && options.videoSource.startsWith('screen:'))
) {
// Video is screen sharing // Video is screen sharing
mustAskForAudioTrackLater = !myConstraints.audioTrack && (options.audioSource !== null && options.audioSource !== false); mustAskForAudioTrackLater =
!myConstraints.audioTrack && options.audioSource !== null && options.audioSource !== false;
if (navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) { if (navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
// getDisplayMedia supported // getDisplayMedia supported
try { try {
@ -557,13 +568,11 @@ export class OpenVidu {
} else { } else {
return resolve(mediaStream); return resolve(mediaStream);
} }
} catch (error) { } catch (error) {
let errorName: OpenViduErrorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED; let errorName: OpenViduErrorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
const errorMessage = error.toString(); const errorMessage = error.toString();
return reject(new OpenViduError(errorName, errorMessage)); return reject(new OpenViduError(errorName, errorMessage));
} }
} else { } else {
// getDisplayMedia NOT supported. Can perform getUserMedia below with already calculated constraints // getDisplayMedia NOT supported. Can perform getUserMedia below with already calculated constraints
} }
@ -572,7 +581,9 @@ export class OpenVidu {
} }
} }
// Use already calculated constraints // Use already calculated constraints
const constraintsAux = mustAskForAudioTrackLater ? { video: myConstraints.constraints!.video } : myConstraints.constraints; const constraintsAux = mustAskForAudioTrackLater
? { video: myConstraints.constraints!.video }
: myConstraints.constraints;
try { try {
const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux); const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
this.addAlreadyProvidedTracks(myConstraints, mediaStream); this.addAlreadyProvidedTracks(myConstraints, mediaStream);
@ -598,7 +609,6 @@ export class OpenVidu {
}); });
} }
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
/** /**
* Disable all logging except error level * Disable all logging except error level
@ -608,7 +618,6 @@ export class OpenVidu {
} }
/* tslint:enable:no-empty */ /* tslint:enable:no-empty */
/** /**
* Set OpenVidu advanced configuration options. `configuration` is an object of type [[OpenViduAdvancedConfiguration]]. Call this method to override previous values at any moment. * Set OpenVidu advanced configuration options. `configuration` is an object of type [[OpenViduAdvancedConfiguration]]. Call this method to override previous values at any moment.
*/ */
@ -616,7 +625,6 @@ export class OpenVidu {
this.advancedConfiguration = configuration; this.advancedConfiguration = configuration;
} }
/* Hidden methods */ /* Hidden methods */
/** /**
@ -639,7 +647,7 @@ export class OpenVidu {
if (attempts > MAX_ATTEMPTS) { if (attempts > MAX_ATTEMPTS) {
clearTimeout(repeatUntilChangeOrMaxAttempts); clearTimeout(repeatUntilChangeOrMaxAttempts);
} }
publisher.getVideoDimensions().then(newDimensions => { publisher.getVideoDimensions().then((newDimensions) => {
if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) { if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
clearTimeout(repeatUntilChangeOrMaxAttempts); clearTimeout(repeatUntilChangeOrMaxAttempts);
this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height); this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
@ -651,7 +659,14 @@ export class OpenVidu {
/** /**
* @hidden * @hidden
*/ */
sendVideoDimensionsChangedEvent(publisher: Publisher, reason: string, oldWidth: number, oldHeight: number, newWidth: number, newHeight: number) { sendVideoDimensionsChangedEvent(
publisher: Publisher,
reason: string,
oldWidth: number,
oldHeight: number,
newWidth: number,
newHeight: number
) {
publisher.stream.videoDimensions = { publisher.stream.videoDimensions = {
width: newWidth || 0, width: newWidth || 0,
height: newHeight || 0 height: newHeight || 0
@ -668,12 +683,31 @@ export class OpenVidu {
if (error) { if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error); logger.error("Error sending 'streamPropertyChanged' event", error);
} else { } else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, reason)]); this.session.emitEvent('streamPropertyChanged', [
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, reason)]); new StreamPropertyChangedEvent(
this.session,
publisher.stream,
'videoDimensions',
publisher.stream.videoDimensions,
{ width: oldWidth, height: oldHeight },
reason
)
]);
publisher.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(
publisher,
publisher.stream,
'videoDimensions',
publisher.stream.videoDimensions,
{ width: oldWidth, height: oldHeight },
reason
)
]);
this.session.sendVideoData(publisher); this.session.sendVideoData(publisher);
} }
}); }
}; );
}
/** /**
* @hidden * @hidden
@ -695,13 +729,22 @@ export class OpenVidu {
if (error) { if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error); logger.error("Error sending 'streamPropertyChanged' event", error);
} else { } else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)]); this.session.emitEvent('streamPropertyChanged', [
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)]); new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
]);
publisher.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
]);
} }
}); }
);
} else { } else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)]); this.session.emitEvent('streamPropertyChanged', [
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)]); new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
]);
publisher.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
]);
} }
} }
@ -710,7 +753,6 @@ export class OpenVidu {
*/ */
generateMediaConstraints(publisherProperties: PublisherProperties): Promise<CustomMediaStreamConstraints> { generateMediaConstraints(publisherProperties: PublisherProperties): Promise<CustomMediaStreamConstraints> {
return new Promise<CustomMediaStreamConstraints>((resolve, reject) => { return new Promise<CustomMediaStreamConstraints>((resolve, reject) => {
const myConstraints: CustomMediaStreamConstraints = { const myConstraints: CustomMediaStreamConstraints = {
audioTrack: undefined, audioTrack: undefined,
videoTrack: undefined, videoTrack: undefined,
@ -718,7 +760,7 @@ export class OpenVidu {
audio: undefined, audio: undefined,
video: undefined video: undefined
} }
} };
const audioSource = publisherProperties.audioSource; const audioSource = publisherProperties.audioSource;
const videoSource = publisherProperties.videoSource; const videoSource = publisherProperties.videoSource;
@ -733,8 +775,12 @@ export class OpenVidu {
} }
if (myConstraints.constraints!.audio === false && myConstraints.constraints!.video === false) { if (myConstraints.constraints!.audio === false && myConstraints.constraints!.video === false) {
// ERROR! audioSource and videoSource cannot be both false at the same time // ERROR! audioSource and videoSource cannot be both false at the same time
return reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET, return reject(
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time")); new OpenViduError(
OpenViduErrorName.NO_INPUT_SOURCE_SET,
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time"
)
);
} }
// CASE 2: MediaStreamTracks // CASE 2: MediaStreamTracks
@ -775,7 +821,7 @@ export class OpenVidu {
height: { height: {
ideal: idealHeight ideal: idealHeight
} }
} };
} }
if (!!publisherProperties.frameRate) { if (!!publisherProperties.frameRate) {
(<MediaTrackConstraints>myConstraints.constraints!.video).frameRate = { ideal: publisherProperties.frameRate }; (<MediaTrackConstraints>myConstraints.constraints!.video).frameRate = { ideal: publisherProperties.frameRate };
@ -833,8 +879,8 @@ export class OpenVidu {
onMasterNodeCrashedNotification(response): void { onMasterNodeCrashedNotification(response): void {
console.error('Master Node has crashed'); console.error('Master Node has crashed');
this.masterNodeHasCrashed = true; this.masterNodeHasCrashed = true;
this.session.onLostConnection("nodeCrashed"); this.session.onLostConnection('nodeCrashed');
this.jsonRpcClient.close(4103, "Master Node has crashed"); this.jsonRpcClient.close(4103, 'Master Node has crashed');
} }
/** /**
@ -848,7 +894,7 @@ export class OpenVidu {
* @hidden * @hidden
*/ */
closeWs(): void { closeWs(): void {
this.jsonRpcClient.close(4102, "Connection closed by client"); this.jsonRpcClient.close(4102, 'Connection closed by client');
} }
/** /**
@ -905,7 +951,10 @@ export class OpenVidu {
case 'overconstrainederror': case 'overconstrainederror':
if (error.constraint.toLowerCase() === 'deviceid') { if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact + "' not found"; errorMessage =
"Audio input device with deviceId '" +
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact +
"' not found";
} else { } else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
@ -914,7 +963,7 @@ export class OpenVidu {
case 'notreadableerror': case 'notreadableerror':
errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE; errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
errorMessage = error.toString(); errorMessage = error.toString();
return (new OpenViduError(errorName, errorMessage)); return new OpenViduError(errorName, errorMessage);
default: default:
return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString()); return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString());
} }
@ -943,7 +992,12 @@ export class OpenVidu {
/** /**
* @hidden * @hidden
*/ */
protected configureDeviceIdOrScreensharing(myConstraints: CustomMediaStreamConstraints, publisherProperties: PublisherProperties, resolve, reject) { protected configureDeviceIdOrScreensharing(
myConstraints: CustomMediaStreamConstraints,
publisherProperties: PublisherProperties,
resolve,
reject
) {
const audioSource = publisherProperties.audioSource; const audioSource = publisherProperties.audioSource;
const videoSource = publisherProperties.videoSource; const videoSource = publisherProperties.videoSource;
if (typeof audioSource === 'string') { if (typeof audioSource === 'string') {
@ -951,22 +1005,24 @@ export class OpenVidu {
} }
if (typeof videoSource === 'string') { if (typeof videoSource === 'string') {
if (!this.isScreenShare(videoSource)) { if (!this.isScreenShare(videoSource)) {
this.setVideoSource(myConstraints, videoSource); this.setVideoSource(myConstraints, videoSource);
} else { } else {
// Screen sharing // Screen sharing
if (!this.checkScreenSharingCapabilities()) { if (!this.checkScreenSharingCapabilities()) {
const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0), Edge (>= 80) or Electron. Detected client: ' + platform.getName() + ' ' + platform.getVersion()); const error = new OpenViduError(
OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED,
'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0), Edge (>= 80) or Electron. Detected client: ' +
platform.getName() +
' ' +
platform.getVersion()
);
logger.error(error); logger.error(error);
return reject(error); return reject(error);
} else { } else {
if (platform.isElectron()) { if (platform.isElectron()) {
const prefix = "screen:"; const prefix = 'screen:';
const videoSourceString: string = videoSource; const videoSourceString: string = videoSource;
const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length); const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length);
(<any>myConstraints.constraints!.video) = { (<any>myConstraints.constraints!.video) = {
@ -976,29 +1032,45 @@ export class OpenVidu {
} }
}; };
return resolve(myConstraints); return resolve(myConstraints);
} else { } else {
if (
if (!!this.advancedConfiguration.screenShareChromeExtension && !(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) && !navigator.mediaDevices['getDisplayMedia']) { !!this.advancedConfiguration.screenShareChromeExtension &&
!(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) &&
!navigator.mediaDevices['getDisplayMedia']
) {
// Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia() // Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia()
screenSharing.getScreenConstraints((error, screenConstraints) => { screenSharing.getScreenConstraints((error, screenConstraints) => {
if (!!error || !!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen') { if (
!!error ||
(!!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen')
) {
if (error === 'permission-denied' || error === 'PermissionDeniedError') { if (error === 'permission-denied' || error === 'PermissionDeniedError') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop'); const error = new OpenViduError(
OpenViduErrorName.SCREEN_CAPTURE_DENIED,
'You must allow access to one window of your desktop'
);
logger.error(error); logger.error(error);
return reject(error); return reject(error);
} else { } else {
const extensionId = this.advancedConfiguration.screenShareChromeExtension!.split('/').pop()!!.trim(); const extensionId = this.advancedConfiguration
screenSharing.getChromeExtensionStatus(extensionId, status => { .screenShareChromeExtension!.split('/')
.pop()!!
.trim();
screenSharing.getChromeExtensionStatus(extensionId, (status) => {
if (status === 'installed-disabled') { if (status === 'installed-disabled') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension'); const error = new OpenViduError(
OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
'You must enable the screen extension'
);
logger.error(error); logger.error(error);
return reject(error); return reject(error);
} }
if (status === 'not-installed') { if (status === 'not-installed') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, (<string>this.advancedConfiguration.screenShareChromeExtension)); const error = new OpenViduError(
OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED,
<string>this.advancedConfiguration.screenShareChromeExtension
);
logger.error(error); logger.error(error);
return reject(error); return reject(error);
} }
@ -1012,32 +1084,44 @@ export class OpenVidu {
}); });
return; return;
} else { } else {
if (navigator.mediaDevices['getDisplayMedia']) { if (navigator.mediaDevices['getDisplayMedia']) {
// getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13) // getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13)
return resolve(myConstraints); return resolve(myConstraints);
} else { } else {
// Default screen sharing extension for Chrome/Opera, or is Firefox < 66 // Default screen sharing extension for Chrome/Opera, or is Firefox < 66
const firefoxString = (platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) ? publisherProperties.videoSource : undefined; const firefoxString =
platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()
? publisherProperties.videoSource
: undefined;
screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => { screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => {
if (!!error) { if (!!error) {
if (error === 'not-installed') { if (error === 'not-installed') {
const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension ? this.advancedConfiguration.screenShareChromeExtension : const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension
'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold'; ? this.advancedConfiguration.screenShareChromeExtension
: 'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl); const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl);
logger.error(err); logger.error(err);
return reject(err); return reject(err);
} else if (error === 'installed-disabled') { } else if (error === 'installed-disabled') {
const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension'); const err = new OpenViduError(
OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
'You must enable the screen extension'
);
logger.error(err); logger.error(err);
return reject(err); return reject(err);
} else if (error === 'permission-denied') { } else if (error === 'permission-denied') {
const err = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop'); const err = new OpenViduError(
OpenViduErrorName.SCREEN_CAPTURE_DENIED,
'You must allow access to one window of your desktop'
);
logger.error(err); logger.error(err);
return reject(err); return reject(err);
} else { } else {
const err = new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Unknown error when accessing screen share'); const err = new OpenViduError(
OpenViduErrorName.GENERIC_ERROR,
'Unknown error when accessing screen share'
);
logger.error(err); logger.error(err);
logger.error(error); logger.error(error);
return reject(err); return reject(err);
@ -1066,7 +1150,6 @@ export class OpenVidu {
(<MediaTrackConstraints>myConstraints.constraints!.video)['deviceId'] = { exact: videoSource }; (<MediaTrackConstraints>myConstraints.constraints!.video)['deviceId'] = { exact: videoSource };
} }
/* Private methods */ /* Private methods */
private disconnectCallback(): void { private disconnectCallback(): void {
@ -1098,25 +1181,24 @@ export class OpenVidu {
// if life is greater, nodeCrashed // if life is greater, nodeCrashed
this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => { this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => {
if (!!error) { if (!!error) {
if (this.isMasterNodeCrashed()) { if (this.isMasterNodeCrashed()) {
logger.warn('Master Node has crashed!'); logger.warn('Master Node has crashed!');
} else { } else {
logger.error(error); logger.error(error);
const notifyLostConnection = (reason, errorMsg) => { const notifyLostConnection = (reason, errorMsg) => {
logger.warn(errorMsg); logger.warn(errorMsg);
this.session.onLostConnection(reason); this.session.onLostConnection(reason);
this.jsonRpcClient.close(4101, "Reconnection fault: " + errorMsg); this.jsonRpcClient.close(4101, 'Reconnection fault: ' + errorMsg);
} };
const rpcSessionStatus = () => { const rpcSessionStatus = () => {
if (this.life === -1) { if (this.life === -1) {
// Single Master // Single Master
notifyLostConnection('networkDisconnect', 'WS successfully reconnected but the user was already evicted due to timeout'); notifyLostConnection(
'networkDisconnect',
'WS successfully reconnected but the user was already evicted due to timeout'
);
} else { } else {
// Multi Master // Multi Master
// This RPC method is only required to find out the reason of the disconnection: // This RPC method is only required to find out the reason of the disconnection:
@ -1127,10 +1209,16 @@ export class OpenVidu {
} else { } else {
if (this.life === response.life) { if (this.life === response.life) {
// If the life stored in the client matches the life stored in the server, it means that the client lost its network connection // If the life stored in the client matches the life stored in the server, it means that the client lost its network connection
notifyLostConnection('networkDisconnect', 'WS successfully reconnected but the user was already evicted due to timeout'); notifyLostConnection(
'networkDisconnect',
'WS successfully reconnected but the user was already evicted due to timeout'
);
} else { } else {
// If the life stored in the client is below the life stored in the server, it means that the Master Node has crashed // If the life stored in the client is below the life stored in the server, it means that the Master Node has crashed
notifyLostConnection('nodeCrashed', 'WS successfully reconnected to OpenVidu Server but your Master Node crashed'); notifyLostConnection(
'nodeCrashed',
'WS successfully reconnected to OpenVidu Server but your Master Node crashed'
);
} }
} }
}); });
@ -1148,7 +1236,6 @@ export class OpenVidu {
} else { } else {
rpcSessionStatus(); rpcSessionStatus();
} }
} }
} else { } else {
this.jsonRpcClient.resetPing(); this.jsonRpcClient.resetPing();
@ -1188,9 +1275,6 @@ export class OpenVidu {
} }
private isScreenShare(videoSource: string) { private isScreenShare(videoSource: string) {
return videoSource === 'screen' || return videoSource === 'screen' || videoSource === 'window' || (platform.isElectron() && videoSource.startsWith('screen:'));
videoSource === 'window' ||
(platform.isElectron() && videoSource.startsWith('screen:'))
} }
} }

View File

@ -45,7 +45,6 @@ let platform: PlatformUtils;
* See available event listeners at [[PublisherEventMap]]. * See available event listeners at [[PublisherEventMap]].
*/ */
export class Publisher extends StreamManager { export class Publisher extends StreamManager {
/** /**
* Whether the Publisher has been granted access to the requested input devices or not * Whether the Publisher has been granted access to the requested input devices or not
*/ */
@ -82,7 +81,13 @@ export class Publisher extends StreamManager {
* @hidden * @hidden
*/ */
constructor(targEl: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) { constructor(targEl: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) {
super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl); super(
new Stream(!!openvidu.session ? openvidu.session : new Session(openvidu), {
publisherProperties: properties,
mediaConstraints: {}
}),
targEl
);
platform = PlatformUtils.getInstance(); platform = PlatformUtils.getInstance();
this.properties = properties; this.properties = properties;
this.openvidu = openvidu; this.openvidu = openvidu;
@ -95,7 +100,6 @@ export class Publisher extends StreamManager {
}); });
} }
/** /**
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect * Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
* *
@ -115,7 +119,9 @@ export class Publisher extends StreamManager {
*/ */
publishAudio(enabled: boolean): void { publishAudio(enabled: boolean): void {
if (this.stream.audioActive !== enabled) { if (this.stream.audioActive !== enabled) {
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream(); const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
? this.stream.localMediaStreamWhenSubscribedToRemote!
: this.stream.getMediaStream();
affectedMediaStream.getAudioTracks().forEach((track) => { affectedMediaStream.getAudioTracks().forEach((track) => {
track.enabled = enabled; track.enabled = enabled;
}); });
@ -132,18 +138,22 @@ export class Publisher extends StreamManager {
if (error) { if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error); logger.error("Error sending 'streamPropertyChanged' event", error);
} else { } else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')]); this.session.emitEvent('streamPropertyChanged', [
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')]); new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
]);
this.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
]);
this.session.sendVideoData(this.stream.streamManager); this.session.sendVideoData(this.stream.streamManager);
} }
}); }
);
} }
this.stream.audioActive = enabled; this.stream.audioActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream'); logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
} }
} }
/** /**
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect * Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
* *
@ -169,12 +179,11 @@ export class Publisher extends StreamManager {
* will be used instead. * will be used instead.
*/ */
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> { publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (this.stream.videoActive !== enabled) { if (this.stream.videoActive !== enabled) {
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream(); ? this.stream.localMediaStreamWhenSubscribedToRemote!
: this.stream.getMediaStream();
let mustRestartMediaStream = false; let mustRestartMediaStream = false;
affectedMediaStream.getVideoTracks().forEach((track) => { affectedMediaStream.getVideoTracks().forEach((track) => {
track.enabled = enabled; track.enabled = enabled;
@ -212,13 +221,16 @@ export class Publisher extends StreamManager {
delete this.stream.lastVBFilter; delete this.stream.lastVBFilter;
}, 1); }, 1);
} }
} };
if (!!resource && resource instanceof MediaStreamTrack) { if (!!resource && resource instanceof MediaStreamTrack) {
await replaceVideoTrack(resource); await replaceVideoTrack(resource);
} else { } else {
try { try {
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: this.stream.lastVideoTrackConstraints }); const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: this.stream.lastVideoTrackConstraints
});
await replaceVideoTrack(mediaStream.getVideoTracks()[0]); await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
} catch (error) { } catch (error) {
return reject(error); return reject(error);
@ -239,11 +251,23 @@ export class Publisher extends StreamManager {
if (error) { if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error); logger.error("Error sending 'streamPropertyChanged' event", error);
} else { } else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]); this.session.emitEvent('streamPropertyChanged', [
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]); new StreamPropertyChangedEvent(
this.session,
this.stream,
'videoActive',
enabled,
!enabled,
'publishVideo'
)
]);
this.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
]);
this.session.sendVideoData(this.stream.streamManager); this.session.sendVideoData(this.stream.streamManager);
} }
}); }
);
} }
this.stream.videoActive = enabled; this.stream.videoActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream'); logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
@ -252,22 +276,19 @@ export class Publisher extends StreamManager {
}); });
} }
/** /**
* Call this method before [[Session.publish]] if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do. * Call this method before [[Session.publish]] if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
*/ */
subscribeToRemote(value?: boolean): void { subscribeToRemote(value?: boolean): void {
value = (value !== undefined) ? value : true; value = value !== undefined ? value : true;
this.isSubscribedToRemote = value; this.isSubscribedToRemote = value;
this.stream.subscribeToMyRemote(value); this.stream.subscribeToMyRemote(value);
} }
/** /**
* See [[EventDispatcher.on]] * See [[EventDispatcher.on]]
*/ */
on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this { on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
super.on(<any>type, handler); super.on(<any>type, handler);
if (type === 'streamCreated') { if (type === 'streamCreated') {
@ -292,12 +313,10 @@ export class Publisher extends StreamManager {
return this; return this;
} }
/** /**
* See [[EventDispatcher.once]] * See [[EventDispatcher.once]]
*/ */
once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this { once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
super.once(<any>type, handler); super.once(<any>type, handler);
if (type === 'streamCreated') { if (type === 'streamCreated') {
@ -322,7 +341,6 @@ export class Publisher extends StreamManager {
return this; return this;
} }
/** /**
* See [[EventDispatcher.off]] * See [[EventDispatcher.off]]
*/ */
@ -331,7 +349,6 @@ export class Publisher extends StreamManager {
return this; return this;
} }
/** /**
* Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one * Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one
* without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one * without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one
@ -359,7 +376,6 @@ export class Publisher extends StreamManager {
*/ */
initialize(): Promise<void> { initialize(): Promise<void> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
let constraints: MediaStreamConstraints = {}; let constraints: MediaStreamConstraints = {};
let constraintsAux: MediaStreamConstraints = {}; let constraintsAux: MediaStreamConstraints = {};
const timeForDialogEvent = 2000; const timeForDialogEvent = 2000;
@ -368,7 +384,7 @@ export class Publisher extends StreamManager {
const errorCallback = (openViduError: OpenViduError) => { const errorCallback = (openViduError: OpenViduError) => {
this.accessDenied = true; this.accessDenied = true;
this.accessAllowed = false; this.accessAllowed = false;
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`) logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
return reject(openViduError); return reject(openViduError);
}; };
@ -378,21 +394,27 @@ export class Publisher extends StreamManager {
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]); mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource)); mediaStream.addTrack(<MediaStreamTrack>this.properties.audioSource);
} }
if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]); mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource)); mediaStream.addTrack(<MediaStreamTrack>this.properties.videoSource);
} }
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo // Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
if (!!mediaStream.getAudioTracks()[0]) { if (!!mediaStream.getAudioTracks()[0]) {
const enabled = (this.stream.audioActive !== undefined && this.stream.audioActive !== null) ? this.stream.audioActive : !!this.stream.outboundStreamOpts.publisherProperties.publishAudio; const enabled =
this.stream.audioActive !== undefined && this.stream.audioActive !== null
? this.stream.audioActive
: !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
mediaStream.getAudioTracks()[0].enabled = enabled; mediaStream.getAudioTracks()[0].enabled = enabled;
} }
if (!!mediaStream.getVideoTracks()[0]) { if (!!mediaStream.getVideoTracks()[0]) {
const enabled = (this.stream.videoActive !== undefined && this.stream.videoActive !== null) ? this.stream.videoActive : !!this.stream.outboundStreamOpts.publisherProperties.publishVideo; const enabled =
this.stream.videoActive !== undefined && this.stream.videoActive !== null
? this.stream.videoActive
: !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
mediaStream.getVideoTracks()[0].enabled = enabled; mediaStream.getVideoTracks()[0].enabled = enabled;
} }
@ -411,16 +433,16 @@ export class Publisher extends StreamManager {
// https://w3c.github.io/mst-content-hint/#video-content-hints // https://w3c.github.io/mst-content-hint/#video-content-hints
switch (this.stream.typeOfVideo) { switch (this.stream.typeOfVideo) {
case TypeOfVideo.SCREEN: case TypeOfVideo.SCREEN:
track.contentHint = "detail"; track.contentHint = 'detail';
break; break;
case TypeOfVideo.CUSTOM: case TypeOfVideo.CUSTOM:
logger.warn("CUSTOM type video track was provided without Content Hint!"); logger.warn('CUSTOM type video track was provided without Content Hint!');
track.contentHint = "motion"; track.contentHint = 'motion';
break; break;
case TypeOfVideo.CAMERA: case TypeOfVideo.CAMERA:
case TypeOfVideo.IPCAM: case TypeOfVideo.IPCAM:
default: default:
track.contentHint = "motion"; track.contentHint = 'motion';
break; break;
} }
logger.info(`Video track Content Hint set: '${track.contentHint}'`); logger.info(`Video track Content Hint set: '${track.contentHint}'`);
@ -438,7 +460,7 @@ export class Publisher extends StreamManager {
if (this.stream.isSendVideo()) { if (this.stream.isSendVideo()) {
// Has video track // Has video track
this.getVideoDimensions().then(dimensions => { this.getVideoDimensions().then((dimensions) => {
this.stream.videoDimensions = { this.stream.videoDimensions = {
width: dimensions.width, width: dimensions.width,
height: dimensions.height height: dimensions.height
@ -491,7 +513,6 @@ export class Publisher extends StreamManager {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent); this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]); mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
successCallback(mediaStream); successCallback(mediaStream);
} catch (error) { } catch (error) {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent); this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.getAudioTracks().forEach((track) => { mediaStream.getAudioTracks().forEach((track) => {
@ -529,7 +550,6 @@ export class Publisher extends StreamManager {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = error.toString(); errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage)); errorCallback(new OpenViduError(errorName, errorMessage));
} catch (error) { } catch (error) {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage = error.toString(); errorMessage = error.toString();
@ -538,12 +558,13 @@ export class Publisher extends StreamManager {
break; break;
case 'notallowederror': case 'notallowederror':
errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED; errorName = this.stream.isSendScreen()
? OpenViduErrorName.SCREEN_CAPTURE_DENIED
: OpenViduErrorName.DEVICE_ACCESS_DENIED;
errorMessage = error.toString(); errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage)); errorCallback(new OpenViduError(errorName, errorMessage));
break; break;
case 'overconstrainederror': case 'overconstrainederror':
try { try {
const mediaStream = await navigator.mediaDevices.getUserMedia({ const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false, audio: false,
@ -554,20 +575,27 @@ export class Publisher extends StreamManager {
}); });
if (error.constraint.toLowerCase() === 'deviceid') { if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact + "' not found"; errorMessage =
"Audio input device with deviceId '" +
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact +
"' not found";
} else { } else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; errorMessage =
"Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
} }
errorCallback(new OpenViduError(errorName, errorMessage)); errorCallback(new OpenViduError(errorName, errorMessage));
} catch (error) { } catch (error) {
if (error.constraint.toLowerCase() === 'deviceid') { if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage = "Video input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found"; errorMessage =
"Video input device with deviceId '" +
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact +
"' not found";
} else { } else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'"; errorMessage =
"Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
} }
errorCallback(new OpenViduError(errorName, errorMessage)); errorCallback(new OpenViduError(errorName, errorMessage));
} }
@ -585,13 +613,15 @@ export class Publisher extends StreamManager {
errorCallback(new OpenViduError(errorName, errorMessage)); errorCallback(new OpenViduError(errorName, errorMessage));
break; break;
} }
} };
try { try {
const myConstraints = await this.openvidu.generateMediaConstraints(this.properties); const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack || if (
!!myConstraints.audioTrack && myConstraints.constraints?.video === false || (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) { (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
(!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
) {
// No need to call getUserMedia at all. MediaStreamTracks already provided // No need to call getUserMedia at all. MediaStreamTracks already provided
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream)); successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
} else { } else {
@ -603,7 +633,7 @@ export class Publisher extends StreamManager {
}; };
this.stream.setOutboundStreamOptions(outboundStreamOptions); this.stream.setOutboundStreamOptions(outboundStreamOptions);
const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio); const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint; constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
constraintsAux.video = constraints.video; constraintsAux.video = constraints.video;
startTime = Date.now(); startTime = Date.now();
@ -664,9 +694,8 @@ export class Publisher extends StreamManager {
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we * and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
* use the HTMLVideoElement properties videoWidth and videoHeight * use the HTMLVideoElement properties videoWidth and videoHeight
*/ */
getVideoDimensions(): Promise<{ width: number, height: number }> { getVideoDimensions(): Promise<{ width: number; height: number }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM // Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari(); const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari();
@ -692,7 +721,7 @@ export class Publisher extends StreamManager {
} }
return resolve({ width, height }); return resolve({ width, height });
} };
if (this.videoReference.readyState >= 1) { if (this.videoReference.readyState >= 1) {
// The video already has metadata available // The video already has metadata available
@ -739,7 +768,14 @@ export class Publisher extends StreamManager {
this.videoReference.muted = true; this.videoReference.muted = true;
this.videoReference.autoplay = true; this.videoReference.autoplay = true;
this.videoReference.controls = false; this.videoReference.controls = false;
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) { if (
platform.isSafariBrowser() ||
(platform.isIPhoneOrIPad() &&
(platform.isChromeMobileBrowser() ||
platform.isEdgeMobileBrowser() ||
platform.isOperaMobileBrowser() ||
platform.isFirefoxMobileBrowser()))
) {
this.videoReference.playsInline = true; this.videoReference.playsInline = true;
} }
this.stream.setMediaStream(mediaStream); this.stream.setMediaStream(mediaStream);
@ -753,7 +789,9 @@ export class Publisher extends StreamManager {
* @hidden * @hidden
*/ */
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void { replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
const mediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream(); const mediaStream: MediaStream = this.stream.displayMyRemote()
? this.stream.localMediaStreamWhenSubscribedToRemote!
: this.stream.getMediaStream();
let removedTrack: MediaStreamTrack; let removedTrack: MediaStreamTrack;
if (track.kind === 'video') { if (track.kind === 'video') {
removedTrack = mediaStream.getVideoTracks()[0]; removedTrack = mediaStream.getVideoTracks()[0];
@ -798,7 +836,7 @@ export class Publisher extends StreamManager {
private clearPermissionDialogTimer(startTime: number, waitTime: number): void { private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
clearTimeout(this.permissionDialogTimeout); clearTimeout(this.permissionDialogTimeout);
if ((Date.now() - startTime) > waitTime) { if (Date.now() - startTime > waitTime) {
// Permission dialog was shown and now is closed // Permission dialog was shown and now is closed
this.emitEvent('accessDialogClosed', []); this.emitEvent('accessDialogClosed', []);
} }
@ -808,19 +846,18 @@ export class Publisher extends StreamManager {
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders(); const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
let sender: RTCRtpSender | undefined; let sender: RTCRtpSender | undefined;
if (track.kind === 'video') { if (track.kind === 'video') {
sender = senders.find(s => !!s.track && s.track.kind === 'video'); sender = senders.find((s) => !!s.track && s.track.kind === 'video');
if (!sender) { if (!sender) {
throw new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'); throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
} }
} else if (track.kind === 'audio') { } else if (track.kind === 'audio') {
sender = senders.find(s => !!s.track && s.track.kind === 'audio'); sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
if (!sender) { if (!sender) {
throw new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'); throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
} }
} else { } else {
throw new Error('Unknown track kind ' + track.kind); throw new Error('Unknown track kind ' + track.kind);
} }
await (sender as RTCRtpSender).replaceTrack(track); await (sender as RTCRtpSender).replaceTrack(track);
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,6 @@ let platform: PlatformUtils;
* See available event listeners at [[StreamManagerEventMap]]. * See available event listeners at [[StreamManagerEventMap]].
*/ */
export abstract class StreamManager extends EventDispatcher { export abstract class StreamManager extends EventDispatcher {
/** /**
* The Stream represented in the DOM by the Publisher/Subscriber * The Stream represented in the DOM by the Publisher/Subscriber
*/ */
@ -126,7 +125,14 @@ export abstract class StreamManager extends EventDispatcher {
id: '', id: '',
canplayListenerAdded: false canplayListenerAdded: false
}; };
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) { if (
platform.isSafariBrowser() ||
(platform.isIPhoneOrIPad() &&
(platform.isChromeMobileBrowser() ||
platform.isEdgeMobileBrowser() ||
platform.isOperaMobileBrowser() ||
platform.isFirefoxMobileBrowser()))
) {
this.firstVideoElement.video.playsInline = true; this.firstVideoElement.video.playsInline = true;
} }
this.targetElement = targEl; this.targetElement = targEl;
@ -144,7 +150,6 @@ export abstract class StreamManager extends EventDispatcher {
* See [[EventDispatcher.on]] * See [[EventDispatcher.on]]
*/ */
on<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this { on<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler); super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
if (type === 'videoElementCreated') { if (type === 'videoElementCreated') {
@ -154,11 +159,14 @@ export abstract class StreamManager extends EventDispatcher {
} }
} }
if (type === 'streamPlaying') { if (type === 'streamPlaying') {
if (this.videos[0] && this.videos[0].video && if (
this.videos[0] &&
this.videos[0].video &&
this.videos[0].video.currentTime > 0 && this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false && this.videos[0].video.paused === false &&
this.videos[0].video.ended === false && this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) { this.videos[0].video.readyState === 4
) {
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]); this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
} }
} }
@ -180,7 +188,6 @@ export abstract class StreamManager extends EventDispatcher {
* See [[EventDispatcher.once]] * See [[EventDispatcher.once]]
*/ */
once<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this { once<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler); super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
if (type === 'videoElementCreated') { if (type === 'videoElementCreated') {
@ -189,11 +196,14 @@ export abstract class StreamManager extends EventDispatcher {
} }
} }
if (type === 'streamPlaying') { if (type === 'streamPlaying') {
if (this.videos[0] && this.videos[0].video && if (
this.videos[0] &&
this.videos[0].video &&
this.videos[0].video.currentTime > 0 && this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false && this.videos[0].video.paused === false &&
this.videos[0].video.ended === false && this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) { this.videos[0].video.readyState === 4
) {
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]); this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
} }
} }
@ -215,19 +225,20 @@ export abstract class StreamManager extends EventDispatcher {
* See [[EventDispatcher.off]] * See [[EventDispatcher.off]]
*/ */
off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this { off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
super.offAux(type, handler); super.offAux(type, handler);
if (type === 'publisherStartSpeaking') { if (type === 'publisherStartSpeaking') {
// Both StreamManager and Session can have "publisherStartSpeaking" event listeners // Both StreamManager and Session can have "publisherStartSpeaking" event listeners
const remainingStartSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length; const remainingStartSpeakingEventListeners =
this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
if (remainingStartSpeakingEventListeners === 0) { if (remainingStartSpeakingEventListeners === 0) {
this.stream.disableHarkSpeakingEvent(false); this.stream.disableHarkSpeakingEvent(false);
} }
} }
if (type === 'publisherStopSpeaking') { if (type === 'publisherStopSpeaking') {
// Both StreamManager and Session can have "publisherStopSpeaking" event listeners // Both StreamManager and Session can have "publisherStopSpeaking" event listeners
const remainingStopSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length; const remainingStopSpeakingEventListeners =
this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
if (remainingStopSpeakingEventListeners === 0) { if (remainingStopSpeakingEventListeners === 0) {
this.stream.disableHarkStoppedSpeakingEvent(false); this.stream.disableHarkStoppedSpeakingEvent(false);
} }
@ -255,7 +266,6 @@ export abstract class StreamManager extends EventDispatcher {
* Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one. * Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one.
*/ */
addVideoElement(video: HTMLVideoElement): number { addVideoElement(video: HTMLVideoElement): number {
this.initializeVideoProperties(video); this.initializeVideoProperties(video);
if (!this.remote && this.stream.displayMyRemote()) { if (!this.remote && this.stream.displayMyRemote()) {
@ -280,7 +290,7 @@ export abstract class StreamManager extends EventDispatcher {
} }
} }
this.stream.session.streamManagers.forEach(streamManager => { this.stream.session.streamManagers.forEach((streamManager) => {
streamManager.disassociateVideo(video); streamManager.disassociateVideo(video);
}); });
@ -370,12 +380,22 @@ export abstract class StreamManager extends EventDispatcher {
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms) * - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB) * - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
*/ */
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void { updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number; threshold?: number }): void {
const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {}); const currentHarkOptions = !!this.stream.harkOptions
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ? ? this.stream.harkOptions
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100); : this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
const newThreshold = (typeof publisherSpeakingEventsOptions.threshold === 'number') ? const newInterval =
publisherSpeakingEventsOptions.threshold : ((typeof currentHarkOptions.threshold === 'number') ? currentHarkOptions.threshold : -50); typeof publisherSpeakingEventsOptions.interval === 'number'
? publisherSpeakingEventsOptions.interval
: typeof currentHarkOptions.interval === 'number'
? currentHarkOptions.interval
: 100;
const newThreshold =
typeof publisherSpeakingEventsOptions.threshold === 'number'
? publisherSpeakingEventsOptions.threshold
: typeof currentHarkOptions.threshold === 'number'
? currentHarkOptions.threshold
: -50;
this.stream.harkOptions = { this.stream.harkOptions = {
interval: newInterval, interval: newInterval,
threshold: newThreshold threshold: newThreshold
@ -402,7 +422,14 @@ export abstract class StreamManager extends EventDispatcher {
video.autoplay = true; video.autoplay = true;
video.controls = false; video.controls = false;
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) { if (
platform.isSafariBrowser() ||
(platform.isIPhoneOrIPad() &&
(platform.isChromeMobileBrowser() ||
platform.isEdgeMobileBrowser() ||
platform.isOperaMobileBrowser() ||
platform.isFirefoxMobileBrowser()))
) {
video.playsInline = true; video.playsInline = true;
} }
@ -440,7 +467,7 @@ export abstract class StreamManager extends EventDispatcher {
} }
} }
this.videos.forEach(streamManagerVideo => { this.videos.forEach((streamManagerVideo) => {
// Remove oncanplay event listener (only OpenVidu browser listener, not the user ones) // Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) { if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener); streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
@ -450,12 +477,14 @@ export abstract class StreamManager extends EventDispatcher {
// Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher // Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event // and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video); streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')]); this.ee.emitEvent('videoElementDestroyed', [
new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')
]);
} }
// Remove srcObject from the video // Remove srcObject from the video
this.removeSrcObject(streamManagerVideo); this.removeSrcObject(streamManagerVideo);
// Remove from collection of videos every video managed by OpenVidu Browser // Remove from collection of videos every video managed by OpenVidu Browser
this.videos.filter(v => !v.targetElement); this.videos.filter((v) => !v.targetElement);
}); });
} }
@ -480,7 +509,7 @@ export abstract class StreamManager extends EventDispatcher {
* @hidden * @hidden
*/ */
addPlayEventToFirstVideo() { addPlayEventToFirstVideo() {
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) { if (!!this.videos[0] && !!this.videos[0].video && !this.videos[0].canplayListenerAdded) {
this.activateStreamPlayingEventExceptionTimeout(); this.activateStreamPlayingEventExceptionTimeout();
this.videos[0].video.addEventListener('canplay', this.canPlayListener); this.videos[0].video.addEventListener('canplay', this.canPlayListener);
this.videos[0].canplayListenerAdded = true; this.videos[0].canplayListenerAdded = true;
@ -491,7 +520,7 @@ export abstract class StreamManager extends EventDispatcher {
* @hidden * @hidden
*/ */
updateMediaStream(mediaStream: MediaStream) { updateMediaStream(mediaStream: MediaStream) {
this.videos.forEach(streamManagerVideo => { this.videos.forEach((streamManagerVideo) => {
streamManagerVideo.video.srcObject = mediaStream; streamManagerVideo.video.srcObject = mediaStream;
if (platform.isIonicIos()) { if (platform.isIonicIos()) {
// iOS Ionic. LIMITATION: must reinsert the video in the DOM for // iOS Ionic. LIMITATION: must reinsert the video in the DOM for
@ -569,9 +598,18 @@ export abstract class StreamManager extends EventDispatcher {
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event // Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000; const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
this.streamPlayingEventExceptionTimeout = setTimeout(() => { this.streamPlayingEventExceptionTimeout = setTimeout(() => {
const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms'; const msg =
'StreamManager of Stream ' +
this.stream.streamId +
' (' +
(this.remote ? 'Subscriber' : 'Publisher') +
') did not trigger "streamPlaying" event in ' +
msTimeout +
' ms';
logger.warn(msg); logger.warn(msg);
this.stream.session.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)]); this.stream.session.emitEvent('exception', [
new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)
]);
delete this.streamPlayingEventExceptionTimeout; delete this.streamPlayingEventExceptionTimeout;
}, msTimeout); }, msTimeout);
} }
@ -580,5 +618,4 @@ export abstract class StreamManager extends EventDispatcher {
clearTimeout(this.streamPlayingEventExceptionTimeout as any); clearTimeout(this.streamPlayingEventExceptionTimeout as any);
delete this.streamPlayingEventExceptionTimeout; delete this.streamPlayingEventExceptionTimeout;
} }
} }

View File

@ -31,7 +31,6 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
* See available event listeners at [[StreamManagerEventMap]]. * See available event listeners at [[StreamManagerEventMap]].
*/ */
export class Subscriber extends StreamManager { export class Subscriber extends StreamManager {
/** /**
* @hidden * @hidden
*/ */
@ -52,7 +51,10 @@ export class Subscriber extends StreamManager {
* @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it * @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it
*/ */
subscribeToAudio(value: boolean): Subscriber { subscribeToAudio(value: boolean): Subscriber {
this.stream.getMediaStream().getAudioTracks().forEach((track) => { this.stream
.getMediaStream()
.getAudioTracks()
.forEach((track) => {
track.enabled = value; track.enabled = value;
}); });
this.stream.audioActive = value; this.stream.audioActive = value;
@ -65,7 +67,10 @@ export class Subscriber extends StreamManager {
* @param value `true` to subscribe to the video stream, `false` to unsubscribe from it * @param value `true` to subscribe to the video stream, `false` to unsubscribe from it
*/ */
subscribeToVideo(value: boolean): Subscriber { subscribeToVideo(value: boolean): Subscriber {
this.stream.getMediaStream().getVideoTracks().forEach((track) => { this.stream
.getMediaStream()
.getVideoTracks()
.forEach((track) => {
track.enabled = value; track.enabled = value;
}); });
this.stream.videoActive = value; this.stream.videoActive = value;
@ -93,5 +98,4 @@ export class Subscriber extends StreamManager {
removedTrack.stop(); removedTrack.stop();
mediaStream.addTrack(track); mediaStream.addTrack(track);
} }
} }

View File

@ -19,7 +19,6 @@
* Defines property [[OpenViduError.name]] * Defines property [[OpenViduError.name]]
*/ */
export enum OpenViduErrorName { export enum OpenViduErrorName {
/** /**
* Browser is not supported by OpenVidu. * Browser is not supported by OpenVidu.
* Returned upon unsuccessful [[Session.connect]] * Returned upon unsuccessful [[Session.connect]]
@ -38,7 +37,7 @@ export enum OpenViduErrorName {
* error occurred at the OS, browser or web page level, which prevented access to the device. * error occurred at the OS, browser or web page level, which prevented access to the device.
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] * Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
*/ */
DEVICE_ALREADY_IN_USE = "DEVICE_ALREADY_IN_USE", DEVICE_ALREADY_IN_USE = 'DEVICE_ALREADY_IN_USE',
/** /**
* The user hasn't granted permissions to capture some desktop screen when the browser asked for them. * The user hasn't granted permissions to capture some desktop screen when the browser asked for them.
@ -122,7 +121,6 @@ export enum OpenViduErrorName {
* Simple object to identify runtime errors on the client side * Simple object to identify runtime errors on the client side
*/ */
export class OpenViduError { export class OpenViduError {
/** /**
* Uniquely identifying name of the error * Uniquely identifying name of the error
*/ */
@ -140,5 +138,4 @@ export class OpenViduError {
this.name = name; this.name = name;
this.message = message; this.message = message;
} }
} }

View File

@ -19,7 +19,6 @@
* How the video will be inserted in the DOM for Publishers and Subscribers. See [[PublisherProperties.insertMode]] and [[SubscriberProperties.insertMode]] * How the video will be inserted in the DOM for Publishers and Subscribers. See [[PublisherProperties.insertMode]] and [[SubscriberProperties.insertMode]]
*/ */
export enum VideoInsertMode { export enum VideoInsertMode {
/** /**
* Video inserted after the target element (as next sibling) * Video inserted after the target element (as next sibling)
*/ */
@ -40,5 +39,4 @@ export enum VideoInsertMode {
* Video replaces target element * Video replaces target element
*/ */
REPLACE = 'REPLACE' REPLACE = 'REPLACE'
} }

View File

@ -19,14 +19,12 @@ import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection'; import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session'; import { Session } from '../../OpenVidu/Session';
/** /**
* Triggered by: * Triggered by:
* - [[connectionCreated]] * - [[connectionCreated]]
* - [[connectionDestroyed]] * - [[connectionDestroyed]]
*/ */
export class ConnectionEvent extends Event { export class ConnectionEvent extends Event {
/** /**
* Connection object that was created or destroyed * Connection object that was created or destroyed
*/ */
@ -59,5 +57,4 @@ export class ConnectionEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -25,7 +25,6 @@ import { Event } from './Event';
* Triggered by [[connectionPropertyChanged]] * Triggered by [[connectionPropertyChanged]]
*/ */
export class ConnectionPropertyChangedEvent extends Event { export class ConnectionPropertyChangedEvent extends Event {
/** /**
* The Connection whose property has changed * The Connection whose property has changed
*/ */
@ -62,5 +61,4 @@ export class ConnectionPropertyChangedEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -20,7 +20,6 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
import { Session } from '../../OpenVidu/Session'; import { Session } from '../../OpenVidu/Session';
export abstract class Event { export abstract class Event {
/** /**
* Whether the event has a default behavior that may be prevented by calling [[Event.preventDefault]] * Whether the event has a default behavior that may be prevented by calling [[Event.preventDefault]]
*/ */
@ -81,5 +80,4 @@ export abstract class Event {
* @hidden * @hidden
*/ */
abstract callDefaultBehavior(); abstract callDefaultBehavior();
} }

View File

@ -33,7 +33,6 @@ import { StreamManagerEventMap } from './StreamManagerEventMap';
* ``` * ```
*/ */
export interface PublisherEventMap extends StreamManagerEventMap { export interface PublisherEventMap extends StreamManagerEventMap {
/** /**
* Event dispatched when the [[Publisher]] has been published to the session (see [[Session.publish]]). * Event dispatched when the [[Publisher]] has been published to the session (see [[Session.publish]]).
*/ */

View File

@ -42,7 +42,6 @@ import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
* ``` * ```
*/ */
export interface SessionEventMap extends EventMap { export interface SessionEventMap extends EventMap {
/** /**
* Event dispatched when a new user has connected to the session. * Event dispatched when a new user has connected to the session.
* *

View File

@ -36,7 +36,6 @@ import { VideoElementEvent } from '../VideoElementEvent';
* ``` * ```
*/ */
export interface StreamManagerEventMap extends EventMap { export interface StreamManagerEventMap extends EventMap {
/** /**
* Event dispatched when a new HTML video element has been inserted into DOM by OpenVidu Browser library. See * Event dispatched when a new HTML video element has been inserted into DOM by OpenVidu Browser library. See
* [Manage video players](/en/stable/cheatsheet/manage-videos) section. * [Manage video players](/en/stable/cheatsheet/manage-videos) section.

View File

@ -20,12 +20,10 @@ import { Stream } from '../../OpenVidu/Stream';
import { Subscriber } from '../../OpenVidu/Subscriber'; import { Subscriber } from '../../OpenVidu/Subscriber';
import { Event } from './Event'; import { Event } from './Event';
/** /**
* Defines property [[ExceptionEvent.name]] * Defines property [[ExceptionEvent.name]]
*/ */
export enum ExceptionEventName { export enum ExceptionEventName {
/** /**
* There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side. * There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side.
* *
@ -87,7 +85,6 @@ export enum ExceptionEventName {
* Triggered by [[SessionEventMap.exception]] * Triggered by [[SessionEventMap.exception]]
*/ */
export class ExceptionEvent extends Event { export class ExceptionEvent extends Event {
/** /**
* Name of the exception * Name of the exception
*/ */
@ -127,5 +124,4 @@ export class ExceptionEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -18,12 +18,10 @@
import { Event } from './Event'; import { Event } from './Event';
import { Filter } from '../../OpenVidu/Filter'; import { Filter } from '../../OpenVidu/Filter';
/** /**
* Defines every event dispatched by audio/video stream filters. You can subscribe to filter events by calling [[Filter.addEventListener]] * Defines every event dispatched by audio/video stream filters. You can subscribe to filter events by calling [[Filter.addEventListener]]
*/ */
export class FilterEvent extends Event { export class FilterEvent extends Event {
/** /**
* Data of the event * Data of the event
*/ */
@ -42,5 +40,4 @@ export class FilterEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -23,7 +23,6 @@ import { Connection } from '../../OpenVidu/Connection';
* Triggered by [[networkQualityLevelChanged]] * Triggered by [[networkQualityLevelChanged]]
*/ */
export class NetworkQualityLevelChangedEvent extends Event { export class NetworkQualityLevelChangedEvent extends Event {
/** /**
* New value of the network quality level * New value of the network quality level
*/ */
@ -37,7 +36,7 @@ export class NetworkQualityLevelChangedEvent extends Event {
/** /**
* Connection for whom the network quality level changed * Connection for whom the network quality level changed
*/ */
connection: Connection connection: Connection;
/** /**
* @hidden * @hidden
@ -54,5 +53,4 @@ export class NetworkQualityLevelChangedEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -20,14 +20,12 @@ import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session'; import { Session } from '../../OpenVidu/Session';
import { StreamManager } from '../../OpenVidu/StreamManager'; import { StreamManager } from '../../OpenVidu/StreamManager';
/** /**
* Triggered by: * Triggered by:
* - `publisherStartSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStartSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStartSpeaking) objects) * - `publisherStartSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStartSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStartSpeaking) objects)
* - `publisherStopSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStopSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStopSpeaking) objects) * - `publisherStopSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStopSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStopSpeaking) objects)
*/ */
export class PublisherSpeakingEvent extends Event { export class PublisherSpeakingEvent extends Event {
/** /**
* The client that started or stopped speaking * The client that started or stopped speaking
*/ */
@ -53,5 +51,4 @@ export class PublisherSpeakingEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -18,14 +18,12 @@
import { Event } from './Event'; import { Event } from './Event';
import { Session } from '../../OpenVidu/Session'; import { Session } from '../../OpenVidu/Session';
/** /**
* Triggered by: * Triggered by:
* - [[recordingStarted]] * - [[recordingStarted]]
* - [[recordingStopped]] * - [[recordingStopped]]
*/ */
export class RecordingEvent extends Event { export class RecordingEvent extends Event {
/** /**
* The recording ID generated in openvidu-server * The recording ID generated in openvidu-server
*/ */
@ -69,5 +67,4 @@ export class RecordingEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -24,12 +24,10 @@ import { OpenViduLogger } from '../Logger/OpenViduLogger';
*/ */
const logger: OpenViduLogger = OpenViduLogger.getInstance(); const logger: OpenViduLogger = OpenViduLogger.getInstance();
/** /**
* Triggered by [[sessionDisconnected]] * Triggered by [[sessionDisconnected]]
*/ */
export class SessionDisconnectedEvent extends Event { export class SessionDisconnectedEvent extends Event {
/** /**
* - "disconnect": you have called `Session.disconnect()` * - "disconnect": you have called `Session.disconnect()`
* - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()` * - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()`
@ -56,13 +54,12 @@ export class SessionDisconnectedEvent extends Event {
* @hidden * @hidden
*/ */
callDefaultBehavior() { callDefaultBehavior() {
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'"); logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
const session = <Session>this.target; const session = <Session>this.target;
// Dispose and delete all remote Connections // Dispose and delete all remote Connections
session.remoteConnections.forEach(remoteConnection => { session.remoteConnections.forEach((remoteConnection) => {
const connectionId = remoteConnection.connectionId; const connectionId = remoteConnection.connectionId;
if (!!session.remoteConnections.get(connectionId)?.stream) { if (!!session.remoteConnections.get(connectionId)?.stream) {
session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer(); session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
@ -79,5 +76,4 @@ export class SessionDisconnectedEvent extends Event {
session.remoteConnections.delete(connectionId); session.remoteConnections.delete(connectionId);
}); });
} }
} }

View File

@ -19,12 +19,10 @@ import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection'; import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session'; import { Session } from '../../OpenVidu/Session';
/** /**
* Triggered by [[SessionEventMap.signal]] * Triggered by [[SessionEventMap.signal]]
*/ */
export class SignalEvent extends Event { export class SignalEvent extends Event {
/** /**
* The type of signal. It is string `"signal"` for those signals sent with no [[SignalOptions.type]] property, and `"signal:type"` if was sent with a * The type of signal. It is string `"signal"` for those signals sent with no [[SignalOptions.type]] property, and `"signal:type"` if was sent with a
* valid [[SignalOptions.type]] property. * valid [[SignalOptions.type]] property.
@ -63,5 +61,4 @@ export class SignalEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -32,7 +32,6 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
* - `streamDestroyed` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamDestroyed) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamDestroyed) objects) * - `streamDestroyed` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamDestroyed) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamDestroyed) objects)
*/ */
export class StreamEvent extends Event { export class StreamEvent extends Event {
/** /**
* Stream object that was created or destroyed * Stream object that was created or destroyed
*/ */
@ -68,7 +67,6 @@ export class StreamEvent extends Event {
*/ */
callDefaultBehavior() { callDefaultBehavior() {
if (this.type === 'streamDestroyed') { if (this.type === 'streamDestroyed') {
if (this.target instanceof Session) { if (this.target instanceof Session) {
// Remote Stream // Remote Stream
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'"); logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
@ -82,7 +80,7 @@ export class StreamEvent extends Event {
// Delete Publisher object from OpenVidu publishers array // Delete Publisher object from OpenVidu publishers array
const openviduPublishers = (<Publisher>this.target).openvidu.publishers; const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
for (let i = 0; i < openviduPublishers.length; i++) { for (let i = 0; i < openviduPublishers.length; i++) {
if (openviduPublishers[i] === (<Publisher>this.target)) { if (openviduPublishers[i] === <Publisher>this.target) {
openviduPublishers.splice(i, 1); openviduPublishers.splice(i, 1);
break; break;
} }
@ -109,8 +107,6 @@ export class StreamEvent extends Event {
} }
} }
} }
} }
} }
} }

View File

@ -24,7 +24,6 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
* - [[streamAudioVolumeChange]] * - [[streamAudioVolumeChange]]
*/ */
export class StreamManagerEvent extends Event { export class StreamManagerEvent extends Event {
/** /**
* For `streamAudioVolumeChange` event: * For `streamAudioVolumeChange` event:
* - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume). * - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume).
@ -47,5 +46,4 @@ export class StreamManagerEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -24,7 +24,6 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
* Triggered by `streamPropertyChanged` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamPropertyChanged) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#streamPropertyChanged) objects) * Triggered by `streamPropertyChanged` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamPropertyChanged) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#streamPropertyChanged) objects)
*/ */
export class StreamPropertyChangedEvent extends Event { export class StreamPropertyChangedEvent extends Event {
/** /**
* The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property [[Stream.connection]] * The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property [[Stream.connection]]
*/ */
@ -57,7 +56,14 @@ export class StreamPropertyChangedEvent extends Event {
/** /**
* @hidden * @hidden
*/ */
constructor(target: Session | StreamManager, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object, reason: string) { constructor(
target: Session | StreamManager,
stream: Stream,
changedProperty: string,
newValue: Object,
oldValue: Object,
reason: string
) {
super(false, target, 'streamPropertyChanged'); super(false, target, 'streamPropertyChanged');
this.stream = stream; this.stream = stream;
this.changedProperty = changedProperty; this.changedProperty = changedProperty;
@ -71,5 +77,4 @@ export class StreamPropertyChangedEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -18,14 +18,12 @@
import { Event } from './Event'; import { Event } from './Event';
import { StreamManager } from '../../OpenVidu/StreamManager'; import { StreamManager } from '../../OpenVidu/StreamManager';
/** /**
* Triggered by: * Triggered by:
* - [[videoElementCreated]] * - [[videoElementCreated]]
* - [[videoElementDestroyed]] * - [[videoElementDestroyed]]
*/ */
export class VideoElementEvent extends Event { export class VideoElementEvent extends Event {
/** /**
* Video element that was created or destroyed * Video element that was created or destroyed
*/ */
@ -44,5 +42,4 @@ export class VideoElementEvent extends Event {
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehavior() {} callDefaultBehavior() {}
} }

View File

@ -29,6 +29,6 @@ export interface InboundStreamOptions {
videoActive: boolean; videoActive: boolean;
typeOfVideo: TypeOfVideo; typeOfVideo: TypeOfVideo;
frameRate: number; frameRate: number;
videoDimensions: { width: number, height: number }; videoDimensions: { width: number; height: number };
filter?: Filter; filter?: Filter;
} }

View File

@ -19,7 +19,6 @@
* See [[Session.capabilities]] * See [[Session.capabilities]]
*/ */
export interface Capabilities { export interface Capabilities {
/** /**
* `true` if the client can call [[Session.forceDisconnect]], `false` if not * `true` if the client can call [[Session.forceDisconnect]], `false` if not
*/ */
@ -39,5 +38,4 @@ export interface Capabilities {
* `true` if the client can call [[Session.subscribe]], `false` if not (true for every user for now) * `true` if the client can call [[Session.subscribe]], `false` if not (true for every user for now)
*/ */
subscribe: boolean; subscribe: boolean;
} }

View File

@ -19,7 +19,6 @@
* See [[OpenVidu.getDevices]] * See [[OpenVidu.getDevices]]
*/ */
export interface Device { export interface Device {
/** /**
* `"videoinput"`, `"audioinput"` * `"videoinput"`, `"audioinput"`
*/ */

View File

@ -19,7 +19,6 @@
* See [[OpenVidu.setAdvancedConfiguration]] * See [[OpenVidu.setAdvancedConfiguration]]
*/ */
export interface OpenViduAdvancedConfiguration { export interface OpenViduAdvancedConfiguration {
/** /**
* Array of [RTCIceServer](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer) to be used by OpenVidu Browser. By default OpenVidu will generate the required credentials to use the COTURN server hosted along OpenVidu Server * Array of [RTCIceServer](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer) to be used by OpenVidu Browser. By default OpenVidu will generate the required credentials to use the COTURN server hosted along OpenVidu Server
* You can also set this property to string 'freeice' to force the use of free STUN servers instead (got thanks to [freeice](https://github.com/DamonOehlman/freeice) library). * You can also set this property to string 'freeice' to force the use of free STUN servers instead (got thanks to [freeice](https://github.com/DamonOehlman/freeice) library).
@ -70,5 +69,4 @@ export interface OpenViduAdvancedConfiguration {
* Default to `4000`. * Default to `4000`.
*/ */
noStreamPlayingEventExceptionTimeout?: number; noStreamPlayingEventExceptionTimeout?: number;
} }

View File

@ -22,7 +22,6 @@ import { VideoInsertMode } from '../../Enums/VideoInsertMode';
* See [[OpenVidu.initPublisher]] * See [[OpenVidu.initPublisher]]
*/ */
export interface PublisherProperties { export interface PublisherProperties {
/** /**
* Which device should provide the audio source. Can be: * Which device should provide the audio source. Can be:
* - Property `deviceId` of a [[Device]] * - Property `deviceId` of a [[Device]]
@ -98,5 +97,4 @@ export interface PublisherProperties {
* Define a filter to apply in the Publisher's stream * Define a filter to apply in the Publisher's stream
*/ */
filter?: Filter; filter?: Filter;
} }

View File

@ -21,7 +21,6 @@ import { Connection } from '../../../OpenVidu/Connection';
* See [[Session.signal]] * See [[Session.signal]]
*/ */
export interface SignalOptions { export interface SignalOptions {
/** /**
* The actual message of the signal. * The actual message of the signal.
*/ */

View File

@ -17,9 +17,7 @@
import { VideoInsertMode } from '../../Enums/VideoInsertMode'; import { VideoInsertMode } from '../../Enums/VideoInsertMode';
export interface StreamManagerVideo { export interface StreamManagerVideo {
/** /**
* DOM video element displaying the StreamManager's stream * DOM video element displaying the StreamManager's stream
*/ */
@ -56,6 +54,4 @@ export interface StreamManagerVideo {
* @hidden * @hidden
*/ */
canplayListenerAdded: boolean; canplayListenerAdded: boolean;
} }

View File

@ -21,7 +21,6 @@ import { VideoInsertMode } from '../../Enums/VideoInsertMode';
* See [[Session.subscribe]] * See [[Session.subscribe]]
*/ */
export interface SubscriberProperties { export interface SubscriberProperties {
/** /**
* How the video element of the subscriber should be inserted in the DOM * How the video element of the subscriber should be inserted in the DOM
* @default VideoInsertMode.APPEND * @default VideoInsertMode.APPEND
@ -39,5 +38,4 @@ export interface SubscriberProperties {
* @default true * @default true
*/ */
subscribeToVideo?: boolean; subscribeToVideo?: boolean;
} }

View File

@ -1,61 +1,52 @@
function Mapper() { function Mapper() {
var sources = {}; var sources = {};
this.forEach = function (callback) { this.forEach = function (callback) {
for (var key in sources) { for (var key in sources) {
var source = sources[key]; var source = sources[key];
for (var key2 in source) for (var key2 in source) callback(source[key2]);
callback(source[key2]); }
};
}; };
this.get = function (id, source) { this.get = function (id, source) {
var ids = sources[source]; var ids = sources[source];
if (ids == undefined) if (ids == undefined) return undefined;
return undefined;
return ids[id]; return ids[id];
}; };
this.remove = function (id, source) { this.remove = function (id, source) {
var ids = sources[source]; var ids = sources[source];
if (ids == undefined) if (ids == undefined) return;
return;
delete ids[id]; delete ids[id];
// Check it's empty // Check it's empty
for (var i in ids) { for (var i in ids) {
return false return false;
} }
delete sources[source]; delete sources[source];
}; };
this.set = function (value, id, source) { this.set = function (value, id, source) {
if (value == undefined) if (value == undefined) return this.remove(id, source);
return this.remove(id, source);
var ids = sources[source]; var ids = sources[source];
if (ids == undefined) if (ids == undefined) sources[source] = ids = {};
sources[source] = ids = {};
ids[id] = value; ids[id] = value;
}; };
}; }
Mapper.prototype.pop = function (id, source) { Mapper.prototype.pop = function (id, source) {
var value = this.get(id, source); var value = this.get(id, source);
if (value == undefined) if (value == undefined) return undefined;
return undefined;
this.remove(id, source); this.remove(id, source);
return value; return value;
}; };
module.exports = Mapper; module.exports = Mapper;

View File

@ -17,5 +17,4 @@
var JsonRpcClient = require('./jsonrpcclient'); var JsonRpcClient = require('./jsonrpcclient');
exports.JsonRpcClient = JsonRpcClient; exports.JsonRpcClient = JsonRpcClient;

View File

@ -19,8 +19,10 @@ var RpcBuilder = require('../');
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection'); var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
var OpenViduLogger = require('../../../Logger/OpenViduLogger').OpenViduLogger; var OpenViduLogger = require('../../../Logger/OpenViduLogger').OpenViduLogger;
Date.now = Date.now || function () { Date.now =
return +new Date; Date.now ||
function () {
return +new Date();
}; };
var PING_INTERVAL = 5000; var PING_INTERVAL = 5000;
@ -51,7 +53,6 @@ var Logger = OpenViduLogger.getInstance();
* </pre> * </pre>
*/ */
function JsonRpcClient(configuration) { function JsonRpcClient(configuration) {
var self = this; var self = this;
var wsConfig = configuration.ws; var wsConfig = configuration.ws;
@ -71,13 +72,13 @@ function JsonRpcClient(configuration) {
var onerror = wsConfig.onerror; var onerror = wsConfig.onerror;
configuration.rpc.pull = function (params, request) { configuration.rpc.pull = function (params, request) {
request.reply(null, "push"); request.reply(null, 'push');
} };
wsConfig.onreconnecting = function () { wsConfig.onreconnecting = function () {
Logger.debug("--------- ONRECONNECTING -----------"); Logger.debug('--------- ONRECONNECTING -----------');
if (status === RECONNECTING) { if (status === RECONNECTING) {
Logger.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it"); Logger.error('Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it');
return; return;
} }
@ -87,12 +88,12 @@ function JsonRpcClient(configuration) {
if (onreconnecting) { if (onreconnecting) {
onreconnecting(); onreconnecting();
} }
} };
wsConfig.onreconnected = function () { wsConfig.onreconnected = function () {
Logger.debug("--------- ONRECONNECTED -----------"); Logger.debug('--------- ONRECONNECTED -----------');
if (status === CONNECTED) { if (status === CONNECTED) {
Logger.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it"); Logger.error('Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it');
return; return;
} }
status = CONNECTED; status = CONNECTED;
@ -102,12 +103,12 @@ function JsonRpcClient(configuration) {
if (onreconnected) { if (onreconnected) {
onreconnected(); onreconnected();
} }
} };
wsConfig.onconnected = function () { wsConfig.onconnected = function () {
Logger.debug("--------- ONCONNECTED -----------"); Logger.debug('--------- ONCONNECTED -----------');
if (status === CONNECTED) { if (status === CONNECTED) {
Logger.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it"); Logger.error('Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it');
return; return;
} }
status = CONNECTED; status = CONNECTED;
@ -118,10 +119,10 @@ function JsonRpcClient(configuration) {
if (onconnected) { if (onconnected) {
onconnected(); onconnected();
} }
} };
wsConfig.onerror = function (error) { wsConfig.onerror = function (error) {
Logger.debug("--------- ONERROR -----------"); Logger.debug('--------- ONERROR -----------');
status = DISCONNECTED; status = DISCONNECTED;
@ -130,7 +131,7 @@ function JsonRpcClient(configuration) {
if (onerror) { if (onerror) {
onerror(error); onerror(error);
} }
} };
var ws = new WebSocketWithReconnection(wsConfig); var ws = new WebSocketWithReconnection(wsConfig);
@ -141,16 +142,14 @@ function JsonRpcClient(configuration) {
ping_request_timeout: configuration.rpc.heartbeatRequestTimeout ping_request_timeout: configuration.rpc.heartbeatRequestTimeout
}; };
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws, var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws, function (request) {
function (request) {
Logger.debug('Received request: ' + JSON.stringify(request)); Logger.debug('Received request: ' + JSON.stringify(request));
try { try {
var func = configuration.rpc[request.method]; var func = configuration.rpc[request.method];
if (func === undefined) { if (func === undefined) {
Logger.error("Method " + request.method + " not registered in client"); Logger.error('Method ' + request.method + ' not registered in client');
} else { } else {
func(request.params, request); func(request.params, request);
} }
@ -161,17 +160,23 @@ function JsonRpcClient(configuration) {
}); });
this.send = function (method, params, callback) { this.send = function (method, params, callback) {
var requestTime = Date.now(); var requestTime = Date.now();
rpc.encode(method, params, function (error, result) { rpc.encode(method, params, function (error, result) {
if (error) { if (error) {
try { try {
Logger.error("ERROR:" + error.message + " in Request: method:" + Logger.error(
method + " params:" + JSON.stringify(params) + " request:" + 'ERROR:' +
error.request); error.message +
' in Request: method:' +
method +
' params:' +
JSON.stringify(params) +
' request:' +
error.request
);
if (error.data) { if (error.data) {
Logger.error("ERROR DATA:" + JSON.stringify(error.data)); Logger.error('ERROR DATA:' + JSON.stringify(error.data));
} }
} catch (e) {} } catch (e) {}
error.requestTime = requestTime; error.requestTime = requestTime;
@ -183,11 +188,10 @@ function JsonRpcClient(configuration) {
callback(error, result); callback(error, result);
} }
}); });
} };
function updateNotReconnectIfLessThan() { function updateNotReconnectIfLessThan() {
Logger.debug("notReconnectIfNumLessThan = " + pingNextNum + ' (old=' + Logger.debug('notReconnectIfNumLessThan = ' + pingNextNum + ' (old=' + notReconnectIfNumLessThan + ')');
notReconnectIfNumLessThan + ')');
notReconnectIfNumLessThan = pingNextNum; notReconnectIfNumLessThan = pingNextNum;
} }
@ -201,23 +205,25 @@ function JsonRpcClient(configuration) {
} }
pingNextNum++; pingNextNum++;
self.send('ping', params, (function (pingNum) { self.send(
'ping',
params,
(function (pingNum) {
return function (error, result) { return function (error, result) {
if (error) { if (error) {
Logger.debug("Error in ping request #" + pingNum + " (" + Logger.debug('Error in ping request #' + pingNum + ' (' + error.message + ')');
error.message + ")");
if (pingNum > notReconnectIfNumLessThan) { if (pingNum > notReconnectIfNumLessThan) {
enabledPings = false; enabledPings = false;
updateNotReconnectIfLessThan(); updateNotReconnectIfLessThan();
Logger.debug("Server did not respond to ping message #" + Logger.debug('Server did not respond to ping message #' + pingNum + '. Reconnecting... ');
pingNum + ". Reconnecting... ");
ws.reconnectWs(); ws.reconnectWs();
} }
} }
} };
})(pingNextNum)); })(pingNextNum)
);
} else { } else {
Logger.debug("Trying to send ping, but ping is not enabled"); Logger.debug('Trying to send ping, but ping is not enabled');
} }
} }
@ -227,7 +233,7 @@ function JsonRpcClient(configuration) {
*/ */
function usePing() { function usePing() {
if (!pingPongStarted) { if (!pingPongStarted) {
Logger.debug("Starting ping (if configured)") Logger.debug('Starting ping (if configured)');
pingPongStarted = true; pingPongStarted = true;
if (configuration.heartbeat != undefined) { if (configuration.heartbeat != undefined) {
@ -246,30 +252,29 @@ function JsonRpcClient(configuration) {
} }
this.close = function (code, reason) { this.close = function (code, reason) {
Logger.debug("Closing with code: " + code + " because: " + reason); Logger.debug('Closing with code: ' + code + ' because: ' + reason);
if (pingInterval != undefined) { if (pingInterval != undefined) {
Logger.debug("Clearing ping interval"); Logger.debug('Clearing ping interval');
clearInterval(pingInterval); clearInterval(pingInterval);
} }
pingPongStarted = false; pingPongStarted = false;
enabledPings = false; enabledPings = false;
ws.close(code, reason); ws.close(code, reason);
} };
this.reconnect = function () { this.reconnect = function () {
ws.reconnectWs(); ws.reconnectWs();
} };
this.resetPing = function () { this.resetPing = function () {
enabledPings = true; enabledPings = true;
pingNextNum = 0; pingNextNum = 0;
usePing(); usePing();
} };
this.getReadyState = function () { this.getReadyState = function () {
return ws.getReadyState(); return ws.getReadyState();
};
} }
}
module.exports = JsonRpcClient; module.exports = JsonRpcClient;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
"use strict"; 'use strict';
var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger; var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
var Logger = OpenViduLogger.getInstance(); var Logger = OpenViduLogger.getInstance();
@ -45,17 +45,14 @@ function WebSocketWithReconnection(config) {
var ws = new WebSocket(wsUri); var ws = new WebSocket(wsUri);
ws.onopen = () => { ws.onopen = () => {
Logger.debug("WebSocket connected to " + wsUri); Logger.debug('WebSocket connected to ' + wsUri);
if (config.onconnected) { if (config.onconnected) {
config.onconnected(); config.onconnected();
} }
}; };
ws.onerror = error => { ws.onerror = (error) => {
Logger.error( Logger.error('Could not connect to ' + wsUri + ' (invoking onerror if defined)', error);
"Could not connect to " + wsUri + " (invoking onerror if defined)",
error
);
if (config.onerror) { if (config.onerror) {
config.onerror(error); config.onerror(error);
} }
@ -64,31 +61,27 @@ function WebSocketWithReconnection(config) {
var reconnectionOnClose = () => { var reconnectionOnClose = () => {
if (ws.readyState === CLOSED) { if (ws.readyState === CLOSED) {
if (closing) { if (closing) {
Logger.debug("Connection closed by user"); Logger.debug('Connection closed by user');
} else { } else {
if (config.ismasternodecrashed()) { if (config.ismasternodecrashed()) {
Logger.error("Master Node has crashed. Stopping reconnection process"); Logger.error('Master Node has crashed. Stopping reconnection process');
} else { } else {
Logger.debug("Connection closed unexpectedly. Reconnecting..."); Logger.debug('Connection closed unexpectedly. Reconnecting...');
reconnect(MAX_RETRIES, 1); reconnect(MAX_RETRIES, 1);
} }
} }
} else { } else {
Logger.debug("Close callback from previous websocket. Ignoring it"); Logger.debug('Close callback from previous websocket. Ignoring it');
} }
}; };
ws.onclose = reconnectionOnClose; ws.onclose = reconnectionOnClose;
function reconnect(maxRetries, numRetries) { function reconnect(maxRetries, numRetries) {
Logger.debug( Logger.debug('reconnect (attempt #' + numRetries + ', max=' + maxRetries + ')');
"reconnect (attempt #" + numRetries + ", max=" + maxRetries + ")"
);
if (numRetries === 1) { if (numRetries === 1) {
if (reconnecting) { if (reconnecting) {
Logger.warn( Logger.warn('Trying to reconnect when already reconnecting... Ignoring this reconnection.');
"Trying to reconnect when already reconnecting... Ignoring this reconnection."
);
return; return;
} else { } else {
reconnecting = true; reconnecting = true;
@ -101,24 +94,22 @@ function WebSocketWithReconnection(config) {
} }
function addReconnectionQueryParamsIfMissing(uriString) { function addReconnectionQueryParamsIfMissing(uriString) {
var searchParams = new URLSearchParams((new URL(uriString)).search); var searchParams = new URLSearchParams(new URL(uriString).search);
if (!searchParams.has("reconnect")) { if (!searchParams.has('reconnect')) {
uriString = (Array.from(searchParams).length > 0) ? (uriString + '&reconnect=true') : (uriString + '?reconnect=true'); uriString = Array.from(searchParams).length > 0 ? uriString + '&reconnect=true' : uriString + '?reconnect=true';
} }
return uriString; return uriString;
} }
function reconnectAux(maxRetries, numRetries) { function reconnectAux(maxRetries, numRetries) {
Logger.debug("Reconnection attempt #" + numRetries); Logger.debug('Reconnection attempt #' + numRetries);
ws.close(4104, 'Connection closed for reconnection'); ws.close(4104, 'Connection closed for reconnection');
wsUri = addReconnectionQueryParamsIfMissing(wsUri); wsUri = addReconnectionQueryParamsIfMissing(wsUri);
ws = new WebSocket(wsUri); ws = new WebSocket(wsUri);
ws.onopen = () => { ws.onopen = () => {
Logger.debug( Logger.debug('Reconnected to ' + wsUri + ' after ' + numRetries + ' attempts...');
"Reconnected to " + wsUri + " after " + numRetries + " attempts..."
);
reconnecting = false; reconnecting = false;
registerMessageHandler(); registerMessageHandler();
if (config.onreconnected()) { if (config.onreconnected()) {
@ -127,8 +118,8 @@ function WebSocketWithReconnection(config) {
ws.onclose = reconnectionOnClose; ws.onclose = reconnectionOnClose;
}; };
ws.onerror = error => { ws.onerror = (error) => {
Logger.warn("Reconnection error: ", error); Logger.warn('Reconnection error: ', error);
if (numRetries === maxRetries) { if (numRetries === maxRetries) {
if (config.ondisconnect) { if (config.ondisconnect) {
config.ondisconnect(); config.ondisconnect();
@ -147,11 +138,11 @@ function WebSocketWithReconnection(config) {
}; };
this.reconnectWs = () => { this.reconnectWs = () => {
Logger.debug("reconnectWs"); Logger.debug('reconnectWs');
reconnect(MAX_RETRIES, 1); reconnect(MAX_RETRIES, 1);
}; };
this.send = message => { this.send = (message) => {
ws.send(message); ws.send(message);
}; };
@ -164,7 +155,7 @@ function WebSocketWithReconnection(config) {
this.getReadyState = () => { this.getReadyState = () => {
return ws.readyState; return ws.readyState;
} };
} }
module.exports = WebSocketWithReconnection; module.exports = WebSocketWithReconnection;

View File

@ -15,13 +15,12 @@
* *
*/ */
var defineProperty_IE8 = false;
var defineProperty_IE8 = false
if (Object.defineProperty) { if (Object.defineProperty) {
try { try {
Object.defineProperty({}, "x", {}); Object.defineProperty({}, 'x', {});
} catch (e) { } catch (e) {
defineProperty_IE8 = true defineProperty_IE8 = true;
} }
} }
@ -38,10 +37,7 @@ if (!Function.prototype.bind) {
fToBind = this, fToBind = this,
fNOP = function () {}, fNOP = function () {},
fBound = function () { fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis ? return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
this :
oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
}; };
fNOP.prototype = this.prototype; fNOP.prototype = this.prototype;
@ -51,7 +47,6 @@ if (!Function.prototype.bind) {
}; };
} }
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits'); var inherits = require('inherits');
@ -59,10 +54,8 @@ var inherits = require('inherits');
var packers = require('./packers'); var packers = require('./packers');
var Mapper = require('./Mapper'); var Mapper = require('./Mapper');
var BASE_TIMEOUT = 5000; var BASE_TIMEOUT = 5000;
function unifyResponseMethods(responseMethods) { function unifyResponseMethods(responseMethods) {
if (!responseMethods) return {}; if (!responseMethods) return {};
@ -72,11 +65,11 @@ function unifyResponseMethods(responseMethods) {
if (typeof value == 'string') if (typeof value == 'string')
responseMethods[key] = { responseMethods[key] = {
response: value response: value
}
}; };
}
return responseMethods; return responseMethods;
}; }
function unifyTransport(transport) { function unifyTransport(transport) {
if (!transport) return; if (!transport) return;
@ -88,8 +81,7 @@ function unifyTransport(transport) {
}; };
// WebSocket & DataChannel // WebSocket & DataChannel
if (transport.send instanceof Function) if (transport.send instanceof Function) return transport;
return transport;
// Message API (Inter-window & WebWorker) // Message API (Inter-window & WebWorker)
if (transport.postMessage instanceof Function) { if (transport.postMessage instanceof Function) {
@ -107,9 +99,8 @@ function unifyTransport(transport) {
if (transport.onmessage !== undefined) return; if (transport.onmessage !== undefined) return;
if (transport.pause instanceof Function) return; if (transport.pause instanceof Function) return;
throw new SyntaxError("Transport is not a function nor a valid object"); throw new SyntaxError('Transport is not a function nor a valid object');
}; }
/** /**
* Representation of a RPC notification * Representation of a RPC notification
@ -123,8 +114,8 @@ function unifyTransport(transport) {
*/ */
function RpcNotification(method, params) { function RpcNotification(method, params) {
if (defineProperty_IE8) { if (defineProperty_IE8) {
this.method = method this.method = method;
this.params = params this.params = params;
} else { } else {
Object.defineProperty(this, 'method', { Object.defineProperty(this, 'method', {
value: method, value: method,
@ -135,8 +126,7 @@ function RpcNotification(method, params) {
enumerable: true enumerable: true
}); });
} }
}; }
/** /**
* @class * @class
@ -154,56 +144,45 @@ function RpcNotification(method, params) {
function RpcBuilder(packer, options, transport, onRequest) { function RpcBuilder(packer, options, transport, onRequest) {
var self = this; var self = this;
if (!packer) if (!packer) throw new SyntaxError('Packer is not defined');
throw new SyntaxError('Packer is not defined');
if (!packer.pack || !packer.unpack) if (!packer.pack || !packer.unpack) throw new SyntaxError('Packer is invalid');
throw new SyntaxError('Packer is invalid');
var responseMethods = unifyResponseMethods(packer.responseMethods); var responseMethods = unifyResponseMethods(packer.responseMethods);
if (options instanceof Function) { if (options instanceof Function) {
if (transport != undefined) if (transport != undefined) throw new SyntaxError("There can't be parameters after onRequest");
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = options; onRequest = options;
transport = undefined; transport = undefined;
options = undefined; options = undefined;
}; }
if (options && options.send instanceof Function) { if (options && options.send instanceof Function) {
if (transport && !(transport instanceof Function)) if (transport && !(transport instanceof Function)) throw new SyntaxError('Only a function can be after transport');
throw new SyntaxError("Only a function can be after transport");
onRequest = transport; onRequest = transport;
transport = options; transport = options;
options = undefined; options = undefined;
}; }
if (transport instanceof Function) { if (transport instanceof Function) {
if (onRequest != undefined) if (onRequest != undefined) throw new SyntaxError("There can't be parameters after onRequest");
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = transport; onRequest = transport;
transport = undefined; transport = undefined;
}; }
if (transport && transport.send instanceof Function) if (transport && transport.send instanceof Function)
if (onRequest && !(onRequest instanceof Function)) if (onRequest && !(onRequest instanceof Function)) throw new SyntaxError('Only a function can be after transport');
throw new SyntaxError("Only a function can be after transport");
options = options || {}; options = options || {};
EventEmitter.call(this); EventEmitter.call(this);
if (onRequest) if (onRequest) this.on('request', onRequest);
this.on('request', onRequest);
if (defineProperty_IE8) this.peerID = options.peerID;
if (defineProperty_IE8)
this.peerID = options.peerID
else else
Object.defineProperty(this, 'peerID', { Object.defineProperty(this, 'peerID', {
value: options.peerID value: options.peerID
@ -211,55 +190,46 @@ function RpcBuilder(packer, options, transport, onRequest) {
var max_retries = options.max_retries || 0; var max_retries = options.max_retries || 0;
function transportMessage(event) { function transportMessage(event) {
self.decode(event.data || event); self.decode(event.data || event);
}; }
this.getTransport = function () { this.getTransport = function () {
return transport; return transport;
} };
this.setTransport = function (value) { this.setTransport = function (value) {
// Remove listener from old transport // Remove listener from old transport
if (transport) { if (transport) {
// W3C transports // W3C transports
if (transport.removeEventListener) if (transport.removeEventListener) transport.removeEventListener('message', transportMessage);
transport.removeEventListener('message', transportMessage);
// Node.js Streams API // Node.js Streams API
else if (transport.removeListener) else if (transport.removeListener) transport.removeListener('data', transportMessage);
transport.removeListener('data', transportMessage); }
};
// Set listener on new transport // Set listener on new transport
if (value) { if (value) {
// W3C transports // W3C transports
if (value.addEventListener) if (value.addEventListener) value.addEventListener('message', transportMessage);
value.addEventListener('message', transportMessage);
// Node.js Streams API // Node.js Streams API
else if (value.addListener) else if (value.addListener) value.addListener('data', transportMessage);
value.addListener('data', transportMessage); }
};
transport = unifyTransport(value); transport = unifyTransport(value);
} };
if (!defineProperty_IE8) if (!defineProperty_IE8)
Object.defineProperty(this, 'transport', { Object.defineProperty(this, 'transport', {
get: this.getTransport.bind(this), get: this.getTransport.bind(this),
set: this.setTransport.bind(this) set: this.setTransport.bind(this)
}) });
this.setTransport(transport); this.setTransport(transport);
var request_timeout = options.request_timeout || BASE_TIMEOUT; var request_timeout = options.request_timeout || BASE_TIMEOUT;
var ping_request_timeout = options.ping_request_timeout || request_timeout; var ping_request_timeout = options.ping_request_timeout || request_timeout;
var response_timeout = options.response_timeout || BASE_TIMEOUT; var response_timeout = options.response_timeout || BASE_TIMEOUT;
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT; var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
var requestID = 0; var requestID = 0;
var requests = new Mapper(); var requests = new Mapper();
@ -268,7 +238,6 @@ function RpcBuilder(packer, options, transport, onRequest) {
var message2Key = {}; var message2Key = {};
/** /**
* Store the response to prevent to process duplicate request later * Store the response to prevent to process duplicate request later
*/ */
@ -278,12 +247,11 @@ function RpcBuilder(packer, options, transport, onRequest) {
/** Timeout to auto-clean old responses */ /** Timeout to auto-clean old responses */
timeout: setTimeout(function () { timeout: setTimeout(function () {
responses.remove(id, dest); responses.remove(id, dest);
}, }, response_timeout)
response_timeout)
}; };
responses.set(response, id, dest); responses.set(response, id, dest);
}; }
/** /**
* Store the response to ignore duplicated messages later * Store the response to ignore duplicated messages later
@ -291,12 +259,10 @@ function RpcBuilder(packer, options, transport, onRequest) {
function storeProcessedResponse(ack, from) { function storeProcessedResponse(ack, from) {
var timeout = setTimeout(function () { var timeout = setTimeout(function () {
processedResponses.remove(ack, from); processedResponses.remove(ack, from);
}, }, duplicates_timeout);
duplicates_timeout);
processedResponses.set(timeout, ack, from); processedResponses.set(timeout, ack, from);
}; }
/** /**
* Representation of a RPC request * Representation of a RPC request
@ -316,16 +282,16 @@ function RpcBuilder(packer, options, transport, onRequest) {
this.getTransport = function () { this.getTransport = function () {
return transport; return transport;
} };
this.setTransport = function (value) { this.setTransport = function (value) {
transport = unifyTransport(value); transport = unifyTransport(value);
} };
if (!defineProperty_IE8) if (!defineProperty_IE8)
Object.defineProperty(this, 'transport', { Object.defineProperty(this, 'transport', {
get: this.getTransport.bind(this), get: this.getTransport.bind(this),
set: this.setTransport.bind(this) set: this.setTransport.bind(this)
}) });
var response = responses.get(id, from); var response = responses.get(id, from);
@ -333,8 +299,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
* @constant {Boolean} duplicated * @constant {Boolean} duplicated
*/ */
if (!(transport || self.getTransport())) { if (!(transport || self.getTransport())) {
if (defineProperty_IE8) if (defineProperty_IE8) this.duplicated = Boolean(response);
this.duplicated = Boolean(response)
else else
Object.defineProperty(this, 'duplicated', { Object.defineProperty(this, 'duplicated', {
value: Boolean(response) value: Boolean(response)
@ -343,7 +308,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
var responseMethod = responseMethods[method]; var responseMethod = responseMethods[method];
this.pack = packer.pack.bind(packer, this, id) this.pack = packer.pack.bind(packer, this, id);
/** /**
* Generate a response to this request * Generate a response to this request
@ -355,45 +320,37 @@ function RpcBuilder(packer, options, transport, onRequest) {
*/ */
this.reply = function (error, result, transport) { this.reply = function (error, result, transport) {
// Fix optional parameters // Fix optional parameters
if (error instanceof Function || error && error.send instanceof Function) { if (error instanceof Function || (error && error.send instanceof Function)) {
if (result != undefined) if (result != undefined) throw new SyntaxError("There can't be parameters after callback");
throw new SyntaxError("There can't be parameters after callback");
transport = error; transport = error;
result = null; result = null;
error = undefined; error = undefined;
} else if (result instanceof Function || } else if (result instanceof Function || (result && result.send instanceof Function)) {
result && result.send instanceof Function) { if (transport != undefined) throw new SyntaxError("There can't be parameters after callback");
if (transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = result; transport = result;
result = null; result = null;
}; }
transport = unifyTransport(transport); transport = unifyTransport(transport);
// Duplicated request, remove old response timeout // Duplicated request, remove old response timeout
if (response) if (response) clearTimeout(response.timeout);
clearTimeout(response.timeout);
if (from != undefined) { if (from != undefined) {
if (error) if (error) error.dest = from;
error.dest = from;
if (result) if (result) result.dest = from;
result.dest = from; }
};
var message; var message;
// New request or overriden one, create new response with provided data // New request or overriden one, create new response with provided data
if (error || result != undefined) { if (error || result != undefined) {
if (self.peerID != undefined) { if (self.peerID != undefined) {
if (error) if (error) error.from = self.peerID;
error.from = self.peerID; else result.from = self.peerID;
else
result.from = self.peerID;
} }
// Protocol indicates that responses has own request methods // Protocol indicates that responses has own request methods
@ -402,11 +359,8 @@ function RpcBuilder(packer, options, transport, onRequest) {
message = { message = {
error: error error: error
}; };
else { else {
var method = error ? var method = error ? responseMethod.error : responseMethod.response;
responseMethod.error :
responseMethod.response;
message = { message = {
method: method, method: method,
@ -423,14 +377,15 @@ function RpcBuilder(packer, options, transport, onRequest) {
} }
// Duplicate & not-overriden request, re-send old response // Duplicate & not-overriden request, re-send old response
else if (response) else if (response) message = response.message;
message = response.message;
// New empty reply, response null value // New empty reply, response null value
else else
message = packer.pack({ message = packer.pack(
{
result: null result: null
}, id); },
id
);
// Store the response to prevent to process a duplicated request later // Store the response to prevent to process a duplicated request later
storeResponse(message, id, from); storeResponse(message, id, from);
@ -438,15 +393,13 @@ function RpcBuilder(packer, options, transport, onRequest) {
// Return the stored response so it can be directly send back // Return the stored response so it can be directly send back
transport = transport || this.getTransport() || self.getTransport(); transport = transport || this.getTransport() || self.getTransport();
if (transport) if (transport) return transport.send(message);
return transport.send(message);
return message; return message;
}
}; };
}
inherits(RpcRequest, RpcNotification); inherits(RpcRequest, RpcNotification);
function cancel(message) { function cancel(message) {
var key = message2Key[message]; var key = message2Key[message];
if (!key) return; if (!key) return;
@ -460,7 +413,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
// Start duplicated responses timeout // Start duplicated responses timeout
storeProcessedResponse(key.id, key.dest); storeProcessedResponse(key.id, key.dest);
}; }
/** /**
* Allow to cancel a request and don't wait for a response * Allow to cancel a request and don't wait for a response
@ -470,16 +423,13 @@ function RpcBuilder(packer, options, transport, onRequest) {
this.cancel = function (message) { this.cancel = function (message) {
if (message) return cancel(message); if (message) return cancel(message);
for (var message in message2Key) for (var message in message2Key) cancel(message);
cancel(message);
}; };
this.close = function () { this.close = function () {
// Prevent to receive new messages // Prevent to receive new messages
var transport = this.getTransport(); var transport = this.getTransport();
if (transport && transport.close) if (transport && transport.close) transport.close(4003, 'Cancel request');
transport.close(4003, "Cancel request");
// Request & processed responses // Request & processed responses
this.cancel(); this.cancel();
@ -492,7 +442,6 @@ function RpcBuilder(packer, options, transport, onRequest) {
}); });
}; };
/** /**
* Generates and encode a JsonRPC 2.0 message * Generates and encode a JsonRPC 2.0 message
* *
@ -508,39 +457,36 @@ function RpcBuilder(packer, options, transport, onRequest) {
this.encode = function (method, params, dest, transport, callback) { this.encode = function (method, params, dest, transport, callback) {
// Fix optional parameters // Fix optional parameters
if (params instanceof Function) { if (params instanceof Function) {
if (dest != undefined) if (dest != undefined) throw new SyntaxError("There can't be parameters after callback");
throw new SyntaxError("There can't be parameters after callback");
callback = params; callback = params;
transport = undefined; transport = undefined;
dest = undefined; dest = undefined;
params = undefined; params = undefined;
} else if (dest instanceof Function) { } else if (dest instanceof Function) {
if (transport != undefined) if (transport != undefined) throw new SyntaxError("There can't be parameters after callback");
throw new SyntaxError("There can't be parameters after callback");
callback = dest; callback = dest;
transport = undefined; transport = undefined;
dest = undefined; dest = undefined;
} else if (transport instanceof Function) { } else if (transport instanceof Function) {
if (callback != undefined) if (callback != undefined) throw new SyntaxError("There can't be parameters after callback");
throw new SyntaxError("There can't be parameters after callback");
callback = transport; callback = transport;
transport = undefined; transport = undefined;
}; }
if (self.peerID != undefined) { if (self.peerID != undefined) {
params = params || {}; params = params || {};
params.from = self.peerID; params.from = self.peerID;
}; }
if (dest != undefined) { if (dest != undefined) {
params = params || {}; params = params || {};
params.dest = dest; params.dest = dest;
}; }
// Encode message // Encode message
var message = { var message = {
@ -558,7 +504,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
self.cancel(message); self.cancel(message);
callback(error, result); callback(error, result);
}; }
var request = { var request = {
message: message, message: message,
@ -569,7 +515,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
var encode_transport = unifyTransport(transport); var encode_transport = unifyTransport(transport);
function sendRequest(transport) { function sendRequest(transport) {
var rt = (method === 'ping' ? ping_request_timeout : request_timeout); var rt = method === 'ping' ? ping_request_timeout : request_timeout;
request.timeout = setTimeout(timeout, rt * Math.pow(2, retried++)); request.timeout = setTimeout(timeout, rt * Math.pow(2, retried++));
message2Key[message] = { message2Key[message] = {
id: id, id: id,
@ -578,11 +524,10 @@ function RpcBuilder(packer, options, transport, onRequest) {
requests.set(request, id, dest); requests.set(request, id, dest);
transport = transport || encode_transport || self.getTransport(); transport = transport || encode_transport || self.getTransport();
if (transport) if (transport) return transport.send(message);
return transport.send(message);
return message; return message;
}; }
function retry(transport) { function retry(transport) {
transport = unifyTransport(transport); transport = unifyTransport(transport);
@ -593,29 +538,27 @@ function RpcBuilder(packer, options, transport, onRequest) {
clearTimeout(timeout); clearTimeout(timeout);
return sendRequest(transport); return sendRequest(transport);
}; }
function timeout() { function timeout() {
if (retried < max_retries) if (retried < max_retries) return retry(transport);
return retry(transport);
var error = new Error('Request has timed out'); var error = new Error('Request has timed out');
error.request = message; error.request = message;
error.retry = retry; error.retry = retry;
dispatchCallback(error) dispatchCallback(error);
}; }
return sendRequest(transport); return sendRequest(transport);
}; }
// Return the packed message // Return the packed message
message = packer.pack(message); message = packer.pack(message);
transport = transport || this.getTransport(); transport = transport || this.getTransport();
if (transport) if (transport) return transport.send(message);
return transport.send(message);
return message; return message;
}; };
@ -632,15 +575,14 @@ function RpcBuilder(packer, options, transport, onRequest) {
* @throws {TypeError} - Message is not defined * @throws {TypeError} - Message is not defined
*/ */
this.decode = function (message, transport) { this.decode = function (message, transport) {
if (!message) if (!message) throw new TypeError('Message is not defined');
throw new TypeError("Message is not defined");
try { try {
message = packer.unpack(message); message = packer.unpack(message);
} catch (e) { } catch (e) {
// Ignore invalid messages // Ignore invalid messages
return console.debug(e, message); return console.debug(e, message);
}; }
var id = message.id; var id = message.id;
var ack = message.ack; var ack = message.ack;
@ -659,37 +601,34 @@ function RpcBuilder(packer, options, transport, onRequest) {
if (self.emit('request', notification)) return; if (self.emit('request', notification)) return;
return notification; return notification;
}; }
function processRequest() { function processRequest() {
// If we have a transport and it's a duplicated request, reply inmediatly // If we have a transport and it's a duplicated request, reply inmediatly
transport = unifyTransport(transport) || self.getTransport(); transport = unifyTransport(transport) || self.getTransport();
if (transport) { if (transport) {
var response = responses.get(id, from); var response = responses.get(id, from);
if (response) if (response) return transport.send(response.message);
return transport.send(response.message); }
};
var idAck = (id != undefined) ? id : ack; var idAck = id != undefined ? id : ack;
var request = new RpcRequest(method, params, idAck, from, transport); var request = new RpcRequest(method, params, idAck, from, transport);
if (self.emit('request', request)) return; if (self.emit('request', request)) return;
return request; return request;
}; }
function processResponse(request, error, result) { function processResponse(request, error, result) {
request.callback(error, result); request.callback(error, result);
}; }
function duplicatedResponse(timeout) { function duplicatedResponse(timeout) {
console.warn("Response already processed", message); console.warn('Response already processed', message);
// Update duplicated responses timeout // Update duplicated responses timeout
clearTimeout(timeout); clearTimeout(timeout);
storeProcessedResponse(ack, from); storeProcessedResponse(ack, from);
}; }
// Request, or response with own method // Request, or response with own method
if (method) { if (method) {
@ -699,23 +638,20 @@ function RpcBuilder(packer, options, transport, onRequest) {
if (request) { if (request) {
var responseMethods = request.responseMethods; var responseMethods = request.responseMethods;
if (method == responseMethods.error) if (method == responseMethods.error) return processResponse(request, params);
return processResponse(request, params);
if (method == responseMethods.response) if (method == responseMethods.response) return processResponse(request, null, params);
return processResponse(request, null, params);
return processRequest(); return processRequest();
} }
var processed = processedResponses.get(ack, from); var processed = processedResponses.get(ack, from);
if (processed) if (processed) return duplicatedResponse(processed);
return duplicatedResponse(processed);
} }
// Request // Request
return processRequest(); return processRequest();
}; }
var error = message.error; var error = message.error;
var result = message.result; var result = message.result;
@ -728,22 +664,19 @@ function RpcBuilder(packer, options, transport, onRequest) {
var request = requests.get(ack, from); var request = requests.get(ack, from);
if (!request) { if (!request) {
var processed = processedResponses.get(ack, from); var processed = processedResponses.get(ack, from);
if (processed) if (processed) return duplicatedResponse(processed);
return duplicatedResponse(processed);
return console.warn("No callback was defined for this message", message); return console.warn('No callback was defined for this message', message);
}; }
// Process response // Process response
processResponse(request, error, result); processResponse(request, error, result);
}; };
}; }
inherits(RpcBuilder, EventEmitter); inherits(RpcBuilder, EventEmitter);
RpcBuilder.RpcNotification = RpcNotification; RpcBuilder.RpcNotification = RpcNotification;
module.exports = RpcBuilder; module.exports = RpcBuilder;
var clients = require('./clients'); var clients = require('./clients');

View File

@ -12,38 +12,33 @@
*/ */
function pack(message, id) { function pack(message, id) {
var result = { var result = {
jsonrpc: "2.0" jsonrpc: '2.0'
}; };
// Request // Request
if (message.method) { if (message.method) {
result.method = message.method; result.method = message.method;
if (message.params) if (message.params) result.params = message.params;
result.params = message.params;
// Request is a notification // Request is a notification
if (id != undefined) if (id != undefined) result.id = id;
result.id = id;
} }
// Response // Response
else if (id != undefined) { else if (id != undefined) {
if (message.error) { if (message.error) {
if (message.result !== undefined) if (message.result !== undefined) throw new TypeError('Both result and error are defined');
throw new TypeError("Both result and error are defined");
result.error = message.error; result.error = message.error;
} else if (message.result !== undefined) } else if (message.result !== undefined) result.result = message.result;
result.result = message.result; else throw new TypeError('No result or error is defined');
else
throw new TypeError("No result or error is defined");
result.id = id; result.id = id;
}; }
return JSON.stringify(result); return JSON.stringify(result);
}; }
/** /**
* Unpack a JsonRPC 2.0 message * Unpack a JsonRPC 2.0 message
@ -64,23 +59,19 @@ function unpack(message) {
// Check if it's a valid message // Check if it's a valid message
var version = result.jsonrpc; var version = result.jsonrpc;
if (version !== '2.0') if (version !== '2.0') throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
// Response // Response
if (result.method == undefined) { if (result.method == undefined) {
if (result.id == undefined) if (result.id == undefined) throw new TypeError('Invalid message: ' + message);
throw new TypeError("Invalid message: " + message);
var result_defined = result.result !== undefined; var result_defined = result.result !== undefined;
var error_defined = result.error !== undefined; var error_defined = result.error !== undefined;
// Check only result or error is defined, not both or none // Check only result or error is defined, not both or none
if (result_defined && error_defined) if (result_defined && error_defined) throw new TypeError('Both result and error are defined: ' + message);
throw new TypeError("Both result and error are defined: " + message);
if (!result_defined && !error_defined) if (!result_defined && !error_defined) throw new TypeError('No result or error is defined: ' + message);
throw new TypeError("No result or error is defined: " + message);
result.ack = result.id; result.ack = result.id;
delete result.id; delete result.id;
@ -88,8 +79,7 @@ function unpack(message) {
// Return unpacked message // Return unpacked message
return result; return result;
}; }
exports.pack = pack; exports.pack = pack;
exports.unpack = unpack; exports.unpack = unpack;

View File

@ -1,10 +1,10 @@
function pack(message) { function pack(message) {
throw new TypeError("Not yet implemented"); throw new TypeError('Not yet implemented');
}; }
function unpack(message) { function unpack(message) {
throw new TypeError("Not yet implemented"); throw new TypeError('Not yet implemented');
}; }
exports.pack = pack; exports.pack = pack;
exports.unpack = unpack; exports.unpack = unpack;

View File

@ -1,6 +1,5 @@
var JsonRPC = require('./JsonRPC'); var JsonRPC = require('./JsonRPC');
var XmlRPC = require('./XmlRPC'); var XmlRPC = require('./XmlRPC');
exports.JsonRPC = JsonRPC; exports.JsonRPC = JsonRPC;
exports.XmlRPC = XmlRPC; exports.XmlRPC = XmlRPC;

View File

@ -1,42 +1,41 @@
type ConsoleFunction = (...data: any) => void; type ConsoleFunction = (...data: any) => void;
export class ConsoleLogger { export class ConsoleLogger {
/**
* @hidden
*/
logger: Console;
/** /**
* @hidden * @hidden
*/ */
logger: Console log: ConsoleFunction;
/** /**
* @hidden * @hidden
*/ */
log: ConsoleFunction info: ConsoleFunction;
/** /**
* @hidden * @hidden
*/ */
info: ConsoleFunction debug: ConsoleFunction;
/** /**
* @hidden * @hidden
*/ */
debug: ConsoleFunction warn: ConsoleFunction;
/** /**
* @hidden * @hidden
*/ */
warn: ConsoleFunction error: ConsoleFunction;
/**
* @hidden
*/
error: ConsoleFunction
constructor(console: Console) { constructor(console: Console) {
this.logger = console; this.logger = console;
this.log = console.log, (this.log = console.log),
this.info = console.info, (this.info = console.info),
this.debug = console.debug, (this.debug = console.debug),
this.warn = console.warn, (this.warn = console.warn),
this.error = console.error (this.error = console.error);
} }
} }

View File

@ -1,13 +1,12 @@
import { JL } from 'jsnlog' import { JL } from 'jsnlog';
import { OpenVidu } from "../../OpenVidu/OpenVidu"; import { OpenVidu } from '../../OpenVidu/OpenVidu';
import { ConsoleLogger } from './ConsoleLogger'; import { ConsoleLogger } from './ConsoleLogger';
import { OpenViduLoggerConfiguration } from "./OpenViduLoggerConfiguration"; import { OpenViduLoggerConfiguration } from './OpenViduLoggerConfiguration';
export class OpenViduLogger { export class OpenViduLogger {
private static instance: OpenViduLogger; private static instance: OpenViduLogger;
private JSNLOG_URL: string = "/openvidu/elk/openvidu-browser-logs"; private JSNLOG_URL: string = '/openvidu/elk/openvidu-browser-logs';
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100; private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100;
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000; private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
private MAX_LENGTH_STRING_JSON: number = 1000; private MAX_LENGTH_STRING_JSON: number = 1000;
@ -29,14 +28,16 @@ export class OpenViduLogger {
static configureJSNLog(openVidu: OpenVidu, token: string) { static configureJSNLog(openVidu: OpenVidu, token: string) {
try { try {
// If dev mode or... // If dev mode or...
if ((globalThis['LOG_JSNLOG_RESULTS']) || if (
globalThis['LOG_JSNLOG_RESULTS'] ||
// If instance is created and it is OpenVidu Pro // If instance is created and it is OpenVidu Pro
(this.instance && openVidu.isAtLeastPro (this.instance &&
openVidu.isAtLeastPro &&
// If logs are enabled // If logs are enabled
&& this.instance.isOpenViduBrowserLogsDebugActive(openVidu) this.instance.isOpenViduBrowserLogsDebugActive(openVidu) &&
// Only reconfigure it if session or finalUserId has changed // Only reconfigure it if session or finalUserId has changed
&& this.instance.canConfigureJSNLog(openVidu, this.instance))) { this.instance.canConfigureJSNLog(openVidu, this.instance))
) {
// Check if app logs can be sent // Check if app logs can be sent
// and replace console.log function to send // and replace console.log function to send
// logs of the application // logs of the application
@ -46,7 +47,7 @@ export class OpenViduLogger {
// isJSNLogSetup will not be true until completed setup // isJSNLogSetup will not be true until completed setup
this.instance.isJSNLogSetup = false; this.instance.isJSNLogSetup = false;
this.instance.info("Configuring JSNLogs."); this.instance.info('Configuring JSNLogs.');
const finalUserId = openVidu.finalUserId; const finalUserId = openVidu.finalUserId;
const sessionId = openVidu.session.sessionId; const sessionId = openVidu.session.sessionId;
@ -57,22 +58,22 @@ export class OpenViduLogger {
const parentReadyStateFunction = xhr.onreadystatechange; const parentReadyStateFunction = xhr.onreadystatechange;
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (this.isInvalidResponse(xhr)) { if (this.isInvalidResponse(xhr)) {
Object.defineProperty(xhr, "readyState", { value: 4 }); Object.defineProperty(xhr, 'readyState', { value: 4 });
Object.defineProperty(xhr, "status", { value: 200 }); Object.defineProperty(xhr, 'status', { value: 200 });
// Disable JSNLog too to not send periodically errors // Disable JSNLog too to not send periodically errors
this.instance.disableLogger(); this.instance.disableLogger();
} }
parentReadyStateFunction(); parentReadyStateFunction();
} };
// Headers to identify and authenticate logs // Headers to identify and authenticate logs
xhr.setRequestHeader('Authorization', "Basic " + btoa(`${finalUserId}%/%${sessionId}` + ":" + token)); xhr.setRequestHeader('Authorization', 'Basic ' + btoa(`${finalUserId}%/%${sessionId}` + ':' + token));
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// Additional headers for OpenVidu // Additional headers for OpenVidu
xhr.setRequestHeader('OV-Final-User-Id', finalUserId); xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
xhr.setRequestHeader('OV-Session-Id', sessionId); xhr.setRequestHeader('OV-Session-Id', sessionId);
xhr.setRequestHeader('OV-Token', token); xhr.setRequestHeader('OV-Token', token);
} };
// Creation of the appender. // Creation of the appender.
this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`); this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
@ -88,7 +89,7 @@ export class OpenViduLogger {
const getCircularReplacer = () => { const getCircularReplacer = () => {
const seen = new WeakSet(); const seen = new WeakSet();
return (key, value) => { return (key, value) => {
if (typeof value === "object" && value != null) { if (typeof value === 'object' && value != null) {
if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) { if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) {
return; return;
} }
@ -123,11 +124,11 @@ export class OpenViduLogger {
this.instance.isJSNLogSetup = true; this.instance.isJSNLogSetup = true;
this.instance.loggingSessionId = sessionId; this.instance.loggingSessionId = sessionId;
this.instance.info("JSNLog configured."); this.instance.info('JSNLog configured.');
} }
} catch (e) { } catch (e) {
// Print error // Print error
console.error("Error configuring JSNLog: "); console.error('Error configuring JSNLog: ');
console.error(e); console.error(e);
// Restore defaults values just in case any exception happen- // Restore defaults values just in case any exception happen-
this.instance.disableLogger(); this.instance.disableLogger();
@ -149,17 +150,19 @@ export class OpenViduLogger {
} }
private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean { private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean {
return openVidu.session.sessionId != logger.loggingSessionId return openVidu.session.sessionId != logger.loggingSessionId;
} }
private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) { private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) {
return openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug || return (
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app; openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app
);
} }
// Return console functions with jsnlog integration // Return console functions with jsnlog integration
private getConsoleWithJSNLog() { private getConsoleWithJSNLog() {
return function (openViduLogger: OpenViduLogger) { return (function (openViduLogger: OpenViduLogger) {
return { return {
log: function (...args) { log: function (...args) {
openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments); openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
@ -189,7 +192,7 @@ export class OpenViduLogger {
} }
} }
}; };
}(this); })(this);
} }
private replaceWindowConsole() { private replaceWindowConsole() {
@ -279,5 +282,4 @@ export class OpenViduLogger {
enableProdMode() { enableProdMode() {
this.isProdMode = true; this.isProdMode = true;
} }
} }

View File

@ -54,7 +54,11 @@ globalThis.getScreenId = function (firefoxString, callback, custom_parameter) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') { if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
callback('permission-denied'); callback('permission-denied');
} else { } else {
callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId, event.data.canRequestAudioTrack)); callback(
null,
event.data.chromeMediaSourceId,
getScreenConstraints(null, event.data.chromeMediaSourceId, event.data.canRequestAudioTrack)
);
} }
// this event listener is no more needed // this event listener is no more needed
@ -71,8 +75,7 @@ globalThis.getScreenId = function (firefoxString, callback, custom_parameter) {
if (!custom_parameter) { if (!custom_parameter) {
setTimeout(postGetSourceIdMessage, 100); setTimeout(postGetSourceIdMessage, 100);
} } else {
else {
setTimeout(function () { setTimeout(function () {
postGetSourceIdMessage(custom_parameter); postGetSourceIdMessage(custom_parameter);
}, 100); }, 100);
@ -95,7 +98,7 @@ function getScreenConstraints(error, sourceId, canRequestAudioTrack) {
if (!!canRequestAudioTrack) { if (!!canRequestAudioTrack) {
screen_constraints.audio = { screen_constraints.audio = {
mandatory: { mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop', chromeMediaSource: error ? 'screen' : 'desktop'
// echoCancellation: true // echoCancellation: true
}, },
optional: [] optional: []
@ -129,19 +132,26 @@ function postGetSourceIdMessage(custom_parameter) {
} }
if (!custom_parameter) { if (!custom_parameter) {
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage(
{
captureSourceId: true captureSourceId: true
}, '*'); },
} '*'
else if (!!custom_parameter.forEach) { );
iframe.contentWindow.postMessage({ } else if (!!custom_parameter.forEach) {
iframe.contentWindow.postMessage(
{
captureCustomSourceId: custom_parameter captureCustomSourceId: custom_parameter
}, '*'); },
} '*'
else { );
iframe.contentWindow.postMessage({ } else {
iframe.contentWindow.postMessage(
{
captureSourceIdWithAudio: true captureSourceIdWithAudio: true
}, '*'); },
'*'
);
} }
} }
@ -212,9 +222,12 @@ function postGetChromeExtensionStatusMessage() {
return; return;
} }
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage(
{
getChromeExtensionStatus: true getChromeExtensionStatus: true
}, '*'); },
'*'
);
} }
exports.getScreenId = globalThis.getScreenId; exports.getScreenId = globalThis.getScreenId;

View File

@ -20,10 +20,8 @@ if(typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof n
function onMessageCallback(data) { function onMessageCallback(data) {
// "cancel" button is clicked // "cancel" button is clicked
if (data == 'PermissionDeniedError') { if (data == 'PermissionDeniedError') {
if (screenCallback) if (screenCallback) return screenCallback('PermissionDeniedError');
return screenCallback('PermissionDeniedError'); else throw new Error('PermissionDeniedError');
else
throw new Error('PermissionDeniedError');
} }
// extension notified his presence // extension notified his presence
if (data == 'rtcmulticonnection-extension-loaded') { if (data == 'rtcmulticonnection-extension-loaded') {
@ -31,7 +29,7 @@ function onMessageCallback(data) {
} }
// extension shared temp sourceId // extension shared temp sourceId
if (data.sourceId && screenCallback) { if (data.sourceId && screenCallback) {
screenCallback(sourceId = data.sourceId, data.canRequestAudioTrack === true); screenCallback((sourceId = data.sourceId), data.canRequestAudioTrack === true);
} }
} }
@ -51,10 +49,8 @@ function isChromeExtensionAvailable(callback) {
// this function can be used to get "source-id" from the extension // this function can be used to get "source-id" from the extension
function getSourceId(callback) { function getSourceId(callback) {
if (!callback) if (!callback) throw '"callback" parameter is mandatory.';
throw '"callback" parameter is mandatory.'; if (sourceId) return callback(sourceId);
if (sourceId)
return callback(sourceId);
screenCallback = callback; screenCallback = callback;
window.postMessage('get-sourceId', '*'); window.postMessage('get-sourceId', '*');
} }
@ -67,9 +63,12 @@ function getCustomSourceId(arr, callback) {
if (sourceId) return callback(sourceId); if (sourceId) return callback(sourceId);
screenCallback = callback; screenCallback = callback;
window.postMessage({ window.postMessage(
{
'get-custom-sourceId': arr 'get-custom-sourceId': arr
}, '*'); },
'*'
);
} }
// this function can be used to get "source-id" from the extension // this function can be used to get "source-id" from the extension
@ -82,8 +81,7 @@ function getSourceIdWithAudio(callback) {
} }
function getChromeExtensionStatus(extensionid, callback) { function getChromeExtensionStatus(extensionid, callback) {
if (isFirefox) if (isFirefox) return callback('not-chrome');
return callback('not-chrome');
if (arguments.length != 2) { if (arguments.length != 2) {
callback = extensionid; callback = extensionid;
extensionid = 'lfcgfepafnobdloecchnfaclibenjold'; // default extension-id extensionid = 'lfcgfepafnobdloecchnfaclibenjold'; // default extension-id
@ -96,8 +94,7 @@ function getChromeExtensionStatus(extensionid, callback) {
setTimeout(function () { setTimeout(function () {
if (chromeMediaSource == 'screen') { if (chromeMediaSource == 'screen') {
callback('installed-disabled'); callback('installed-disabled');
} else } else callback('installed-enabled');
callback('installed-enabled');
}, 2000); }, 2000);
}; };
image.onerror = function () { image.onerror = function () {
@ -116,8 +113,7 @@ function getScreenConstraints(callback, captureSourceIdWithAudio) {
mozMediaSource: 'window', mozMediaSource: 'window',
mediaSource: 'window' mediaSource: 'window'
}; };
if (isFirefox) if (isFirefox) return callback(null, firefoxScreenConstraints);
return callback(null, firefoxScreenConstraints);
// this statement defines getUserMedia constraints // this statement defines getUserMedia constraints
// that will be used to capture content of screen // that will be used to capture content of screen
var screen_constraints = { var screen_constraints = {
@ -141,8 +137,7 @@ function getScreenConstraints(callback, captureSourceIdWithAudio) {
} }
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints); callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
}); });
} } else {
else {
getSourceId(function (sourceId) { getSourceId(function (sourceId) {
screen_constraints.mandatory.chromeMediaSourceId = sourceId; screen_constraints.mandatory.chromeMediaSourceId = sourceId;
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints); callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);

View File

@ -12,49 +12,49 @@ export class PlatformUtils {
} }
public isChromeBrowser(): boolean { public isChromeBrowser(): boolean {
return platform.name === "Chrome"; return platform.name === 'Chrome';
} }
/** /**
* @hidden * @hidden
*/ */
public isSafariBrowser(): boolean { public isSafariBrowser(): boolean {
return platform.name === "Safari"; return platform.name === 'Safari';
} }
/** /**
* @hidden * @hidden
*/ */
public isChromeMobileBrowser(): boolean { public isChromeMobileBrowser(): boolean {
return platform.name === "Chrome Mobile"; return platform.name === 'Chrome Mobile';
} }
/** /**
* @hidden * @hidden
*/ */
public isFirefoxBrowser(): boolean { public isFirefoxBrowser(): boolean {
return platform.name === "Firefox"; return platform.name === 'Firefox';
} }
/** /**
* @hidden * @hidden
*/ */
public isFirefoxMobileBrowser(): boolean { public isFirefoxMobileBrowser(): boolean {
return platform.name === "Firefox Mobile" || platform.name === "Firefox for iOS"; return platform.name === 'Firefox Mobile' || platform.name === 'Firefox for iOS';
} }
/** /**
* @hidden * @hidden
*/ */
public isOperaBrowser(): boolean { public isOperaBrowser(): boolean {
return platform.name === "Opera"; return platform.name === 'Opera';
} }
/** /**
* @hidden * @hidden
*/ */
public isOperaMobileBrowser(): boolean { public isOperaMobileBrowser(): boolean {
return platform.name === "Opera Mobile"; return platform.name === 'Opera Mobile';
} }
/** /**
@ -62,7 +62,7 @@ export class PlatformUtils {
*/ */
public isEdgeBrowser(): boolean { public isEdgeBrowser(): boolean {
const version = platform?.version ? parseFloat(platform.version) : -1; const version = platform?.version ? parseFloat(platform.version) : -1;
return platform.name === "Microsoft Edge" && version >= 80; return platform.name === 'Microsoft Edge' && version >= 80;
} }
/** /**
@ -70,38 +70,35 @@ export class PlatformUtils {
*/ */
public isEdgeMobileBrowser(): boolean { public isEdgeMobileBrowser(): boolean {
const version = platform?.version ? parseFloat(platform.version) : -1; const version = platform?.version ? parseFloat(platform.version) : -1;
return platform.name === "Microsoft Edge" && (platform.os?.family === 'Android' || platform.os?.family === 'iOS') && version > 45; return platform.name === 'Microsoft Edge' && (platform.os?.family === 'Android' || platform.os?.family === 'iOS') && version > 45;
} }
/** /**
* @hidden * @hidden
*/ */
public isAndroidBrowser(): boolean { public isAndroidBrowser(): boolean {
return platform.name === "Android Browser"; return platform.name === 'Android Browser';
} }
/** /**
* @hidden * @hidden
*/ */
public isElectron(): boolean { public isElectron(): boolean {
return platform.name === "Electron"; return platform.name === 'Electron';
} }
/** /**
* @hidden * @hidden
*/ */
public isNodeJs(): boolean { public isNodeJs(): boolean {
return platform.name === "Node.js"; return platform.name === 'Node.js';
} }
/** /**
* @hidden * @hidden
*/ */
public isSamsungBrowser(): boolean { public isSamsungBrowser(): boolean {
return ( return platform.name === 'Samsung Internet Mobile' || platform.name === 'Samsung Internet';
platform.name === "Samsung Internet Mobile" ||
platform.name === "Samsung Internet"
);
} }
/** /**
@ -109,12 +106,9 @@ export class PlatformUtils {
*/ */
public isIPhoneOrIPad(): boolean { public isIPhoneOrIPad(): boolean {
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent; const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
const isTouchable = "ontouchend" in document; const isTouchable = 'ontouchend' in document;
const isIPad = /\b(\w*Macintosh\w*)\b/.test(userAgent) && isTouchable; const isIPad = /\b(\w*Macintosh\w*)\b/.test(userAgent) && isTouchable;
const isIPhone = const isIPhone = /\b(\w*iPhone\w*)\b/.test(userAgent) && /\b(\w*Mobile\w*)\b/.test(userAgent) && isTouchable;
/\b(\w*iPhone\w*)\b/.test(userAgent) &&
/\b(\w*Mobile\w*)\b/.test(userAgent) &&
isTouchable;
return isIPad || isIPhone; return isIPad || isIPhone;
} }
@ -123,7 +117,8 @@ export class PlatformUtils {
*/ */
public isIOSWithSafari(): boolean { public isIOSWithSafari(): boolean {
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent; const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
return this.isIPhoneOrIPad() && ( return (
this.isIPhoneOrIPad() &&
/\b(\w*Apple\w*)\b/.test(navigator.vendor) && /\b(\w*Apple\w*)\b/.test(navigator.vendor) &&
/\b(\w*Safari\w*)\b/.test(userAgent) && /\b(\w*Safari\w*)\b/.test(userAgent) &&
!/\b(\w*CriOS\w*)\b/.test(userAgent) && !/\b(\w*CriOS\w*)\b/.test(userAgent) &&
@ -135,23 +130,21 @@ export class PlatformUtils {
* @hidden * @hidden
*/ */
public isIonicIos(): boolean { public isIonicIos(): boolean {
return this.isIPhoneOrIPad() && platform.ua!!.indexOf("Safari") === -1; return this.isIPhoneOrIPad() && platform.ua!!.indexOf('Safari') === -1;
} }
/** /**
* @hidden * @hidden
*/ */
public isIonicAndroid(): boolean { public isIonicAndroid(): boolean {
return ( return platform.os!!.family === 'Android' && platform.name == 'Android Browser';
platform.os!!.family === "Android" && platform.name == "Android Browser"
);
} }
/** /**
* @hidden * @hidden
*/ */
public isMobileDevice(): boolean { public isMobileDevice(): boolean {
return platform.os!!.family === "iOS" || platform.os!!.family === "Android"; return platform.os!!.family === 'iOS' || platform.os!!.family === 'Android';
} }
/** /**
@ -165,12 +158,18 @@ export class PlatformUtils {
* @hidden * @hidden
*/ */
public isChromium(): boolean { public isChromium(): boolean {
return this.isChromeBrowser() || this.isChromeMobileBrowser() || return (
this.isOperaBrowser() || this.isOperaMobileBrowser() || this.isChromeBrowser() ||
this.isEdgeBrowser() || this.isEdgeMobileBrowser() || this.isChromeMobileBrowser() ||
this.isOperaBrowser() ||
this.isOperaMobileBrowser() ||
this.isEdgeBrowser() ||
this.isEdgeMobileBrowser() ||
this.isSamsungBrowser() || this.isSamsungBrowser() ||
this.isIonicAndroid() || this.isIonicIos() || this.isIonicAndroid() ||
this.isElectron(); this.isIonicIos() ||
this.isElectron()
);
} }
/** /**
@ -196,27 +195,27 @@ export class PlatformUtils {
* @hidden * @hidden
*/ */
public getName(): string { public getName(): string {
return platform.name || ""; return platform.name || '';
} }
/** /**
* @hidden * @hidden
*/ */
public getVersion(): string { public getVersion(): string {
return platform.version || ""; return platform.version || '';
} }
/** /**
* @hidden * @hidden
*/ */
public getFamily(): string { public getFamily(): string {
return platform.os!!.family || ""; return platform.os!!.family || '';
} }
/** /**
* @hidden * @hidden
*/ */
public getDescription(): string { public getDescription(): string {
return platform.description || ""; return platform.description || '';
} }
} }

View File

@ -63,24 +63,17 @@ export class WebRtcPeer {
this.configuration = { this.configuration = {
...configuration, ...configuration,
iceServers: iceServers: !!configuration.iceServers && configuration.iceServers.length > 0 ? configuration.iceServers : freeice(),
!!configuration.iceServers && mediaStream: configuration.mediaStream !== undefined ? configuration.mediaStream : null,
configuration.iceServers.length > 0 mode: !!configuration.mode ? configuration.mode : 'sendrecv',
? configuration.iceServers id: !!configuration.id ? configuration.id : this.generateUniqueId()
: freeice(),
mediaStream:
configuration.mediaStream !== undefined
? configuration.mediaStream
: null,
mode: !!configuration.mode ? configuration.mode : "sendrecv",
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
}; };
// prettier-ignore // prettier-ignore
logger.debug(`[WebRtcPeer] configuration:\n${JSON.stringify(this.configuration, null, 2)}`); logger.debug(`[WebRtcPeer] configuration:\n${JSON.stringify(this.configuration, null, 2)}`);
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers }); this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
this.pc.addEventListener("icecandidate", (event: RTCPeerConnectionIceEvent) => { this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
if (event.candidate !== null) { if (event.candidate !== null) {
// `RTCPeerConnectionIceEvent.candidate` is supposed to be an RTCIceCandidate: // `RTCPeerConnectionIceEvent.candidate` is supposed to be an RTCIceCandidate:
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate
@ -140,11 +133,11 @@ export class WebRtcPeer {
const hasVideo = this.configuration.mediaConstraints.video; const hasVideo = this.configuration.mediaConstraints.video;
const options: RTCOfferOptions = { const options: RTCOfferOptions = {
offerToReceiveAudio: this.configuration.mode !== "sendonly" && hasAudio, offerToReceiveAudio: this.configuration.mode !== 'sendonly' && hasAudio,
offerToReceiveVideo: this.configuration.mode !== "sendonly" && hasVideo, offerToReceiveVideo: this.configuration.mode !== 'sendonly' && hasVideo
}; };
logger.debug("[createOfferLegacy] RTCPeerConnection.createOffer() options:", JSON.stringify(options)); logger.debug('[createOfferLegacy] RTCPeerConnection.createOffer() options:', JSON.stringify(options));
return this.pc.createOffer(options); return this.pc.createOffer(options);
} }
@ -156,18 +149,18 @@ export class WebRtcPeer {
async createOffer(): Promise<RTCSessionDescriptionInit> { async createOffer(): Promise<RTCSessionDescriptionInit> {
// TODO: Delete this conditional when all supported browsers are // TODO: Delete this conditional when all supported browsers are
// modern enough to implement the Transceiver methods. // modern enough to implement the Transceiver methods.
if (!("addTransceiver" in this.pc)) { if (!('addTransceiver' in this.pc)) {
logger.warn( logger.warn(
"[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}" '[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}'
); );
return this.createOfferLegacy(); return this.createOfferLegacy();
} else { } else {
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it"); logger.debug('[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it');
} }
// Spec doc: https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver // Spec doc: https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver
if (this.configuration.mode !== "recvonly") { if (this.configuration.mode !== 'recvonly') {
// To send media, assume that all desired media tracks have been // To send media, assume that all desired media tracks have been
// already added by higher level code to our MediaStream. // already added by higher level code to our MediaStream.
@ -180,24 +173,18 @@ export class WebRtcPeer {
for (const track of this.configuration.mediaStream.getTracks()) { for (const track of this.configuration.mediaStream.getTracks()) {
const tcInit: RTCRtpTransceiverInit = { const tcInit: RTCRtpTransceiverInit = {
direction: this.configuration.mode, direction: this.configuration.mode,
streams: [this.configuration.mediaStream], streams: [this.configuration.mediaStream]
}; };
if (track.kind === "video" && this.configuration.simulcast) { if (track.kind === 'video' && this.configuration.simulcast) {
// Check if the requested size is enough to ask for 3 layers. // Check if the requested size is enough to ask for 3 layers.
const trackSettings = track.getSettings(); const trackSettings = track.getSettings();
const trackConsts = track.getConstraints(); const trackConsts = track.getConstraints();
const trackWidth: number = const trackWidth: number =
trackSettings.width ?? trackSettings.width ?? (trackConsts.width as ConstrainULongRange).ideal ?? (trackConsts.width as number) ?? 0;
(trackConsts.width as ConstrainULongRange).ideal ??
(trackConsts.width as number) ??
0;
const trackHeight: number = const trackHeight: number =
trackSettings.height ?? trackSettings.height ?? (trackConsts.height as ConstrainULongRange).ideal ?? (trackConsts.height as number) ?? 0;
(trackConsts.height as ConstrainULongRange).ideal ??
(trackConsts.height as number) ??
0;
logger.info(`[createOffer] Video track dimensions: ${trackWidth}x${trackHeight}`); logger.info(`[createOffer] Video track dimensions: ${trackWidth}x${trackHeight}`);
const trackPixels = trackWidth * trackHeight; const trackPixels = trackWidth * trackHeight;
@ -215,13 +202,13 @@ export class WebRtcPeer {
const layerDiv = 2 ** (maxLayers - l - 1); const layerDiv = 2 ** (maxLayers - l - 1);
const encoding: RTCRtpEncodingParameters = { const encoding: RTCRtpEncodingParameters = {
rid: "rdiv" + layerDiv.toString(), rid: 'rdiv' + layerDiv.toString(),
// @ts-ignore -- Property missing from DOM types. // @ts-ignore -- Property missing from DOM types.
scalabilityMode: "L1T1", scalabilityMode: 'L1T1'
}; };
if (["detail", "text"].includes(track.contentHint)) { if (['detail', 'text'].includes(track.contentHint)) {
// Prioritize best resolution, for maximum picture detail. // Prioritize best resolution, for maximum picture detail.
encoding.scaleResolutionDownBy = 1.0; encoding.scaleResolutionDownBy = 1.0;
@ -237,22 +224,20 @@ export class WebRtcPeer {
const tc = this.pc.addTransceiver(track, tcInit); const tc = this.pc.addTransceiver(track, tcInit);
if (track.kind === "video") { if (track.kind === 'video') {
let sendParams = tc.sender.getParameters(); let sendParams = tc.sender.getParameters();
let needSetParams = false; let needSetParams = false;
if (!sendParams.degradationPreference?.length) { if (!sendParams.degradationPreference?.length) {
// degradationPreference for video: "balanced", "maintain-framerate", "maintain-resolution". // degradationPreference for video: "balanced", "maintain-framerate", "maintain-resolution".
// https://www.w3.org/TR/2018/CR-webrtc-20180927/#dom-rtcdegradationpreference // https://www.w3.org/TR/2018/CR-webrtc-20180927/#dom-rtcdegradationpreference
if (["detail", "text"].includes(track.contentHint)) { if (['detail', 'text'].includes(track.contentHint)) {
sendParams.degradationPreference = "maintain-resolution"; sendParams.degradationPreference = 'maintain-resolution';
} else { } else {
sendParams.degradationPreference = "balanced"; sendParams.degradationPreference = 'balanced';
} }
logger.info( logger.info(`[createOffer] Video sender Degradation Preference set: ${sendParams.degradationPreference}`);
`[createOffer] Video sender Degradation Preference set: ${sendParams.degradationPreference}`
);
// FIXME: Firefox implements degradationPreference on each individual encoding! // FIXME: Firefox implements degradationPreference on each individual encoding!
// (set it on every element of the sendParams.encodings array) // (set it on every element of the sendParams.encodings array)
@ -310,7 +295,7 @@ export class WebRtcPeer {
} }
} else { } else {
// To just receive media, create new recvonly transceivers. // To just receive media, create new recvonly transceivers.
for (const kind of ["audio", "video"]) { for (const kind of ['audio', 'video']) {
// Check if the media kind should be used. // Check if the media kind should be used.
if (!this.configuration.mediaConstraints[kind]) { if (!this.configuration.mediaConstraints[kind]) {
continue; continue;
@ -319,7 +304,7 @@ export class WebRtcPeer {
this.configuration.mediaStream = new MediaStream(); this.configuration.mediaStream = new MediaStream();
this.pc.addTransceiver(kind, { this.pc.addTransceiver(kind, {
direction: this.configuration.mode, direction: this.configuration.mode,
streams: [this.configuration.mediaStream], streams: [this.configuration.mediaStream]
}); });
} }
} }
@ -352,23 +337,21 @@ export class WebRtcPeer {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// TODO: Delete this conditional when all supported browsers are // TODO: Delete this conditional when all supported browsers are
// modern enough to implement the Transceiver methods. // modern enough to implement the Transceiver methods.
if ("getTransceivers" in this.pc) { if ('getTransceivers' in this.pc) {
logger.debug("[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it"); logger.debug('[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it');
// Ensure that the PeerConnection already contains one Transceiver // Ensure that the PeerConnection already contains one Transceiver
// for each kind of media. // for each kind of media.
// The Transceivers should have been already created internally by // The Transceivers should have been already created internally by
// the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called. // the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called.
for (const kind of ["audio", "video"]) { for (const kind of ['audio', 'video']) {
// Check if the media kind should be used. // Check if the media kind should be used.
if (!this.configuration.mediaConstraints[kind]) { if (!this.configuration.mediaConstraints[kind]) {
continue; continue;
} }
let tc = this.pc let tc = this.pc.getTransceivers().find((tc) => tc.receiver.track.kind === kind);
.getTransceivers()
.find((tc) => tc.receiver.track.kind === kind);
if (tc) { if (tc) {
// Enforce our desired direction. // Enforce our desired direction.
@ -382,27 +365,25 @@ export class WebRtcPeer {
.createAnswer() .createAnswer()
.then((sdpAnswer) => resolve(sdpAnswer)) .then((sdpAnswer) => resolve(sdpAnswer))
.catch((error) => reject(error)); .catch((error) => reject(error));
} else { } else {
// TODO: Delete else branch when all supported browsers are // TODO: Delete else branch when all supported browsers are
// modern enough to implement the Transceiver methods // modern enough to implement the Transceiver methods
let offerAudio, offerVideo = true; let offerAudio,
offerVideo = true;
if (!!this.configuration.mediaConstraints) { if (!!this.configuration.mediaConstraints) {
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ? offerAudio =
this.configuration.mediaConstraints.audio : true; typeof this.configuration.mediaConstraints.audio === 'boolean' ? this.configuration.mediaConstraints.audio : true;
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ? offerVideo =
this.configuration.mediaConstraints.video : true; typeof this.configuration.mediaConstraints.video === 'boolean' ? this.configuration.mediaConstraints.video : true;
const constraints: RTCOfferOptions = { const constraints: RTCOfferOptions = {
offerToReceiveAudio: offerAudio, offerToReceiveAudio: offerAudio,
offerToReceiveVideo: offerVideo offerToReceiveVideo: offerVideo
}; };
this.pc!.createAnswer(constraints) this.pc!.createAnswer(constraints)
.then(sdpAnswer => resolve(sdpAnswer)) .then((sdpAnswer) => resolve(sdpAnswer))
.catch(error => reject(error)); .catch((error) => reject(error));
} }
} }
// else, there is nothing to do; the legacy createAnswer() options do // else, there is nothing to do; the legacy createAnswer() options do
@ -415,7 +396,8 @@ export class WebRtcPeer {
*/ */
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> { processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.pc.setLocalDescription(offer) this.pc
.setLocalDescription(offer)
.then(() => { .then(() => {
const localDescription = this.pc.localDescription; const localDescription = this.pc.localDescription;
if (!!localDescription) { if (!!localDescription) {
@ -425,7 +407,7 @@ export class WebRtcPeer {
return reject('Local description is not defined'); return reject('Local description is not defined');
} }
}) })
.catch(error => reject(error)); .catch((error) => reject(error));
}); });
} }
@ -445,7 +427,7 @@ export class WebRtcPeer {
} }
this.setRemoteDescription(offer) this.setRemoteDescription(offer)
.then(() => resolve()) .then(() => resolve())
.catch(error => reject(error)); .catch((error) => reject(error));
}); });
} }
@ -458,9 +440,10 @@ export class WebRtcPeer {
if (this.pc.signalingState === 'closed') { if (this.pc.signalingState === 'closed') {
return reject('RTCPeerConnection is closed when trying to set local description'); return reject('RTCPeerConnection is closed when trying to set local description');
} }
this.pc.setLocalDescription(answer) this.pc
.setLocalDescription(answer)
.then(() => resolve()) .then(() => resolve())
.catch(error => reject(error)); .catch((error) => reject(error));
}); });
} }
@ -513,7 +496,10 @@ export class WebRtcPeer {
break; break;
case 'stable': case 'stable':
if (!!this.pc.remoteDescription) { if (!!this.pc.remoteDescription) {
this.pc.addIceCandidate(iceCandidate).then(() => resolve()).catch(error => reject(error)); this.pc
.addIceCandidate(iceCandidate)
.then(() => resolve())
.catch((error) => reject(error));
} else { } else {
this.iceCandidateList.push(iceCandidate); this.iceCandidateList.push(iceCandidate);
resolve(); resolve();
@ -532,7 +518,12 @@ export class WebRtcPeer {
switch (iceConnectionState) { switch (iceConnectionState) {
case 'disconnected': case 'disconnected':
// Possible network disconnection // Possible network disconnection
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection'; const msg1 =
'IceConnectionState of RTCPeerConnection ' +
this.configuration.id +
' (' +
otherId +
') change to "disconnected". Possible network disconnection';
logger.warn(msg1); logger.warn(msg1);
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1); this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
break; break;
@ -542,19 +533,27 @@ export class WebRtcPeer {
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2); this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
break; break;
case 'closed': case 'closed':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"'); logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"'
);
break; break;
case 'new': case 'new':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"'); logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"');
break; break;
case 'checking': case 'checking':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"'); logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"'
);
break; break;
case 'connected': case 'connected':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"'); logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"'
);
break; break;
case 'completed': case 'completed':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"'); logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"'
);
break; break;
} }
}); });
@ -566,10 +565,8 @@ export class WebRtcPeer {
generateUniqueId(): string { generateUniqueId(): string {
return uuidv4(); return uuidv4();
} }
} }
export class WebRtcPeerRecvonly extends WebRtcPeer { export class WebRtcPeerRecvonly extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) { constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'recvonly'; configuration.mode = 'recvonly';

View File

@ -30,18 +30,18 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
let platform: PlatformUtils; let platform: PlatformUtils;
interface WebrtcStatsConfig { interface WebrtcStatsConfig {
interval: number, interval: number;
httpEndpoint: string httpEndpoint: string;
} }
interface JSONStatsResponse { interface JSONStatsResponse {
'@timestamp': string, '@timestamp': string;
participant_id: string, participant_id: string;
session_id: string, session_id: string;
platform: string, platform: string;
platform_description: string, platform_description: string;
stream: string, stream: string;
webrtc_stats: IWebrtcStats webrtc_stats: IWebrtcStats;
} }
/** /**
@ -49,55 +49,62 @@ interface JSONStatsResponse {
*/ */
interface IWebrtcStats { interface IWebrtcStats {
inbound?: { inbound?: {
audio: { audio:
bytesReceived: number, | {
packetsReceived: number, bytesReceived: number;
packetsLost: number, packetsReceived: number;
jitter: number packetsLost: number;
} | {}, jitter: number;
video: {
bytesReceived: number,
packetsReceived: number,
packetsLost: number,
jitter?: number, // Firefox
jitterBufferDelay?: number, // Chrome
framesDecoded: number,
firCount: number,
nackCount: number,
pliCount: number,
frameHeight?: number, // Chrome
frameWidth?: number, // Chrome
framesDropped?: number, // Chrome
framesReceived?: number // Chrome
} | {}
},
outbound?: {
audio: {
bytesSent: number,
packetsSent: number,
} | {},
video: {
bytesSent: number,
packetsSent: number,
firCount: number,
framesEncoded: number,
nackCount: number,
pliCount: number,
qpSum: number,
frameHeight?: number, // Chrome
frameWidth?: number, // Chrome
framesSent?: number // Chrome
} | {}
},
candidatepair?: {
currentRoundTripTime?: number // Chrome
availableOutgoingBitrate?: number //Chrome
// availableIncomingBitrate?: number // No support for any browsers (https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableIncomingBitrate)
} }
| {};
video:
| {
bytesReceived: number;
packetsReceived: number;
packetsLost: number;
jitter?: number; // Firefox
jitterBufferDelay?: number; // Chrome
framesDecoded: number;
firCount: number;
nackCount: number;
pliCount: number;
frameHeight?: number; // Chrome
frameWidth?: number; // Chrome
framesDropped?: number; // Chrome
framesReceived?: number; // Chrome
}
| {};
}; };
outbound?: {
audio:
| {
bytesSent: number;
packetsSent: number;
}
| {};
video:
| {
bytesSent: number;
packetsSent: number;
firCount: number;
framesEncoded: number;
nackCount: number;
pliCount: number;
qpSum: number;
frameHeight?: number; // Chrome
frameWidth?: number; // Chrome
framesSent?: number; // Chrome
}
| {};
};
candidatepair?: {
currentRoundTripTime?: number; // Chrome
availableOutgoingBitrate?: number; //Chrome
// availableIncomingBitrate?: number // No support for any browsers (https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableIncomingBitrate)
};
}
export class WebRtcStats { export class WebRtcStats {
private readonly STATS_ITEM_NAME = 'webrtc-stats-config'; private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
private webRtcStatsEnabled = false; private webRtcStatsEnabled = false;
@ -114,14 +121,15 @@ export class WebRtcStats {
} }
public initWebRtcStats(): void { public initWebRtcStats(): void {
const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME); const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
if (!!webrtcObj) { if (!!webrtcObj) {
this.webRtcStatsEnabled = true; this.webRtcStatsEnabled = true;
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj); const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
// webrtc object found in local storage // webrtc object found in local storage
logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId); logger.warn(
'WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId
);
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig)); logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
this.POST_URL = webrtcStatsConfig.httpEndpoint; this.POST_URL = webrtcStatsConfig.httpEndpoint;
@ -130,7 +138,6 @@ export class WebRtcStats {
this.webRtcStatsIntervalId = setInterval(async () => { this.webRtcStatsIntervalId = setInterval(async () => {
await this.sendStatsToHttpEndpoint(); await this.sendStatsToHttpEndpoint();
}, this.statsInterval * 1000); }, this.statsInterval * 1000);
} else { } else {
logger.debug('WebRtc stats not enabled'); logger.debug('WebRtc stats not enabled');
} }
@ -206,7 +213,6 @@ export class WebRtcStats {
// - ¿React Native? // - ¿React Native?
public getSelectedIceCandidateInfo(): Promise<any> { public getSelectedIceCandidateInfo(): Promise<any> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats(); const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
let transportStat; let transportStat;
const candidatePairs: Map<string, any> = new Map(); const candidatePairs: Map<string, any> = new Map();
@ -230,7 +236,7 @@ export class WebRtcStats {
}); });
let selectedCandidatePair; let selectedCandidatePair;
if (transportStat != null) { if (transportStat != null) {
const selectedCandidatePairId = transportStat.selectedCandidatePairId const selectedCandidatePairId = transportStat.selectedCandidatePairId;
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId); selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
} else { } else {
// This is basically Firefox // This is basically Firefox
@ -250,9 +256,11 @@ export class WebRtcStats {
if (!!finalLocalCandidate) { if (!!finalLocalCandidate) {
const candList = this.stream.getLocalIceCandidateList(); const candList = this.stream.getLocalIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => { const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate && return (
!!c.candidate &&
(c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) && (c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
c.candidate.indexOf(finalLocalCandidate.port) >= 0); c.candidate.indexOf(finalLocalCandidate.port) >= 0
);
}); });
finalLocalCandidate.raw = []; finalLocalCandidate.raw = [];
for (let c of cand) { for (let c of cand) {
@ -266,9 +274,11 @@ export class WebRtcStats {
if (!!finalRemoteCandidate) { if (!!finalRemoteCandidate) {
const candList = this.stream.getRemoteIceCandidateList(); const candList = this.stream.getRemoteIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => { const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate && return (
!!c.candidate &&
(c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) && (c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
c.candidate.indexOf(finalRemoteCandidate.port) >= 0); c.candidate.indexOf(finalRemoteCandidate.port) >= 0
);
}); });
finalRemoteCandidate.raw = []; finalRemoteCandidate.raw = [];
for (let c of cand) { for (let c of cand) {
@ -288,7 +298,9 @@ export class WebRtcStats {
public stopWebRtcStats() { public stopWebRtcStats() {
if (this.webRtcStatsEnabled) { if (this.webRtcStatsEnabled) {
clearInterval(this.webRtcStatsIntervalId); clearInterval(this.webRtcStatsIntervalId);
logger.warn('WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId); logger.warn(
'WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId
);
} }
} }
@ -299,10 +311,9 @@ export class WebRtcStats {
'Content-type': 'application/json' 'Content-type': 'application/json'
}, },
body: JSON.stringify(response), body: JSON.stringify(response),
method: 'POST', method: 'POST'
}; };
await fetch(url, configuration); await fetch(url, configuration);
} catch (error) { } catch (error) {
logger.error(`sendStats error: ${JSON.stringify(error)}`); logger.error(`sendStats error: ${JSON.stringify(error)}`);
} }
@ -350,9 +361,7 @@ export class WebRtcStats {
// - ¿Ionic? // - ¿Ionic?
// - ¿React Native? // - ¿React Native?
public async getCommonStats(): Promise<IWebrtcStats> { public async getCommonStats(): Promise<IWebrtcStats> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats(); const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
const response: IWebrtcStats = this.getWebRtcStatsResponseOutline(); const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
@ -360,11 +369,10 @@ export class WebRtcStats {
const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime']; const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
statsReport.forEach((stat: any) => { statsReport.forEach((stat: any) => {
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind; let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
const addStat = (direction: string, key: string): void => { const addStat = (direction: string, key: string): void => {
if (stat[key] != null && response[direction] != null) { if (stat[key] != null && response[direction] != null) {
if (!mediaType && (videoTrackStats.indexOf(key) > -1)) { if (!mediaType && videoTrackStats.indexOf(key) > -1) {
mediaType = 'video'; mediaType = 'video';
} }
if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) { if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
@ -374,10 +382,10 @@ export class WebRtcStats {
response[direction][key] = Number(stat[key]); response[direction][key] = Number(stat[key]);
} }
} }
} };
switch (stat.type) { switch (stat.type) {
case "outbound-rtp": case 'outbound-rtp':
addStat('outbound', 'bytesSent'); addStat('outbound', 'bytesSent');
addStat('outbound', 'packetsSent'); addStat('outbound', 'packetsSent');
addStat('outbound', 'framesEncoded'); addStat('outbound', 'framesEncoded');
@ -386,7 +394,7 @@ export class WebRtcStats {
addStat('outbound', 'pliCount'); addStat('outbound', 'pliCount');
addStat('outbound', 'qpSum'); addStat('outbound', 'qpSum');
break; break;
case "inbound-rtp": case 'inbound-rtp':
addStat('inbound', 'bytesReceived'); addStat('inbound', 'bytesReceived');
addStat('inbound', 'packetsReceived'); addStat('inbound', 'packetsReceived');
addStat('inbound', 'packetsLost'); addStat('inbound', 'packetsLost');
@ -421,7 +429,6 @@ export class WebRtcStats {
logger.error('Error getting common stats: ', error); logger.error('Error getting common stats: ', error);
return reject(error); return reject(error);
} }
}); });
} }
@ -455,5 +462,4 @@ export class WebRtcStats {
}; };
} }
} }
} }

View File

@ -45,4 +45,4 @@ export { StreamManagerEventMap } from './OpenViduInternal/Events/EventMap/Stream
export { PublisherEventMap } from './OpenViduInternal/Events/EventMap/PublisherEventMap'; export { PublisherEventMap } from './OpenViduInternal/Events/EventMap/PublisherEventMap';
// Disable jsnlog when library is loaded // Disable jsnlog when library is loaded
JL.setOptions({ enabled: false }) JL.setOptions({ enabled: false });