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
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();
/**
* 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
*/
export class Connection {
/**
* Unique identifier of the connection
*/
@ -125,38 +123,46 @@ export class Connection {
logger.info(msg);
}
/* Hidden methods */
/**
* @hidden
*/
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.connectionId, candidate);
this.session.openvidu.sendRequest('onIceCandidate', {
this.session.openvidu.sendRequest(
'onIceCandidate',
{
endpointName: this.connectionId,
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, (error, response) => {
},
(error, response) => {
if (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
*/
initRemoteStreams(options: StreamOptionsServer[]): void {
// 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
options.forEach(opts => {
options.forEach((opts) => {
const streamOptions: InboundStreamOptions = {
id: opts.id,
createdAt: opts.createdAt,
@ -175,7 +181,10 @@ export class Connection {
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;
}
}

View File

@ -27,7 +27,6 @@ import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
const logger: OpenViduLogger = OpenViduLogger.getInstance();
export abstract class EventDispatcher {
/**
* @hidden
*/
@ -42,27 +41,27 @@ export abstract class EventDispatcher {
*
* @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
*
* @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
*
* @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
*/
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
const arrowHandler = event => {
const arrowHandler = (event) => {
if (event) {
logger.info(message, event);
} else {
@ -79,7 +78,7 @@ export abstract class EventDispatcher {
* @hidden
*/
onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
const arrowHandler = event => {
const arrowHandler = (event) => {
if (event) {
logger.info(message, event);
} else {
@ -110,5 +109,4 @@ export abstract class EventDispatcher {
}
return this;
}
}

View File

@ -32,7 +32,6 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
* Video/audio filter applied to a Stream. See [[Stream.applyFilter]]
*/
export class Filter {
/**
* 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"`.
@ -59,7 +58,8 @@ export class Filter {
* You can use this value to know the current status of any applied filter
*/
lastExecMethod?: {
method: string, params: Object
method: string;
params: Object;
};
/**
@ -73,7 +73,6 @@ export class Filter {
stream: Stream;
private logger: OpenViduLogger;
/**
* @hidden
*/
@ -82,7 +81,6 @@ export class Filter {
this.options = options;
}
/**
* 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> {
return new Promise((resolve, reject) => {
logger.info('Executing filter method to stream ' + this.stream.streamId);
let finalParams;
const successExecMethod = triggerEvent => {
const successExecMethod = (triggerEvent) => {
logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
const oldValue = (<any>Object).assign({}, this.stream.filter);
this.stream.filter!.lastExecMethod = { method, params: finalParams };
if (triggerEvent) {
this.stream.session.emitEvent('streamPropertyChanged', [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')]);
this.stream.session.emitEvent('streamPropertyChanged', [
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();
}
};
if (this.type.startsWith('VB:')) {
if (typeof params === 'string') {
try {
params = JSON.parse(params);
@ -121,23 +135,31 @@ export class Filter {
if (method === 'update') {
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 {
this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
.then(() => successExecMethod(false))
.catch(error => {
.catch((error) => {
if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
return reject(new OpenViduError(error.name, error.message));
} 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 {
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 {
let stringParams;
if (typeof params !== 'string') {
try {
@ -160,7 +182,12 @@ export class Filter {
if (error) {
logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
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 {
return reject(error);
}
@ -173,7 +200,6 @@ export class 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 },
(error, response) => {
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) {
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 {
return reject(error);
}
} else {
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();
}
}
@ -206,7 +242,6 @@ export class Filter {
});
}
/**
* Removes certain filter event listener previously set.
*
@ -222,20 +257,29 @@ export class Filter {
{ streamId: this.stream.streamId, eventType },
(error, response) => {
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) {
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 {
return reject(error);
}
} else {
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();
}
}
);
});
}
}

View File

@ -31,12 +31,10 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
*/
let platform: PlatformUtils;
/**
* Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method
*/
export class LocalRecorder {
state: LocalRecorderState;
private connectionId: string;
@ -52,12 +50,11 @@ export class LocalRecorder {
*/
constructor(private stream: Stream) {
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.state = LocalRecorderState.READY;
}
/**
* 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) => {
try {
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') {
logger.error('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'));
logger.error(
'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) {
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 + "'");
@ -103,7 +110,6 @@ export class LocalRecorder {
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
this.mediaRecorder.start();
} catch (err) {
return reject(err);
}
@ -136,11 +142,9 @@ export class LocalRecorder {
this.state = LocalRecorderState.RECORDING;
return resolve();
});
}
/**
* 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
@ -149,7 +153,11 @@ export class LocalRecorder {
return new Promise((resolve, reject) => {
try {
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.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`
* @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) => {
try {
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.state = LocalRecorderState.PAUSED;
@ -190,7 +203,11 @@ export class LocalRecorder {
return new Promise((resolve, reject) => {
try {
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.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`
*/
preview(parentElement): HTMLVideoElement {
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');
@ -234,7 +253,6 @@ export class LocalRecorder {
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
*/
@ -245,19 +263,24 @@ export class LocalRecorder {
this.state = LocalRecorderState.READY;
};
if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) {
this.stop().then(() => f()).catch(() => f());
this.stop()
.then(() => f())
.catch(() => f());
} else {
f();
}
}
/**
* Downloads the recorded video through the browser. [[state]] property must be `FINISHED`
*/
download(): void {
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 {
const a: HTMLAnchorElement = document.createElement('a');
a.style.display = 'none';
@ -278,13 +301,12 @@ export class LocalRecorder {
*/
getBlob(): Blob {
if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('Call \'LocalRecord.stop()\' before getting Blob file'));
throw Error("Call 'LocalRecord.stop()' before getting Blob file");
} else {
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:
* ```
@ -298,7 +320,13 @@ export class LocalRecorder {
uploadAsBinary(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => {
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 {
const http = new XMLHttpRequest();
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:
* ```
@ -338,7 +365,13 @@ export class LocalRecorder {
uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => {
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 {
const http = new XMLHttpRequest();
http.open('POST', endpoint, true);
@ -368,7 +401,6 @@ export class LocalRecorder {
});
}
/* Private methods */
private onStopDefault(): void {
@ -381,5 +413,4 @@ export class LocalRecorder {
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 screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
import { OpenViduLoggerConfiguration } from "../OpenViduInternal/Logger/OpenViduLoggerConfiguration";
import { OpenViduLoggerConfiguration } from '../OpenViduInternal/Logger/OpenViduLoggerConfiguration';
/**
* @hidden
*/
@ -65,7 +65,6 @@ let platform: PlatformUtils;
* Use it to initialize objects of type [[Session]], [[Publisher]] and [[LocalRecorder]]
*/
export class OpenVidu {
private jsonRpcClient: any;
private masterNodeHasCrashed = false;
@ -144,19 +143,19 @@ export class OpenVidu {
/**
* @hidden
*/
ee = new EventEmitter()
ee = new EventEmitter();
constructor() {
platform = PlatformUtils.getInstance();
this.libraryVersion = packageJson.version;
logger.info("OpenVidu initialized");
logger.info('OpenVidu initialized');
logger.info('Platform detected: ' + platform.getDescription());
logger.info('openvidu-browser version: ' + this.libraryVersion);
if (platform.isMobileDevice() || platform.isReactNative()) {
// Listen to orientationchange only on mobile devices
this.onOrientationChanged(() => {
this.publishers.forEach(publisher => {
this.publishers.forEach((publisher) => {
if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
}
@ -173,11 +172,14 @@ export class OpenVidu {
return this.session;
}
initPublisher(targetElement: string | HTMLElement | undefined): 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, 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
@ -200,29 +202,41 @@ export class OpenVidu {
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
*/
initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Publisher {
let properties: PublisherProperties;
if (!!param2 && (typeof param2 !== 'function')) {
if (!!param2 && typeof param2 !== 'function') {
// Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
properties = (<PublisherProperties>param2);
properties = <PublisherProperties>param2;
properties = {
audioSource: (typeof properties.audioSource !== 'undefined') ? properties.audioSource : undefined,
frameRate: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.frameRate !== 'undefined') ? properties.frameRate : 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,
audioSource: typeof properties.audioSource !== 'undefined' ? properties.audioSource : undefined,
frameRate:
typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
? undefined
: typeof properties.frameRate !== 'undefined'
? properties.frameRate
: 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,
filter: properties.filter
};
} else {
// Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
properties = {
@ -237,19 +251,21 @@ export class OpenVidu {
const publisher: Publisher = new Publisher(targetElement, properties, this);
let completionHandler: (error: Error | undefined) => void;
if (!!param2 && (typeof param2 === 'function')) {
if (!!param2 && typeof param2 === 'function') {
completionHandler = param2;
} else if (!!param3) {
completionHandler = param3;
}
publisher.initialize()
publisher
.initialize()
.then(() => {
if (completionHandler !== undefined) {
completionHandler(undefined);
}
publisher.emitEvent('accessAllowed', []);
}).catch((error) => {
})
.catch((error) => {
if (completionHandler !== undefined) {
completionHandler(error);
}
@ -260,7 +276,6 @@ export class OpenVidu {
return publisher;
}
/**
* Promisified version of [[OpenVidu.initPublisher]]
*
@ -271,7 +286,6 @@ export class OpenVidu {
initPublisherAsync(targetElement: string | HTMLElement | undefined, properties?: PublisherProperties): Promise<Publisher> {
return new Promise<Publisher>((resolve, reject) => {
let publisher: Publisher;
const callback = (error: Error) => {
@ -290,7 +304,6 @@ export class OpenVidu {
});
}
/**
* Returns a new local recorder for recording streams straight away from the browser
* @param stream Stream to record
@ -299,7 +312,6 @@ export class OpenVidu {
return new LocalRecorder(stream);
}
/**
* Checks if the browser supports OpenVidu
* @returns 1 if the browser supports OpenVidu, 0 otherwise
@ -343,17 +355,18 @@ export class OpenVidu {
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
*/
getDevices(): Promise<Device[]> {
return new Promise<Device[]>((resolve, reject) => {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
navigator.mediaDevices
.enumerateDevices()
.then((deviceInfos) => {
const devices: Device[] = [];
// 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[]) => {
let pluginAudioDevices: Device[] = [];
let videoDevices: Device[] = [];
@ -363,20 +376,19 @@ export class OpenVidu {
audioDevices = deviceInfos.filter((device: Device) => device.kind === 'audioinput');
videoDevices.forEach((deviceInfo, index) => {
if (!deviceInfo.label) {
let label = "";
let label = '';
if (index === 0) {
label = "Front Camera";
label = 'Front Camera';
} else if (index === 1) {
label = "Back Camera";
label = 'Back Camera';
} else {
label = "Unknown Camera";
label = 'Unknown Camera';
}
devices.push({
kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId,
label: label
});
} else {
devices.push({
kind: deviceInfo.kind,
@ -387,7 +399,7 @@ export class OpenVidu {
});
audioDevices.forEach((deviceInfo, index) => {
if (!deviceInfo.label) {
let label = "";
let label = '';
switch (index) {
case 0: // Default Microphone
label = 'Default';
@ -409,7 +421,7 @@ export class OpenVidu {
label = wirelessMatch ? wirelessMatch.label : 'Wireless';
break;
default:
label = "Unknown Microphone";
label = 'Unknown Microphone';
break;
}
devices.push({
@ -417,7 +429,6 @@ export class OpenVidu {
deviceId: deviceInfo.deviceId,
label: label
});
} else {
devices.push({
kind: deviceInfo.kind,
@ -429,9 +440,8 @@ export class OpenVidu {
return resolve(devices);
});
} else {
// Rest of platforms
deviceInfos.forEach(deviceInfo => {
deviceInfos.forEach((deviceInfo) => {
if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
devices.push({
kind: deviceInfo.kind,
@ -442,15 +452,14 @@ export class OpenVidu {
});
return resolve(devices);
}
}).catch((error) => {
})
.catch((error) => {
logger.error('Error getting devices', 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]])
*
@ -500,9 +509,8 @@ export class OpenVidu {
*/
getUserMedia(options: PublisherProperties): Promise<MediaStream> {
return new Promise<MediaStream>(async (resolve, reject) => {
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 };
try {
const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
@ -517,17 +525,17 @@ export class OpenVidu {
});
return reject(this.generateAudioDeviceError(error, constraintsAux));
}
}
};
try {
const myConstraints = await this.generateMediaConstraints(options);
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack ||
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
if (
(!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
(!!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
return resolve(this.addAlreadyProvidedTracks(myConstraints, new MediaStream()));
} else {
// getUserMedia must be called. AUDIO or VIDEO are requesting a new track
@ -542,11 +550,14 @@ export class OpenVidu {
let mustAskForAudioTrackLater = false;
if (typeof options.videoSource === 'string') {
// Video is deviceId or screen sharing
if (options.videoSource === 'screen' ||
if (
options.videoSource === 'screen' ||
options.videoSource === 'window' ||
(platform.isElectron() && options.videoSource.startsWith('screen:'))) {
(platform.isElectron() && options.videoSource.startsWith('screen:'))
) {
// 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()) {
// getDisplayMedia supported
try {
@ -557,13 +568,11 @@ export class OpenVidu {
} else {
return resolve(mediaStream);
}
} catch (error) {
let errorName: OpenViduErrorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
const errorMessage = error.toString();
return reject(new OpenViduError(errorName, errorMessage));
}
} else {
// getDisplayMedia NOT supported. Can perform getUserMedia below with already calculated constraints
}
@ -572,7 +581,9 @@ export class OpenVidu {
}
}
// Use already calculated constraints
const constraintsAux = mustAskForAudioTrackLater ? { video: myConstraints.constraints!.video } : myConstraints.constraints;
const constraintsAux = mustAskForAudioTrackLater
? { video: myConstraints.constraints!.video }
: myConstraints.constraints;
try {
const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
this.addAlreadyProvidedTracks(myConstraints, mediaStream);
@ -598,7 +609,6 @@ export class OpenVidu {
});
}
/* tslint:disable:no-empty */
/**
* Disable all logging except error level
@ -608,7 +618,6 @@ export class OpenVidu {
}
/* 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.
*/
@ -616,7 +625,6 @@ export class OpenVidu {
this.advancedConfiguration = configuration;
}
/* Hidden methods */
/**
@ -639,7 +647,7 @@ export class OpenVidu {
if (attempts > MAX_ATTEMPTS) {
clearTimeout(repeatUntilChangeOrMaxAttempts);
}
publisher.getVideoDimensions().then(newDimensions => {
publisher.getVideoDimensions().then((newDimensions) => {
if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
clearTimeout(repeatUntilChangeOrMaxAttempts);
this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
@ -651,7 +659,14 @@ export class OpenVidu {
/**
* @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 = {
width: newWidth || 0,
height: newHeight || 0
@ -668,12 +683,31 @@ export class OpenVidu {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [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.emitEvent('streamPropertyChanged', [
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);
}
});
};
}
);
}
/**
* @hidden
@ -695,13 +729,22 @@ export class OpenVidu {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)]);
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)]);
this.session.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
]);
publisher.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
]);
}
});
}
);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)]);
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)]);
this.session.emitEvent('streamPropertyChanged', [
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> {
return new Promise<CustomMediaStreamConstraints>((resolve, reject) => {
const myConstraints: CustomMediaStreamConstraints = {
audioTrack: undefined,
videoTrack: undefined,
@ -718,7 +760,7 @@ export class OpenVidu {
audio: undefined,
video: undefined
}
}
};
const audioSource = publisherProperties.audioSource;
const videoSource = publisherProperties.videoSource;
@ -733,8 +775,12 @@ export class OpenVidu {
}
if (myConstraints.constraints!.audio === false && myConstraints.constraints!.video === false) {
// ERROR! audioSource and videoSource cannot be both false at the same time
return reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET,
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time"));
return reject(
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
@ -775,7 +821,7 @@ export class OpenVidu {
height: {
ideal: idealHeight
}
}
};
}
if (!!publisherProperties.frameRate) {
(<MediaTrackConstraints>myConstraints.constraints!.video).frameRate = { ideal: publisherProperties.frameRate };
@ -833,8 +879,8 @@ export class OpenVidu {
onMasterNodeCrashedNotification(response): void {
console.error('Master Node has crashed');
this.masterNodeHasCrashed = true;
this.session.onLostConnection("nodeCrashed");
this.jsonRpcClient.close(4103, "Master Node has crashed");
this.session.onLostConnection('nodeCrashed');
this.jsonRpcClient.close(4103, 'Master Node has crashed');
}
/**
@ -848,7 +894,7 @@ export class OpenVidu {
* @hidden
*/
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':
if (error.constraint.toLowerCase() === 'deviceid') {
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 {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
@ -914,7 +963,7 @@ export class OpenVidu {
case 'notreadableerror':
errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
errorMessage = error.toString();
return (new OpenViduError(errorName, errorMessage));
return new OpenViduError(errorName, errorMessage);
default:
return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString());
}
@ -943,7 +992,12 @@ export class OpenVidu {
/**
* @hidden
*/
protected configureDeviceIdOrScreensharing(myConstraints: CustomMediaStreamConstraints, publisherProperties: PublisherProperties, resolve, reject) {
protected configureDeviceIdOrScreensharing(
myConstraints: CustomMediaStreamConstraints,
publisherProperties: PublisherProperties,
resolve,
reject
) {
const audioSource = publisherProperties.audioSource;
const videoSource = publisherProperties.videoSource;
if (typeof audioSource === 'string') {
@ -951,22 +1005,24 @@ export class OpenVidu {
}
if (typeof videoSource === 'string') {
if (!this.isScreenShare(videoSource)) {
this.setVideoSource(myConstraints, videoSource);
} else {
// Screen sharing
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);
return reject(error);
} else {
if (platform.isElectron()) {
const prefix = "screen:";
const prefix = 'screen:';
const videoSourceString: string = videoSource;
const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length);
(<any>myConstraints.constraints!.video) = {
@ -976,29 +1032,45 @@ export class OpenVidu {
}
};
return resolve(myConstraints);
} else {
if (!!this.advancedConfiguration.screenShareChromeExtension && !(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) && !navigator.mediaDevices['getDisplayMedia']) {
if (
!!this.advancedConfiguration.screenShareChromeExtension &&
!(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) &&
!navigator.mediaDevices['getDisplayMedia']
) {
// Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia()
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') {
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);
return reject(error);
} else {
const extensionId = this.advancedConfiguration.screenShareChromeExtension!.split('/').pop()!!.trim();
screenSharing.getChromeExtensionStatus(extensionId, status => {
const extensionId = this.advancedConfiguration
.screenShareChromeExtension!.split('/')
.pop()!!
.trim();
screenSharing.getChromeExtensionStatus(extensionId, (status) => {
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);
return reject(error);
}
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);
return reject(error);
}
@ -1012,32 +1084,44 @@ export class OpenVidu {
});
return;
} else {
if (navigator.mediaDevices['getDisplayMedia']) {
// getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13)
return resolve(myConstraints);
} else {
// 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) => {
if (!!error) {
if (error === 'not-installed') {
const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension ? this.advancedConfiguration.screenShareChromeExtension :
'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension
? this.advancedConfiguration.screenShareChromeExtension
: 'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl);
logger.error(err);
return reject(err);
} 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);
return reject(err);
} 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);
return reject(err);
} 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(error);
return reject(err);
@ -1066,7 +1150,6 @@ export class OpenVidu {
(<MediaTrackConstraints>myConstraints.constraints!.video)['deviceId'] = { exact: videoSource };
}
/* Private methods */
private disconnectCallback(): void {
@ -1098,25 +1181,24 @@ export class OpenVidu {
// if life is greater, nodeCrashed
this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => {
if (!!error) {
if (this.isMasterNodeCrashed()) {
logger.warn('Master Node has crashed!');
} else {
logger.error(error);
const notifyLostConnection = (reason, errorMsg) => {
logger.warn(errorMsg);
this.session.onLostConnection(reason);
this.jsonRpcClient.close(4101, "Reconnection fault: " + errorMsg);
}
this.jsonRpcClient.close(4101, 'Reconnection fault: ' + errorMsg);
};
const rpcSessionStatus = () => {
if (this.life === -1) {
// 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 {
// Multi Master
// This RPC method is only required to find out the reason of the disconnection:
@ -1127,10 +1209,16 @@ export class OpenVidu {
} else {
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
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 {
// 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 {
rpcSessionStatus();
}
}
} else {
this.jsonRpcClient.resetPing();
@ -1188,9 +1275,6 @@ export class OpenVidu {
}
private isScreenShare(videoSource: string) {
return videoSource === 'screen' ||
videoSource === 'window' ||
(platform.isElectron() && videoSource.startsWith('screen:'))
return videoSource === 'screen' || videoSource === 'window' || (platform.isElectron() && videoSource.startsWith('screen:'));
}
}

View File

@ -45,7 +45,6 @@ let platform: PlatformUtils;
* See available event listeners at [[PublisherEventMap]].
*/
export class Publisher extends StreamManager {
/**
* Whether the Publisher has been granted access to the requested input devices or not
*/
@ -82,7 +81,13 @@ export class Publisher extends StreamManager {
* @hidden
*/
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();
this.properties = properties;
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
*
@ -115,7 +119,9 @@ export class Publisher extends StreamManager {
*/
publishAudio(enabled: boolean): void {
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) => {
track.enabled = enabled;
});
@ -132,18 +138,22 @@ export class Publisher extends StreamManager {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')]);
this.session.emitEvent('streamPropertyChanged', [
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.stream.audioActive = enabled;
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
*
@ -169,12 +179,11 @@ export class Publisher extends StreamManager {
* will be used instead.
*/
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
return new Promise(async (resolve, reject) => {
if (this.stream.videoActive !== 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();
let mustRestartMediaStream = false;
affectedMediaStream.getVideoTracks().forEach((track) => {
track.enabled = enabled;
@ -212,13 +221,16 @@ export class Publisher extends StreamManager {
delete this.stream.lastVBFilter;
}, 1);
}
}
};
if (!!resource && resource instanceof MediaStreamTrack) {
await replaceVideoTrack(resource);
} else {
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]);
} catch (error) {
return reject(error);
@ -239,11 +251,23 @@ export class Publisher extends StreamManager {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
this.session.emitEvent('streamPropertyChanged', [
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.stream.videoActive = enabled;
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.
*/
subscribeToRemote(value?: boolean): void {
value = (value !== undefined) ? value : true;
value = value !== undefined ? value : true;
this.isSubscribedToRemote = value;
this.stream.subscribeToMyRemote(value);
}
/**
* See [[EventDispatcher.on]]
*/
on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
super.on(<any>type, handler);
if (type === 'streamCreated') {
@ -292,12 +313,10 @@ export class Publisher extends StreamManager {
return this;
}
/**
* See [[EventDispatcher.once]]
*/
once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
super.once(<any>type, handler);
if (type === 'streamCreated') {
@ -322,7 +341,6 @@ export class Publisher extends StreamManager {
return this;
}
/**
* See [[EventDispatcher.off]]
*/
@ -331,7 +349,6 @@ export class Publisher extends StreamManager {
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
* 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> {
return new Promise(async (resolve, reject) => {
let constraints: MediaStreamConstraints = {};
let constraintsAux: MediaStreamConstraints = {};
const timeForDialogEvent = 2000;
@ -368,7 +384,7 @@ export class Publisher extends StreamManager {
const errorCallback = (openViduError: OpenViduError) => {
this.accessDenied = true;
this.accessAllowed = false;
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`)
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
return reject(openViduError);
};
@ -378,21 +394,27 @@ export class Publisher extends StreamManager {
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
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) {
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource));
mediaStream.addTrack(<MediaStreamTrack>this.properties.videoSource);
}
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
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;
}
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;
}
@ -411,16 +433,16 @@ export class Publisher extends StreamManager {
// https://w3c.github.io/mst-content-hint/#video-content-hints
switch (this.stream.typeOfVideo) {
case TypeOfVideo.SCREEN:
track.contentHint = "detail";
track.contentHint = 'detail';
break;
case TypeOfVideo.CUSTOM:
logger.warn("CUSTOM type video track was provided without Content Hint!");
track.contentHint = "motion";
logger.warn('CUSTOM type video track was provided without Content Hint!');
track.contentHint = 'motion';
break;
case TypeOfVideo.CAMERA:
case TypeOfVideo.IPCAM:
default:
track.contentHint = "motion";
track.contentHint = 'motion';
break;
}
logger.info(`Video track Content Hint set: '${track.contentHint}'`);
@ -438,7 +460,7 @@ export class Publisher extends StreamManager {
if (this.stream.isSendVideo()) {
// Has video track
this.getVideoDimensions().then(dimensions => {
this.getVideoDimensions().then((dimensions) => {
this.stream.videoDimensions = {
width: dimensions.width,
height: dimensions.height
@ -491,7 +513,6 @@ export class Publisher extends StreamManager {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
successCallback(mediaStream);
} catch (error) {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.getAudioTracks().forEach((track) => {
@ -529,7 +550,6 @@ export class Publisher extends StreamManager {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
} catch (error) {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
@ -538,12 +558,13 @@ export class Publisher extends StreamManager {
break;
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();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
case 'overconstrainederror':
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false,
@ -554,20 +575,27 @@ export class Publisher extends StreamManager {
});
if (error.constraint.toLowerCase() === 'deviceid') {
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 {
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));
} catch (error) {
if (error.constraint.toLowerCase() === 'deviceid') {
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 {
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));
}
@ -585,13 +613,15 @@ export class Publisher extends StreamManager {
errorCallback(new OpenViduError(errorName, errorMessage));
break;
}
}
};
try {
const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack ||
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
if (
(!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
(!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
(!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
) {
// No need to call getUserMedia at all. MediaStreamTracks already provided
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
} else {
@ -603,7 +633,7 @@ export class Publisher extends StreamManager {
};
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.video = constraints.video;
startTime = Date.now();
@ -664,9 +694,8 @@ export class Publisher extends StreamManager {
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
* use the HTMLVideoElement properties videoWidth and videoHeight
*/
getVideoDimensions(): Promise<{ width: number, height: number }> {
getVideoDimensions(): Promise<{ width: number; height: number }> {
return new Promise((resolve, reject) => {
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari();
@ -692,7 +721,7 @@ export class Publisher extends StreamManager {
}
return resolve({ width, height });
}
};
if (this.videoReference.readyState >= 1) {
// The video already has metadata available
@ -739,7 +768,14 @@ export class Publisher extends StreamManager {
this.videoReference.muted = true;
this.videoReference.autoplay = true;
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.stream.setMediaStream(mediaStream);
@ -753,7 +789,9 @@ export class Publisher extends StreamManager {
* @hidden
*/
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;
if (track.kind === 'video') {
removedTrack = mediaStream.getVideoTracks()[0];
@ -798,7 +836,7 @@ export class Publisher extends StreamManager {
private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
clearTimeout(this.permissionDialogTimeout);
if ((Date.now() - startTime) > waitTime) {
if (Date.now() - startTime > waitTime) {
// Permission dialog was shown and now is closed
this.emitEvent('accessDialogClosed', []);
}
@ -808,19 +846,18 @@ export class Publisher extends StreamManager {
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
let sender: RTCRtpSender | undefined;
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) {
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') {
sender = senders.find(s => !!s.track && s.track.kind === 'audio');
sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
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 {
throw new Error('Unknown track kind ' + track.kind);
}
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]].
*/
export abstract class StreamManager extends EventDispatcher {
/**
* The Stream represented in the DOM by the Publisher/Subscriber
*/
@ -126,7 +125,14 @@ export abstract class StreamManager extends EventDispatcher {
id: '',
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.targetElement = targEl;
@ -144,7 +150,6 @@ export abstract class StreamManager extends EventDispatcher {
* See [[EventDispatcher.on]]
*/
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);
if (type === 'videoElementCreated') {
@ -154,11 +159,14 @@ export abstract class StreamManager extends EventDispatcher {
}
}
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.paused === 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)]);
}
}
@ -180,7 +188,6 @@ export abstract class StreamManager extends EventDispatcher {
* See [[EventDispatcher.once]]
*/
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);
if (type === 'videoElementCreated') {
@ -189,11 +196,14 @@ export abstract class StreamManager extends EventDispatcher {
}
}
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.paused === 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)]);
}
}
@ -215,19 +225,20 @@ export abstract class StreamManager extends EventDispatcher {
* See [[EventDispatcher.off]]
*/
off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
super.offAux(type, handler);
if (type === 'publisherStartSpeaking') {
// 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) {
this.stream.disableHarkSpeakingEvent(false);
}
}
if (type === 'publisherStopSpeaking') {
// 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) {
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.
*/
addVideoElement(video: HTMLVideoElement): number {
this.initializeVideoProperties(video);
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);
});
@ -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)
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
*/
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void {
const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
const newInterval = (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);
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number; threshold?: number }): void {
const currentHarkOptions = !!this.stream.harkOptions
? this.stream.harkOptions
: this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
const newInterval =
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 = {
interval: newInterval,
threshold: newThreshold
@ -402,7 +422,14 @@ export abstract class StreamManager extends EventDispatcher {
video.autoplay = true;
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;
}
@ -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)
if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
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
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
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
this.removeSrcObject(streamManagerVideo);
// 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
*/
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.videos[0].video.addEventListener('canplay', this.canPlayListener);
this.videos[0].canplayListenerAdded = true;
@ -491,7 +520,7 @@ export abstract class StreamManager extends EventDispatcher {
* @hidden
*/
updateMediaStream(mediaStream: MediaStream) {
this.videos.forEach(streamManagerVideo => {
this.videos.forEach((streamManagerVideo) => {
streamManagerVideo.video.srcObject = mediaStream;
if (platform.isIonicIos()) {
// 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
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
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);
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;
}, msTimeout);
}
@ -580,5 +618,4 @@ export abstract class StreamManager extends EventDispatcher {
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
delete this.streamPlayingEventExceptionTimeout;
}
}

View File

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

View File

@ -19,7 +19,6 @@
* Defines property [[OpenViduError.name]]
*/
export enum OpenViduErrorName {
/**
* Browser is not supported by OpenVidu.
* 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.
* 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.
@ -122,7 +121,6 @@ export enum OpenViduErrorName {
* Simple object to identify runtime errors on the client side
*/
export class OpenViduError {
/**
* Uniquely identifying name of the error
*/
@ -140,5 +138,4 @@ export class OpenViduError {
this.name = name;
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]]
*/
export enum VideoInsertMode {
/**
* Video inserted after the target element (as next sibling)
*/
@ -40,5 +39,4 @@ export enum VideoInsertMode {
* Video replaces target element
*/
REPLACE = 'REPLACE'
}

View File

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

View File

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

View File

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

View File

@ -33,7 +33,6 @@ import { StreamManagerEventMap } from './StreamManagerEventMap';
* ```
*/
export interface PublisherEventMap extends StreamManagerEventMap {
/**
* 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 {
/**
* 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 {
/**
* 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.

View File

@ -20,12 +20,10 @@ import { Stream } from '../../OpenVidu/Stream';
import { Subscriber } from '../../OpenVidu/Subscriber';
import { Event } from './Event';
/**
* Defines property [[ExceptionEvent.name]]
*/
export enum ExceptionEventName {
/**
* 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]]
*/
export class ExceptionEvent extends Event {
/**
* Name of the exception
*/
@ -127,5 +124,4 @@ export class ExceptionEvent extends Event {
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

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

View File

@ -23,7 +23,6 @@ import { Connection } from '../../OpenVidu/Connection';
* Triggered by [[networkQualityLevelChanged]]
*/
export class NetworkQualityLevelChangedEvent extends Event {
/**
* 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: Connection
connection: Connection;
/**
* @hidden
@ -54,5 +53,4 @@ export class NetworkQualityLevelChangedEvent extends Event {
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -20,14 +20,12 @@ import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* 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)
* - `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 {
/**
* The client that started or stopped speaking
*/
@ -53,5 +51,4 @@ export class PublisherSpeakingEvent extends Event {
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

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

View File

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

View File

@ -19,12 +19,10 @@ import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
/**
* Triggered by [[SessionEventMap.signal]]
*/
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
* valid [[SignalOptions.type]] property.
@ -63,5 +61,4 @@ export class SignalEvent extends Event {
*/
// tslint:disable-next-line:no-empty
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)
*/
export class StreamEvent extends Event {
/**
* Stream object that was created or destroyed
*/
@ -68,7 +67,6 @@ export class StreamEvent extends Event {
*/
callDefaultBehavior() {
if (this.type === 'streamDestroyed') {
if (this.target instanceof Session) {
// Remote Stream
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
const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
for (let i = 0; i < openviduPublishers.length; i++) {
if (openviduPublishers[i] === (<Publisher>this.target)) {
if (openviduPublishers[i] === <Publisher>this.target) {
openviduPublishers.splice(i, 1);
break;
}
@ -109,8 +107,6 @@ export class StreamEvent extends Event {
}
}
}
}
}
}

View File

@ -24,7 +24,6 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
* - [[streamAudioVolumeChange]]
*/
export class StreamManagerEvent extends 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).
@ -47,5 +46,4 @@ export class StreamManagerEvent extends Event {
*/
// tslint:disable-next-line:no-empty
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)
*/
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]]
*/
@ -57,7 +56,14 @@ export class StreamPropertyChangedEvent extends Event {
/**
* @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');
this.stream = stream;
this.changedProperty = changedProperty;
@ -71,5 +77,4 @@ export class StreamPropertyChangedEvent extends Event {
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

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

View File

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

View File

@ -19,7 +19,6 @@
* See [[Session.capabilities]]
*/
export interface Capabilities {
/**
* `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)
*/
subscribe: boolean;
}

View File

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

View File

@ -19,7 +19,6 @@
* See [[OpenVidu.setAdvancedConfiguration]]
*/
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
* 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`.
*/
noStreamPlayingEventExceptionTimeout?: number;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,18 +30,18 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
let platform: PlatformUtils;
interface WebrtcStatsConfig {
interval: number,
httpEndpoint: string
interval: number;
httpEndpoint: string;
}
interface JSONStatsResponse {
'@timestamp': string,
participant_id: string,
session_id: string,
platform: string,
platform_description: string,
stream: string,
webrtc_stats: IWebrtcStats
'@timestamp': string;
participant_id: string;
session_id: string;
platform: string;
platform_description: string;
stream: string;
webrtc_stats: IWebrtcStats;
}
/**
@ -49,55 +49,62 @@ interface JSONStatsResponse {
*/
interface IWebrtcStats {
inbound?: {
audio: {
bytesReceived: number,
packetsReceived: 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)
audio:
| {
bytesReceived: number;
packetsReceived: 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)
};
}
export class WebRtcStats {
private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
private webRtcStatsEnabled = false;
@ -114,14 +121,15 @@ export class WebRtcStats {
}
public initWebRtcStats(): void {
const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
if (!!webrtcObj) {
this.webRtcStatsEnabled = true;
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
// 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));
this.POST_URL = webrtcStatsConfig.httpEndpoint;
@ -130,7 +138,6 @@ export class WebRtcStats {
this.webRtcStatsIntervalId = setInterval(async () => {
await this.sendStatsToHttpEndpoint();
}, this.statsInterval * 1000);
} else {
logger.debug('WebRtc stats not enabled');
}
@ -206,7 +213,6 @@ export class WebRtcStats {
// - ¿React Native?
public getSelectedIceCandidateInfo(): Promise<any> {
return new Promise(async (resolve, reject) => {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
let transportStat;
const candidatePairs: Map<string, any> = new Map();
@ -230,7 +236,7 @@ export class WebRtcStats {
});
let selectedCandidatePair;
if (transportStat != null) {
const selectedCandidatePairId = transportStat.selectedCandidatePairId
const selectedCandidatePairId = transportStat.selectedCandidatePairId;
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
} else {
// This is basically Firefox
@ -250,9 +256,11 @@ export class WebRtcStats {
if (!!finalLocalCandidate) {
const candList = this.stream.getLocalIceCandidateList();
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.port) >= 0);
c.candidate.indexOf(finalLocalCandidate.port) >= 0
);
});
finalLocalCandidate.raw = [];
for (let c of cand) {
@ -266,9 +274,11 @@ export class WebRtcStats {
if (!!finalRemoteCandidate) {
const candList = this.stream.getRemoteIceCandidateList();
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.port) >= 0);
c.candidate.indexOf(finalRemoteCandidate.port) >= 0
);
});
finalRemoteCandidate.raw = [];
for (let c of cand) {
@ -288,7 +298,9 @@ export class WebRtcStats {
public stopWebRtcStats() {
if (this.webRtcStatsEnabled) {
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'
},
body: JSON.stringify(response),
method: 'POST',
method: 'POST'
};
await fetch(url, configuration);
} catch (error) {
logger.error(`sendStats error: ${JSON.stringify(error)}`);
}
@ -350,9 +361,7 @@ export class WebRtcStats {
// - ¿Ionic?
// - ¿React Native?
public async getCommonStats(): Promise<IWebrtcStats> {
return new Promise(async (resolve, reject) => {
try {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
@ -360,11 +369,10 @@ export class WebRtcStats {
const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
statsReport.forEach((stat: any) => {
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
const addStat = (direction: string, key: string): void => {
if (stat[key] != null && response[direction] != null) {
if (!mediaType && (videoTrackStats.indexOf(key) > -1)) {
if (!mediaType && videoTrackStats.indexOf(key) > -1) {
mediaType = 'video';
}
if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
@ -374,10 +382,10 @@ export class WebRtcStats {
response[direction][key] = Number(stat[key]);
}
}
}
};
switch (stat.type) {
case "outbound-rtp":
case 'outbound-rtp':
addStat('outbound', 'bytesSent');
addStat('outbound', 'packetsSent');
addStat('outbound', 'framesEncoded');
@ -386,7 +394,7 @@ export class WebRtcStats {
addStat('outbound', 'pliCount');
addStat('outbound', 'qpSum');
break;
case "inbound-rtp":
case 'inbound-rtp':
addStat('inbound', 'bytesReceived');
addStat('inbound', 'packetsReceived');
addStat('inbound', 'packetsLost');
@ -421,7 +429,6 @@ export class WebRtcStats {
logger.error('Error getting common stats: ', 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';
// Disable jsnlog when library is loaded
JL.setOptions({ enabled: false })
JL.setOptions({ enabled: false });