mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: Added common format config file
parent
128dd3cfed
commit
2ce54f577b
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 140,
|
||||
"trailingComma": "none",
|
||||
"semi": true,
|
||||
"bracketSpacing": true,
|
||||
"useTabs": false,
|
||||
"jsxSingleQuote": true,
|
||||
"tabWidth": 4
|
||||
}
|
|
@ -6,4 +6,4 @@ if (typeof globalThis !== 'undefined') {
|
|||
}
|
||||
|
||||
// Disable jsnlog when library is loaded
|
||||
JL.setOptions({ enabled: false })
|
||||
JL.setOptions({ enabled: false });
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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:'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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'
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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]]).
|
||||
*/
|
||||
|
|
|
@ -42,7 +42,6 @@ import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
|
|||
* ```
|
||||
*/
|
||||
export interface SessionEventMap extends EventMap {
|
||||
|
||||
/**
|
||||
* Event dispatched when a new user has connected to the session.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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() {}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -19,7 +19,6 @@
|
|||
* See [[OpenVidu.getDevices]]
|
||||
*/
|
||||
export interface Device {
|
||||
|
||||
/**
|
||||
* `"videoinput"`, `"audioinput"`
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import { Connection } from '../../../OpenVidu/Connection';
|
|||
* See [[Session.signal]]
|
||||
*/
|
||||
export interface SignalOptions {
|
||||
|
||||
/**
|
||||
* The actual message of the signal.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -17,5 +17,4 @@
|
|||
|
||||
var JsonRpcClient = require('./jsonrpcclient');
|
||||
|
||||
|
||||
exports.JsonRpcClient = JsonRpcClient;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,6 +1,5 @@
|
|||
var JsonRPC = require('./JsonRPC');
|
||||
var XmlRPC = require('./XmlRPC');
|
||||
|
||||
|
||||
exports.JsonRPC = JsonRPC;
|
||||
exports.XmlRPC = XmlRPC;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 || '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 });
|
||||
|
|
Loading…
Reference in New Issue