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
|
// 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();
|
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents each one of the user's connection to the session (the local one and other user's connections).
|
* Represents each one of the user's connection to the session (the local one and other user's connections).
|
||||||
* Therefore each [[Session]] and [[Stream]] object has an attribute of type Connection
|
* Therefore each [[Session]] and [[Stream]] object has an attribute of type Connection
|
||||||
*/
|
*/
|
||||||
export class Connection {
|
export class Connection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique identifier of the connection
|
* Unique identifier of the connection
|
||||||
*/
|
*/
|
||||||
|
@ -125,38 +123,46 @@ export class Connection {
|
||||||
logger.info(msg);
|
logger.info(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Hidden methods */
|
/* Hidden methods */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
sendIceCandidate(candidate: RTCIceCandidate): void {
|
sendIceCandidate(candidate: RTCIceCandidate): void {
|
||||||
|
logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' + this.connectionId, candidate);
|
||||||
|
|
||||||
logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' +
|
this.session.openvidu.sendRequest(
|
||||||
this.connectionId, candidate);
|
'onIceCandidate',
|
||||||
|
{
|
||||||
this.session.openvidu.sendRequest('onIceCandidate', {
|
|
||||||
endpointName: this.connectionId,
|
endpointName: this.connectionId,
|
||||||
candidate: candidate.candidate,
|
candidate: candidate.candidate,
|
||||||
sdpMid: candidate.sdpMid,
|
sdpMid: candidate.sdpMid,
|
||||||
sdpMLineIndex: candidate.sdpMLineIndex
|
sdpMLineIndex: candidate.sdpMLineIndex
|
||||||
}, (error, response) => {
|
},
|
||||||
|
(error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
|
logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
|
||||||
this.session.emitEvent('exception', [new ExceptionEvent(this.session, ExceptionEventName.ICE_CANDIDATE_ERROR, this.session, "There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side", error)]);
|
this.session.emitEvent('exception', [
|
||||||
|
new ExceptionEvent(
|
||||||
|
this.session,
|
||||||
|
ExceptionEventName.ICE_CANDIDATE_ERROR,
|
||||||
|
this.session,
|
||||||
|
'There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
initRemoteStreams(options: StreamOptionsServer[]): void {
|
initRemoteStreams(options: StreamOptionsServer[]): void {
|
||||||
|
|
||||||
// This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
|
// This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
|
||||||
// this.stream should also be replaced by a collection of streams to support multiple streams per Connection
|
// this.stream should also be replaced by a collection of streams to support multiple streams per Connection
|
||||||
options.forEach(opts => {
|
options.forEach((opts) => {
|
||||||
const streamOptions: InboundStreamOptions = {
|
const streamOptions: InboundStreamOptions = {
|
||||||
id: opts.id,
|
id: opts.id,
|
||||||
createdAt: opts.createdAt,
|
createdAt: opts.createdAt,
|
||||||
|
@ -175,7 +181,10 @@ export class Connection {
|
||||||
this.addStream(stream);
|
this.addStream(stream);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info("Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ', this.stream!.inboundStreamOpts);
|
logger.info(
|
||||||
|
"Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ',
|
||||||
|
this.stream!.inboundStreamOpts
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,5 +211,4 @@ export class Connection {
|
||||||
}
|
}
|
||||||
this.disposed = true;
|
this.disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
|
|
||||||
export abstract class EventDispatcher {
|
export abstract class EventDispatcher {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -42,27 +41,27 @@ export abstract class EventDispatcher {
|
||||||
*
|
*
|
||||||
* @returns The EventDispatcher object
|
* @returns The EventDispatcher object
|
||||||
*/
|
*/
|
||||||
abstract on<K extends keyof (EventMap)>(type: K, handler: (event: (EventMap)[K]) => void): this;
|
abstract on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
|
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
|
||||||
*
|
*
|
||||||
* @returns The object that dispatched the event
|
* @returns The object that dispatched the event
|
||||||
*/
|
*/
|
||||||
abstract once<K extends keyof (EventMap)>(type: K, handler: (event: (EventMap)[K]) => void): this;
|
abstract once<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
|
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
|
||||||
*
|
*
|
||||||
* @returns The object that dispatched the event
|
* @returns The object that dispatched the event
|
||||||
*/
|
*/
|
||||||
abstract off<K extends keyof (EventMap)>(type: K, handler?: (event: (EventMap)[K]) => void): this;
|
abstract off<K extends keyof EventMap>(type: K, handler?: (event: EventMap[K]) => void): this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
||||||
const arrowHandler = event => {
|
const arrowHandler = (event) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
logger.info(message, event);
|
logger.info(message, event);
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,7 +78,7 @@ export abstract class EventDispatcher {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
||||||
const arrowHandler = event => {
|
const arrowHandler = (event) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
logger.info(message, event);
|
logger.info(message, event);
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,5 +109,4 @@ export abstract class EventDispatcher {
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -32,7 +32,6 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
* Video/audio filter applied to a Stream. See [[Stream.applyFilter]]
|
* Video/audio filter applied to a Stream. See [[Stream.applyFilter]]
|
||||||
*/
|
*/
|
||||||
export class Filter {
|
export class Filter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of filter applied. This is the name of the remote class identifying the filter to apply in Kurento Media Server.
|
* Type of filter applied. This is the name of the remote class identifying the filter to apply in Kurento Media Server.
|
||||||
* For example: `"FaceOverlayFilter"`, `"GStreamerFilter"`.
|
* For example: `"FaceOverlayFilter"`, `"GStreamerFilter"`.
|
||||||
|
@ -59,7 +58,8 @@ export class Filter {
|
||||||
* You can use this value to know the current status of any applied filter
|
* You can use this value to know the current status of any applied filter
|
||||||
*/
|
*/
|
||||||
lastExecMethod?: {
|
lastExecMethod?: {
|
||||||
method: string, params: Object
|
method: string;
|
||||||
|
params: Object;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +73,6 @@ export class Filter {
|
||||||
stream: Stream;
|
stream: Stream;
|
||||||
private logger: OpenViduLogger;
|
private logger: OpenViduLogger;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -82,7 +81,6 @@ export class Filter {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a filter method. Available methods are specific for each filter
|
* Executes a filter method. Available methods are specific for each filter
|
||||||
*
|
*
|
||||||
|
@ -91,24 +89,40 @@ export class Filter {
|
||||||
*/
|
*/
|
||||||
execMethod(method: string, params: Object): Promise<void> {
|
execMethod(method: string, params: Object): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
logger.info('Executing filter method to stream ' + this.stream.streamId);
|
logger.info('Executing filter method to stream ' + this.stream.streamId);
|
||||||
|
|
||||||
let finalParams;
|
let finalParams;
|
||||||
|
|
||||||
const successExecMethod = triggerEvent => {
|
const successExecMethod = (triggerEvent) => {
|
||||||
logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
|
logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
|
||||||
const oldValue = (<any>Object).assign({}, this.stream.filter);
|
const oldValue = (<any>Object).assign({}, this.stream.filter);
|
||||||
this.stream.filter!.lastExecMethod = { method, params: finalParams };
|
this.stream.filter!.lastExecMethod = { method, params: finalParams };
|
||||||
if (triggerEvent) {
|
if (triggerEvent) {
|
||||||
this.stream.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.stream.session, this.stream, 'filter', this.stream.filter!, oldValue, 'execFilterMethod')]);
|
this.stream.session.emitEvent('streamPropertyChanged', [
|
||||||
this.stream.streamManager.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.stream.streamManager, this.stream, 'filter', this.stream.filter!, oldValue, 'execFilterMethod')]);
|
new StreamPropertyChangedEvent(
|
||||||
|
this.stream.session,
|
||||||
|
this.stream,
|
||||||
|
'filter',
|
||||||
|
this.stream.filter!,
|
||||||
|
oldValue,
|
||||||
|
'execFilterMethod'
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
this.stream.streamManager.emitEvent('streamPropertyChanged', [
|
||||||
|
new StreamPropertyChangedEvent(
|
||||||
|
this.stream.streamManager,
|
||||||
|
this.stream,
|
||||||
|
'filter',
|
||||||
|
this.stream.filter!,
|
||||||
|
oldValue,
|
||||||
|
'execFilterMethod'
|
||||||
|
)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
};
|
||||||
|
|
||||||
if (this.type.startsWith('VB:')) {
|
if (this.type.startsWith('VB:')) {
|
||||||
|
|
||||||
if (typeof params === 'string') {
|
if (typeof params === 'string') {
|
||||||
try {
|
try {
|
||||||
params = JSON.parse(params);
|
params = JSON.parse(params);
|
||||||
|
@ -121,23 +135,31 @@ export class Filter {
|
||||||
|
|
||||||
if (method === 'update') {
|
if (method === 'update') {
|
||||||
if (!this.stream.virtualBackgroundSinkElements?.VB) {
|
if (!this.stream.virtualBackgroundSinkElements?.VB) {
|
||||||
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied'));
|
return reject(
|
||||||
|
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
|
this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
|
||||||
.then(() => successExecMethod(false))
|
.then(() => successExecMethod(false))
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
|
if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
|
||||||
return reject(new OpenViduError(error.name, error.message));
|
return reject(new OpenViduError(error.name, error.message));
|
||||||
} else {
|
} else {
|
||||||
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'Error updating values on Virtual Background filter: ' + error));
|
return reject(
|
||||||
|
new OpenViduError(
|
||||||
|
OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
|
||||||
|
'Error updating values on Virtual Background filter: ' + error
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`));
|
return reject(
|
||||||
|
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let stringParams;
|
let stringParams;
|
||||||
if (typeof params !== 'string') {
|
if (typeof params !== 'string') {
|
||||||
try {
|
try {
|
||||||
|
@ -160,7 +182,12 @@ export class Filter {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
|
logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
|
||||||
if (error.code === 401) {
|
if (error.code === 401) {
|
||||||
return reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to execute a filter method"));
|
return reject(
|
||||||
|
new OpenViduError(
|
||||||
|
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
|
||||||
|
"You don't have permissions to execute a filter method"
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +200,6 @@ export class Filter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to certain filter event. Available events are specific for each filter
|
* Subscribe to certain filter event. Available events are specific for each filter
|
||||||
*
|
*
|
||||||
|
@ -190,15 +216,25 @@ export class Filter {
|
||||||
{ streamId: this.stream.streamId, eventType },
|
{ streamId: this.stream.streamId, eventType },
|
||||||
(error, response) => {
|
(error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId, error);
|
logger.error(
|
||||||
|
'Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
|
||||||
|
error
|
||||||
|
);
|
||||||
if (error.code === 401) {
|
if (error.code === 401) {
|
||||||
return reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to add a filter event listener"));
|
return reject(
|
||||||
|
new OpenViduError(
|
||||||
|
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
|
||||||
|
"You don't have permissions to add a filter event listener"
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.handlers.set(eventType, handler);
|
this.handlers.set(eventType, handler);
|
||||||
logger.info('Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId);
|
logger.info(
|
||||||
|
'Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId
|
||||||
|
);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +242,6 @@ export class Filter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes certain filter event listener previously set.
|
* Removes certain filter event listener previously set.
|
||||||
*
|
*
|
||||||
|
@ -222,20 +257,29 @@ export class Filter {
|
||||||
{ streamId: this.stream.streamId, eventType },
|
{ streamId: this.stream.streamId, eventType },
|
||||||
(error, response) => {
|
(error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId, error);
|
logger.error(
|
||||||
|
'Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
|
||||||
|
error
|
||||||
|
);
|
||||||
if (error.code === 401) {
|
if (error.code === 401) {
|
||||||
return reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to add a filter event listener"));
|
return reject(
|
||||||
|
new OpenViduError(
|
||||||
|
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
|
||||||
|
"You don't have permissions to add a filter event listener"
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.handlers.delete(eventType);
|
this.handlers.delete(eventType);
|
||||||
logger.info('Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId);
|
logger.info(
|
||||||
|
'Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId
|
||||||
|
);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -31,12 +31,10 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
*/
|
*/
|
||||||
let platform: PlatformUtils;
|
let platform: PlatformUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method
|
* Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method
|
||||||
*/
|
*/
|
||||||
export class LocalRecorder {
|
export class LocalRecorder {
|
||||||
|
|
||||||
state: LocalRecorderState;
|
state: LocalRecorderState;
|
||||||
|
|
||||||
private connectionId: string;
|
private connectionId: string;
|
||||||
|
@ -52,12 +50,11 @@ export class LocalRecorder {
|
||||||
*/
|
*/
|
||||||
constructor(private stream: Stream) {
|
constructor(private stream: Stream) {
|
||||||
platform = PlatformUtils.getInstance();
|
platform = PlatformUtils.getInstance();
|
||||||
this.connectionId = (!!this.stream.connection) ? this.stream.connection.connectionId : 'default-connection';
|
this.connectionId = !!this.stream.connection ? this.stream.connection.connectionId : 'default-connection';
|
||||||
this.id = this.stream.streamId + '_' + this.connectionId + '_localrecord';
|
this.id = this.stream.streamId + '_' + this.connectionId + '_localrecord';
|
||||||
this.state = LocalRecorderState.READY;
|
this.state = LocalRecorderState.READY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the recording of the Stream. [[state]] property must be `READY`. After method succeeds is set to `RECORDING`
|
* Starts the recording of the Stream. [[state]] property must be `READY`. After method succeeds is set to `RECORDING`
|
||||||
*
|
*
|
||||||
|
@ -84,14 +81,24 @@ export class LocalRecorder {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (typeof options === 'string' || options instanceof String) {
|
if (typeof options === 'string' || options instanceof String) {
|
||||||
return reject(`When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`);
|
return reject(
|
||||||
|
`When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (typeof MediaRecorder === 'undefined') {
|
if (typeof MediaRecorder === 'undefined') {
|
||||||
logger.error('MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder');
|
logger.error(
|
||||||
throw (Error('MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'));
|
'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
|
||||||
|
);
|
||||||
|
throw Error(
|
||||||
|
'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (this.state !== LocalRecorderState.READY) {
|
if (this.state !== LocalRecorderState.READY) {
|
||||||
throw (Error('\'LocalRecord.record()\' needs \'LocalRecord.state\' to be \'READY\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.clean()\' or init a new LocalRecorder before'));
|
throw Error(
|
||||||
|
"'LocalRecord.record()' needs 'LocalRecord.state' to be 'READY' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.clean()' or init a new LocalRecorder before"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
|
logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
|
||||||
|
|
||||||
|
@ -103,7 +110,6 @@ export class LocalRecorder {
|
||||||
|
|
||||||
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
|
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
|
||||||
this.mediaRecorder.start();
|
this.mediaRecorder.start();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
@ -136,11 +142,9 @@ export class LocalRecorder {
|
||||||
|
|
||||||
this.state = LocalRecorderState.RECORDING;
|
this.state = LocalRecorderState.RECORDING;
|
||||||
return resolve();
|
return resolve();
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED`
|
* Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED`
|
||||||
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not
|
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not
|
||||||
|
@ -149,7 +153,11 @@ export class LocalRecorder {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) {
|
if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) {
|
||||||
throw (Error('\'LocalRecord.stop()\' needs \'LocalRecord.state\' to be \'RECORDING\' or \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' before'));
|
throw Error(
|
||||||
|
"'LocalRecord.stop()' needs 'LocalRecord.state' to be 'RECORDING' or 'PAUSED' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.start()' before"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.mediaRecorder.onstop = () => {
|
this.mediaRecorder.onstop = () => {
|
||||||
this.onStopDefault();
|
this.onStopDefault();
|
||||||
|
@ -162,7 +170,6 @@ export class LocalRecorder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED`
|
* Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED`
|
||||||
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not
|
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not
|
||||||
|
@ -171,7 +178,13 @@ export class LocalRecorder {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (this.state !== LocalRecorderState.RECORDING) {
|
if (this.state !== LocalRecorderState.RECORDING) {
|
||||||
return reject(Error('\'LocalRecord.pause()\' needs \'LocalRecord.state\' to be \'RECORDING\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' or \'LocalRecorder.resume()\' before'));
|
return reject(
|
||||||
|
Error(
|
||||||
|
"'LocalRecord.pause()' needs 'LocalRecord.state' to be 'RECORDING' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.start()' or 'LocalRecorder.resume()' before"
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.mediaRecorder.pause();
|
this.mediaRecorder.pause();
|
||||||
this.state = LocalRecorderState.PAUSED;
|
this.state = LocalRecorderState.PAUSED;
|
||||||
|
@ -190,7 +203,11 @@ export class LocalRecorder {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (this.state !== LocalRecorderState.PAUSED) {
|
if (this.state !== LocalRecorderState.PAUSED) {
|
||||||
throw (Error('\'LocalRecord.resume()\' needs \'LocalRecord.state\' to be \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.pause()\' before'));
|
throw Error(
|
||||||
|
"'LocalRecord.resume()' needs 'LocalRecord.state' to be 'PAUSED' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.pause()' before"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.mediaRecorder.resume();
|
this.mediaRecorder.resume();
|
||||||
this.state = LocalRecorderState.RECORDING;
|
this.state = LocalRecorderState.RECORDING;
|
||||||
|
@ -201,14 +218,16 @@ export class LocalRecorder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. [[state]] property must be `FINISHED`
|
* Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. [[state]] property must be `FINISHED`
|
||||||
*/
|
*/
|
||||||
preview(parentElement): HTMLVideoElement {
|
preview(parentElement): HTMLVideoElement {
|
||||||
|
|
||||||
if (this.state !== LocalRecorderState.FINISHED) {
|
if (this.state !== LocalRecorderState.FINISHED) {
|
||||||
throw (Error('\'LocalRecord.preview()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
|
throw Error(
|
||||||
|
"'LocalRecord.preview()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.stop()' before"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.videoPreview = document.createElement('video');
|
this.videoPreview = document.createElement('video');
|
||||||
|
@ -234,7 +253,6 @@ export class LocalRecorder {
|
||||||
return this.videoPreview;
|
return this.videoPreview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gracefully stops and cleans the current recording (WARNING: it is completely dismissed). Sets [[state]] to `READY` so the recording can start again
|
* Gracefully stops and cleans the current recording (WARNING: it is completely dismissed). Sets [[state]] to `READY` so the recording can start again
|
||||||
*/
|
*/
|
||||||
|
@ -245,19 +263,24 @@ export class LocalRecorder {
|
||||||
this.state = LocalRecorderState.READY;
|
this.state = LocalRecorderState.READY;
|
||||||
};
|
};
|
||||||
if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) {
|
if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) {
|
||||||
this.stop().then(() => f()).catch(() => f());
|
this.stop()
|
||||||
|
.then(() => f())
|
||||||
|
.catch(() => f());
|
||||||
} else {
|
} else {
|
||||||
f();
|
f();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads the recorded video through the browser. [[state]] property must be `FINISHED`
|
* Downloads the recorded video through the browser. [[state]] property must be `FINISHED`
|
||||||
*/
|
*/
|
||||||
download(): void {
|
download(): void {
|
||||||
if (this.state !== LocalRecorderState.FINISHED) {
|
if (this.state !== LocalRecorderState.FINISHED) {
|
||||||
throw (Error('\'LocalRecord.download()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
|
throw Error(
|
||||||
|
"'LocalRecord.download()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.stop()' before"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const a: HTMLAnchorElement = document.createElement('a');
|
const a: HTMLAnchorElement = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
|
@ -278,13 +301,12 @@ export class LocalRecorder {
|
||||||
*/
|
*/
|
||||||
getBlob(): Blob {
|
getBlob(): Blob {
|
||||||
if (this.state !== LocalRecorderState.FINISHED) {
|
if (this.state !== LocalRecorderState.FINISHED) {
|
||||||
throw (Error('Call \'LocalRecord.stop()\' before getting Blob file'));
|
throw Error("Call 'LocalRecord.stop()' before getting Blob file");
|
||||||
} else {
|
} else {
|
||||||
return this.blob!;
|
return this.blob!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads the recorded video as a binary file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
|
* Uploads the recorded video as a binary file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
|
||||||
* ```
|
* ```
|
||||||
|
@ -298,7 +320,13 @@ export class LocalRecorder {
|
||||||
uploadAsBinary(endpoint: string, headers?: any): Promise<any> {
|
uploadAsBinary(endpoint: string, headers?: any): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.state !== LocalRecorderState.FINISHED) {
|
if (this.state !== LocalRecorderState.FINISHED) {
|
||||||
return reject(Error('\'LocalRecord.uploadAsBinary()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
|
return reject(
|
||||||
|
Error(
|
||||||
|
"'LocalRecord.uploadAsBinary()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.stop()' before"
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const http = new XMLHttpRequest();
|
const http = new XMLHttpRequest();
|
||||||
http.open('POST', endpoint, true);
|
http.open('POST', endpoint, true);
|
||||||
|
@ -324,7 +352,6 @@ export class LocalRecorder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
|
* Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
|
||||||
* ```
|
* ```
|
||||||
|
@ -338,7 +365,13 @@ export class LocalRecorder {
|
||||||
uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> {
|
uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.state !== LocalRecorderState.FINISHED) {
|
if (this.state !== LocalRecorderState.FINISHED) {
|
||||||
return reject(Error('\'LocalRecord.uploadAsMultipartfile()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
|
return reject(
|
||||||
|
Error(
|
||||||
|
"'LocalRecord.uploadAsMultipartfile()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
|
||||||
|
this.state +
|
||||||
|
"'). Call 'LocalRecorder.stop()' before"
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const http = new XMLHttpRequest();
|
const http = new XMLHttpRequest();
|
||||||
http.open('POST', endpoint, true);
|
http.open('POST', endpoint, true);
|
||||||
|
@ -368,7 +401,6 @@ export class LocalRecorder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Private methods */
|
/* Private methods */
|
||||||
|
|
||||||
private onStopDefault(): void {
|
private onStopDefault(): void {
|
||||||
|
@ -381,5 +413,4 @@ export class LocalRecorder {
|
||||||
|
|
||||||
this.state = LocalRecorderState.FINISHED;
|
this.state = LocalRecorderState.FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
||||||
|
|
||||||
import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
|
import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
|
||||||
import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
|
import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
|
||||||
import { OpenViduLoggerConfiguration } from "../OpenViduInternal/Logger/OpenViduLoggerConfiguration";
|
import { OpenViduLoggerConfiguration } from '../OpenViduInternal/Logger/OpenViduLoggerConfiguration';
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -65,7 +65,6 @@ let platform: PlatformUtils;
|
||||||
* Use it to initialize objects of type [[Session]], [[Publisher]] and [[LocalRecorder]]
|
* Use it to initialize objects of type [[Session]], [[Publisher]] and [[LocalRecorder]]
|
||||||
*/
|
*/
|
||||||
export class OpenVidu {
|
export class OpenVidu {
|
||||||
|
|
||||||
private jsonRpcClient: any;
|
private jsonRpcClient: any;
|
||||||
private masterNodeHasCrashed = false;
|
private masterNodeHasCrashed = false;
|
||||||
|
|
||||||
|
@ -144,19 +143,19 @@ export class OpenVidu {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
ee = new EventEmitter()
|
ee = new EventEmitter();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
platform = PlatformUtils.getInstance();
|
platform = PlatformUtils.getInstance();
|
||||||
this.libraryVersion = packageJson.version;
|
this.libraryVersion = packageJson.version;
|
||||||
logger.info("OpenVidu initialized");
|
logger.info('OpenVidu initialized');
|
||||||
logger.info('Platform detected: ' + platform.getDescription());
|
logger.info('Platform detected: ' + platform.getDescription());
|
||||||
logger.info('openvidu-browser version: ' + this.libraryVersion);
|
logger.info('openvidu-browser version: ' + this.libraryVersion);
|
||||||
|
|
||||||
if (platform.isMobileDevice() || platform.isReactNative()) {
|
if (platform.isMobileDevice() || platform.isReactNative()) {
|
||||||
// Listen to orientationchange only on mobile devices
|
// Listen to orientationchange only on mobile devices
|
||||||
this.onOrientationChanged(() => {
|
this.onOrientationChanged(() => {
|
||||||
this.publishers.forEach(publisher => {
|
this.publishers.forEach((publisher) => {
|
||||||
if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
|
if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
|
||||||
this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
|
this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
|
||||||
}
|
}
|
||||||
|
@ -173,11 +172,14 @@ export class OpenVidu {
|
||||||
return this.session;
|
return this.session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
initPublisher(targetElement: string | HTMLElement | undefined): Publisher;
|
initPublisher(targetElement: string | HTMLElement | undefined): Publisher;
|
||||||
initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Publisher;
|
initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Publisher;
|
||||||
initPublisher(targetElement: string | HTMLElement | undefined, completionHandler: (error: Error | undefined) => void): Publisher;
|
initPublisher(targetElement: string | HTMLElement | undefined, completionHandler: (error: Error | undefined) => void): Publisher;
|
||||||
initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties, completionHandler: (error: Error | undefined) => void): Publisher;
|
initPublisher(
|
||||||
|
targetElement: string | HTMLElement | undefined,
|
||||||
|
properties: PublisherProperties,
|
||||||
|
completionHandler: (error: Error | undefined) => void
|
||||||
|
): Publisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new publisher
|
* Returns a new publisher
|
||||||
|
@ -200,29 +202,41 @@ export class OpenVidu {
|
||||||
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
|
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
|
||||||
*/
|
*/
|
||||||
initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Publisher {
|
initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Publisher {
|
||||||
|
|
||||||
let properties: PublisherProperties;
|
let properties: PublisherProperties;
|
||||||
|
|
||||||
if (!!param2 && (typeof param2 !== 'function')) {
|
if (!!param2 && typeof param2 !== 'function') {
|
||||||
|
|
||||||
// Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
|
// Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
|
||||||
|
|
||||||
properties = (<PublisherProperties>param2);
|
properties = <PublisherProperties>param2;
|
||||||
|
|
||||||
properties = {
|
properties = {
|
||||||
audioSource: (typeof properties.audioSource !== 'undefined') ? properties.audioSource : undefined,
|
audioSource: typeof properties.audioSource !== 'undefined' ? properties.audioSource : undefined,
|
||||||
frameRate: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.frameRate !== 'undefined') ? properties.frameRate : undefined),
|
frameRate:
|
||||||
insertMode: (typeof properties.insertMode !== 'undefined') ? ((typeof properties.insertMode === 'string') ? VideoInsertMode[properties.insertMode] : properties.insertMode) : VideoInsertMode.APPEND,
|
typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
|
||||||
mirror: (typeof properties.mirror !== 'undefined') ? properties.mirror : true,
|
? undefined
|
||||||
publishAudio: (typeof properties.publishAudio !== 'undefined') ? properties.publishAudio : true,
|
: typeof properties.frameRate !== 'undefined'
|
||||||
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true,
|
? properties.frameRate
|
||||||
resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'),
|
: undefined,
|
||||||
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined,
|
insertMode:
|
||||||
|
typeof properties.insertMode !== 'undefined'
|
||||||
|
? typeof properties.insertMode === 'string'
|
||||||
|
? VideoInsertMode[properties.insertMode]
|
||||||
|
: properties.insertMode
|
||||||
|
: VideoInsertMode.APPEND,
|
||||||
|
mirror: typeof properties.mirror !== 'undefined' ? properties.mirror : true,
|
||||||
|
publishAudio: typeof properties.publishAudio !== 'undefined' ? properties.publishAudio : true,
|
||||||
|
publishVideo: typeof properties.publishVideo !== 'undefined' ? properties.publishVideo : true,
|
||||||
|
resolution:
|
||||||
|
typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
|
||||||
|
? undefined
|
||||||
|
: typeof properties.resolution !== 'undefined'
|
||||||
|
? properties.resolution
|
||||||
|
: '640x480',
|
||||||
|
videoSource: typeof properties.videoSource !== 'undefined' ? properties.videoSource : undefined,
|
||||||
videoSimulcast: properties.videoSimulcast,
|
videoSimulcast: properties.videoSimulcast,
|
||||||
filter: properties.filter
|
filter: properties.filter
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
|
// Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
|
||||||
|
|
||||||
properties = {
|
properties = {
|
||||||
|
@ -237,19 +251,21 @@ export class OpenVidu {
|
||||||
const publisher: Publisher = new Publisher(targetElement, properties, this);
|
const publisher: Publisher = new Publisher(targetElement, properties, this);
|
||||||
|
|
||||||
let completionHandler: (error: Error | undefined) => void;
|
let completionHandler: (error: Error | undefined) => void;
|
||||||
if (!!param2 && (typeof param2 === 'function')) {
|
if (!!param2 && typeof param2 === 'function') {
|
||||||
completionHandler = param2;
|
completionHandler = param2;
|
||||||
} else if (!!param3) {
|
} else if (!!param3) {
|
||||||
completionHandler = param3;
|
completionHandler = param3;
|
||||||
}
|
}
|
||||||
|
|
||||||
publisher.initialize()
|
publisher
|
||||||
|
.initialize()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (completionHandler !== undefined) {
|
if (completionHandler !== undefined) {
|
||||||
completionHandler(undefined);
|
completionHandler(undefined);
|
||||||
}
|
}
|
||||||
publisher.emitEvent('accessAllowed', []);
|
publisher.emitEvent('accessAllowed', []);
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
if (completionHandler !== undefined) {
|
if (completionHandler !== undefined) {
|
||||||
completionHandler(error);
|
completionHandler(error);
|
||||||
}
|
}
|
||||||
|
@ -260,7 +276,6 @@ export class OpenVidu {
|
||||||
return publisher;
|
return publisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promisified version of [[OpenVidu.initPublisher]]
|
* Promisified version of [[OpenVidu.initPublisher]]
|
||||||
*
|
*
|
||||||
|
@ -271,7 +286,6 @@ export class OpenVidu {
|
||||||
|
|
||||||
initPublisherAsync(targetElement: string | HTMLElement | undefined, properties?: PublisherProperties): Promise<Publisher> {
|
initPublisherAsync(targetElement: string | HTMLElement | undefined, properties?: PublisherProperties): Promise<Publisher> {
|
||||||
return new Promise<Publisher>((resolve, reject) => {
|
return new Promise<Publisher>((resolve, reject) => {
|
||||||
|
|
||||||
let publisher: Publisher;
|
let publisher: Publisher;
|
||||||
|
|
||||||
const callback = (error: Error) => {
|
const callback = (error: Error) => {
|
||||||
|
@ -290,7 +304,6 @@ export class OpenVidu {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new local recorder for recording streams straight away from the browser
|
* Returns a new local recorder for recording streams straight away from the browser
|
||||||
* @param stream Stream to record
|
* @param stream Stream to record
|
||||||
|
@ -299,7 +312,6 @@ export class OpenVidu {
|
||||||
return new LocalRecorder(stream);
|
return new LocalRecorder(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the browser supports OpenVidu
|
* Checks if the browser supports OpenVidu
|
||||||
* @returns 1 if the browser supports OpenVidu, 0 otherwise
|
* @returns 1 if the browser supports OpenVidu, 0 otherwise
|
||||||
|
@ -343,17 +355,18 @@ export class OpenVidu {
|
||||||
return platform.canScreenShare();
|
return platform.canScreenShare();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects information about the media input devices available on the system. You can pass property `deviceId` of a [[Device]] object as value of `audioSource` or `videoSource` properties in [[initPublisher]] method
|
* Collects information about the media input devices available on the system. You can pass property `deviceId` of a [[Device]] object as value of `audioSource` or `videoSource` properties in [[initPublisher]] method
|
||||||
*/
|
*/
|
||||||
getDevices(): Promise<Device[]> {
|
getDevices(): Promise<Device[]> {
|
||||||
return new Promise<Device[]>((resolve, reject) => {
|
return new Promise<Device[]>((resolve, reject) => {
|
||||||
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
|
navigator.mediaDevices
|
||||||
|
.enumerateDevices()
|
||||||
|
.then((deviceInfos) => {
|
||||||
const devices: Device[] = [];
|
const devices: Device[] = [];
|
||||||
|
|
||||||
// Ionic Android devices
|
// Ionic Android devices
|
||||||
if (platform.isIonicAndroid() && typeof cordova != "undefined" && cordova?.plugins?.EnumerateDevicesPlugin) {
|
if (platform.isIonicAndroid() && typeof cordova != 'undefined' && cordova?.plugins?.EnumerateDevicesPlugin) {
|
||||||
cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => {
|
cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => {
|
||||||
let pluginAudioDevices: Device[] = [];
|
let pluginAudioDevices: Device[] = [];
|
||||||
let videoDevices: Device[] = [];
|
let videoDevices: Device[] = [];
|
||||||
|
@ -363,20 +376,19 @@ export class OpenVidu {
|
||||||
audioDevices = deviceInfos.filter((device: Device) => device.kind === 'audioinput');
|
audioDevices = deviceInfos.filter((device: Device) => device.kind === 'audioinput');
|
||||||
videoDevices.forEach((deviceInfo, index) => {
|
videoDevices.forEach((deviceInfo, index) => {
|
||||||
if (!deviceInfo.label) {
|
if (!deviceInfo.label) {
|
||||||
let label = "";
|
let label = '';
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
label = "Front Camera";
|
label = 'Front Camera';
|
||||||
} else if (index === 1) {
|
} else if (index === 1) {
|
||||||
label = "Back Camera";
|
label = 'Back Camera';
|
||||||
} else {
|
} else {
|
||||||
label = "Unknown Camera";
|
label = 'Unknown Camera';
|
||||||
}
|
}
|
||||||
devices.push({
|
devices.push({
|
||||||
kind: deviceInfo.kind,
|
kind: deviceInfo.kind,
|
||||||
deviceId: deviceInfo.deviceId,
|
deviceId: deviceInfo.deviceId,
|
||||||
label: label
|
label: label
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
devices.push({
|
devices.push({
|
||||||
kind: deviceInfo.kind,
|
kind: deviceInfo.kind,
|
||||||
|
@ -387,7 +399,7 @@ export class OpenVidu {
|
||||||
});
|
});
|
||||||
audioDevices.forEach((deviceInfo, index) => {
|
audioDevices.forEach((deviceInfo, index) => {
|
||||||
if (!deviceInfo.label) {
|
if (!deviceInfo.label) {
|
||||||
let label = "";
|
let label = '';
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0: // Default Microphone
|
case 0: // Default Microphone
|
||||||
label = 'Default';
|
label = 'Default';
|
||||||
|
@ -409,7 +421,7 @@ export class OpenVidu {
|
||||||
label = wirelessMatch ? wirelessMatch.label : 'Wireless';
|
label = wirelessMatch ? wirelessMatch.label : 'Wireless';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
label = "Unknown Microphone";
|
label = 'Unknown Microphone';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
devices.push({
|
devices.push({
|
||||||
|
@ -417,7 +429,6 @@ export class OpenVidu {
|
||||||
deviceId: deviceInfo.deviceId,
|
deviceId: deviceInfo.deviceId,
|
||||||
label: label
|
label: label
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
devices.push({
|
devices.push({
|
||||||
kind: deviceInfo.kind,
|
kind: deviceInfo.kind,
|
||||||
|
@ -429,9 +440,8 @@ export class OpenVidu {
|
||||||
return resolve(devices);
|
return resolve(devices);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Rest of platforms
|
// Rest of platforms
|
||||||
deviceInfos.forEach(deviceInfo => {
|
deviceInfos.forEach((deviceInfo) => {
|
||||||
if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
|
if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
|
||||||
devices.push({
|
devices.push({
|
||||||
kind: deviceInfo.kind,
|
kind: deviceInfo.kind,
|
||||||
|
@ -442,15 +452,14 @@ export class OpenVidu {
|
||||||
});
|
});
|
||||||
return resolve(devices);
|
return resolve(devices);
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
logger.error('Error getting devices', error);
|
logger.error('Error getting devices', error);
|
||||||
return reject(error);
|
return reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a MediaStream object that you can customize before calling [[initPublisher]] (pass _MediaStreamTrack_ property of the _MediaStream_ value resolved by the Promise as `audioSource` or `videoSource` properties in [[initPublisher]])
|
* Get a MediaStream object that you can customize before calling [[initPublisher]] (pass _MediaStreamTrack_ property of the _MediaStream_ value resolved by the Promise as `audioSource` or `videoSource` properties in [[initPublisher]])
|
||||||
*
|
*
|
||||||
|
@ -500,9 +509,8 @@ export class OpenVidu {
|
||||||
*/
|
*/
|
||||||
getUserMedia(options: PublisherProperties): Promise<MediaStream> {
|
getUserMedia(options: PublisherProperties): Promise<MediaStream> {
|
||||||
return new Promise<MediaStream>(async (resolve, reject) => {
|
return new Promise<MediaStream>(async (resolve, reject) => {
|
||||||
|
|
||||||
const askForAudioStreamOnly = async (previousMediaStream: MediaStream, constraints: MediaStreamConstraints) => {
|
const askForAudioStreamOnly = async (previousMediaStream: MediaStream, constraints: MediaStreamConstraints) => {
|
||||||
const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio);
|
const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
|
||||||
const constraintsAux: MediaStreamConstraints = { audio: definedAudioConstraint, video: false };
|
const constraintsAux: MediaStreamConstraints = { audio: definedAudioConstraint, video: false };
|
||||||
try {
|
try {
|
||||||
const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
|
const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
|
||||||
|
@ -517,17 +525,17 @@ export class OpenVidu {
|
||||||
});
|
});
|
||||||
return reject(this.generateAudioDeviceError(error, constraintsAux));
|
return reject(this.generateAudioDeviceError(error, constraintsAux));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const myConstraints = await this.generateMediaConstraints(options);
|
const myConstraints = await this.generateMediaConstraints(options);
|
||||||
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack ||
|
if (
|
||||||
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
|
(!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
|
||||||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
|
(!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
|
||||||
|
(!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
|
||||||
|
) {
|
||||||
// No need to call getUserMedia at all. Both tracks provided, or only AUDIO track provided or only VIDEO track provided
|
// No need to call getUserMedia at all. Both tracks provided, or only AUDIO track provided or only VIDEO track provided
|
||||||
return resolve(this.addAlreadyProvidedTracks(myConstraints, new MediaStream()));
|
return resolve(this.addAlreadyProvidedTracks(myConstraints, new MediaStream()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// getUserMedia must be called. AUDIO or VIDEO are requesting a new track
|
// getUserMedia must be called. AUDIO or VIDEO are requesting a new track
|
||||||
|
|
||||||
|
@ -542,11 +550,14 @@ export class OpenVidu {
|
||||||
let mustAskForAudioTrackLater = false;
|
let mustAskForAudioTrackLater = false;
|
||||||
if (typeof options.videoSource === 'string') {
|
if (typeof options.videoSource === 'string') {
|
||||||
// Video is deviceId or screen sharing
|
// Video is deviceId or screen sharing
|
||||||
if (options.videoSource === 'screen' ||
|
if (
|
||||||
|
options.videoSource === 'screen' ||
|
||||||
options.videoSource === 'window' ||
|
options.videoSource === 'window' ||
|
||||||
(platform.isElectron() && options.videoSource.startsWith('screen:'))) {
|
(platform.isElectron() && options.videoSource.startsWith('screen:'))
|
||||||
|
) {
|
||||||
// Video is screen sharing
|
// Video is screen sharing
|
||||||
mustAskForAudioTrackLater = !myConstraints.audioTrack && (options.audioSource !== null && options.audioSource !== false);
|
mustAskForAudioTrackLater =
|
||||||
|
!myConstraints.audioTrack && options.audioSource !== null && options.audioSource !== false;
|
||||||
if (navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
|
if (navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
|
||||||
// getDisplayMedia supported
|
// getDisplayMedia supported
|
||||||
try {
|
try {
|
||||||
|
@ -557,13 +568,11 @@ export class OpenVidu {
|
||||||
} else {
|
} else {
|
||||||
return resolve(mediaStream);
|
return resolve(mediaStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let errorName: OpenViduErrorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
|
let errorName: OpenViduErrorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
|
||||||
const errorMessage = error.toString();
|
const errorMessage = error.toString();
|
||||||
return reject(new OpenViduError(errorName, errorMessage));
|
return reject(new OpenViduError(errorName, errorMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// getDisplayMedia NOT supported. Can perform getUserMedia below with already calculated constraints
|
// getDisplayMedia NOT supported. Can perform getUserMedia below with already calculated constraints
|
||||||
}
|
}
|
||||||
|
@ -572,7 +581,9 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Use already calculated constraints
|
// Use already calculated constraints
|
||||||
const constraintsAux = mustAskForAudioTrackLater ? { video: myConstraints.constraints!.video } : myConstraints.constraints;
|
const constraintsAux = mustAskForAudioTrackLater
|
||||||
|
? { video: myConstraints.constraints!.video }
|
||||||
|
: myConstraints.constraints;
|
||||||
try {
|
try {
|
||||||
const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
|
const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
|
||||||
this.addAlreadyProvidedTracks(myConstraints, mediaStream);
|
this.addAlreadyProvidedTracks(myConstraints, mediaStream);
|
||||||
|
@ -598,7 +609,6 @@ export class OpenVidu {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
/**
|
/**
|
||||||
* Disable all logging except error level
|
* Disable all logging except error level
|
||||||
|
@ -608,7 +618,6 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
/* tslint:enable:no-empty */
|
/* tslint:enable:no-empty */
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set OpenVidu advanced configuration options. `configuration` is an object of type [[OpenViduAdvancedConfiguration]]. Call this method to override previous values at any moment.
|
* Set OpenVidu advanced configuration options. `configuration` is an object of type [[OpenViduAdvancedConfiguration]]. Call this method to override previous values at any moment.
|
||||||
*/
|
*/
|
||||||
|
@ -616,7 +625,6 @@ export class OpenVidu {
|
||||||
this.advancedConfiguration = configuration;
|
this.advancedConfiguration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Hidden methods */
|
/* Hidden methods */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -639,7 +647,7 @@ export class OpenVidu {
|
||||||
if (attempts > MAX_ATTEMPTS) {
|
if (attempts > MAX_ATTEMPTS) {
|
||||||
clearTimeout(repeatUntilChangeOrMaxAttempts);
|
clearTimeout(repeatUntilChangeOrMaxAttempts);
|
||||||
}
|
}
|
||||||
publisher.getVideoDimensions().then(newDimensions => {
|
publisher.getVideoDimensions().then((newDimensions) => {
|
||||||
if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
|
if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
|
||||||
clearTimeout(repeatUntilChangeOrMaxAttempts);
|
clearTimeout(repeatUntilChangeOrMaxAttempts);
|
||||||
this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
|
this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
|
||||||
|
@ -651,7 +659,14 @@ export class OpenVidu {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
sendVideoDimensionsChangedEvent(publisher: Publisher, reason: string, oldWidth: number, oldHeight: number, newWidth: number, newHeight: number) {
|
sendVideoDimensionsChangedEvent(
|
||||||
|
publisher: Publisher,
|
||||||
|
reason: string,
|
||||||
|
oldWidth: number,
|
||||||
|
oldHeight: number,
|
||||||
|
newWidth: number,
|
||||||
|
newHeight: number
|
||||||
|
) {
|
||||||
publisher.stream.videoDimensions = {
|
publisher.stream.videoDimensions = {
|
||||||
width: newWidth || 0,
|
width: newWidth || 0,
|
||||||
height: newHeight || 0
|
height: newHeight || 0
|
||||||
|
@ -668,40 +683,68 @@ export class OpenVidu {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error("Error sending 'streamPropertyChanged' event", error);
|
logger.error("Error sending 'streamPropertyChanged' event", error);
|
||||||
} else {
|
} else {
|
||||||
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, reason)]);
|
this.session.emitEvent('streamPropertyChanged', [
|
||||||
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, reason)]);
|
new StreamPropertyChangedEvent(
|
||||||
|
this.session,
|
||||||
|
publisher.stream,
|
||||||
|
'videoDimensions',
|
||||||
|
publisher.stream.videoDimensions,
|
||||||
|
{ width: oldWidth, height: oldHeight },
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
publisher.emitEvent('streamPropertyChanged', [
|
||||||
|
new StreamPropertyChangedEvent(
|
||||||
|
publisher,
|
||||||
|
publisher.stream,
|
||||||
|
'videoDimensions',
|
||||||
|
publisher.stream.videoDimensions,
|
||||||
|
{ width: oldWidth, height: oldHeight },
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
]);
|
||||||
this.session.sendVideoData(publisher);
|
this.session.sendVideoData(publisher);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
sendTrackChangedEvent(publisher: Publisher, reason: string, oldLabel: string, newLabel: string, propertyType: string) {
|
sendTrackChangedEvent(publisher: Publisher, reason: string, oldLabel: string, newLabel: string, propertyType: string) {
|
||||||
const oldValue = {label: oldLabel};
|
const oldValue = { label: oldLabel };
|
||||||
const newValue = {label: newLabel};
|
const newValue = { label: newLabel };
|
||||||
|
|
||||||
if(publisher.stream.isLocalStreamPublished){
|
if (publisher.stream.isLocalStreamPublished) {
|
||||||
this.sendRequest(
|
this.sendRequest(
|
||||||
'streamPropertyChanged',
|
'streamPropertyChanged',
|
||||||
{
|
{
|
||||||
streamId: publisher.stream.streamId,
|
streamId: publisher.stream.streamId,
|
||||||
property: propertyType,
|
property: propertyType,
|
||||||
newValue: JSON.stringify({newLabel}),
|
newValue: JSON.stringify({ newLabel }),
|
||||||
reason
|
reason
|
||||||
},
|
},
|
||||||
(error, response) => {
|
(error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error("Error sending 'streamPropertyChanged' event", error);
|
logger.error("Error sending 'streamPropertyChanged' event", error);
|
||||||
} else {
|
} else {
|
||||||
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)]);
|
this.session.emitEvent('streamPropertyChanged', [
|
||||||
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)]);
|
new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
|
||||||
|
]);
|
||||||
|
publisher.emitEvent('streamPropertyChanged', [
|
||||||
|
new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)]);
|
this.session.emitEvent('streamPropertyChanged', [
|
||||||
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)]);
|
new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
|
||||||
|
]);
|
||||||
|
publisher.emitEvent('streamPropertyChanged', [
|
||||||
|
new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,7 +753,6 @@ export class OpenVidu {
|
||||||
*/
|
*/
|
||||||
generateMediaConstraints(publisherProperties: PublisherProperties): Promise<CustomMediaStreamConstraints> {
|
generateMediaConstraints(publisherProperties: PublisherProperties): Promise<CustomMediaStreamConstraints> {
|
||||||
return new Promise<CustomMediaStreamConstraints>((resolve, reject) => {
|
return new Promise<CustomMediaStreamConstraints>((resolve, reject) => {
|
||||||
|
|
||||||
const myConstraints: CustomMediaStreamConstraints = {
|
const myConstraints: CustomMediaStreamConstraints = {
|
||||||
audioTrack: undefined,
|
audioTrack: undefined,
|
||||||
videoTrack: undefined,
|
videoTrack: undefined,
|
||||||
|
@ -718,7 +760,7 @@ export class OpenVidu {
|
||||||
audio: undefined,
|
audio: undefined,
|
||||||
video: undefined
|
video: undefined
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const audioSource = publisherProperties.audioSource;
|
const audioSource = publisherProperties.audioSource;
|
||||||
const videoSource = publisherProperties.videoSource;
|
const videoSource = publisherProperties.videoSource;
|
||||||
|
|
||||||
|
@ -733,8 +775,12 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
if (myConstraints.constraints!.audio === false && myConstraints.constraints!.video === false) {
|
if (myConstraints.constraints!.audio === false && myConstraints.constraints!.video === false) {
|
||||||
// ERROR! audioSource and videoSource cannot be both false at the same time
|
// ERROR! audioSource and videoSource cannot be both false at the same time
|
||||||
return reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET,
|
return reject(
|
||||||
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time"));
|
new OpenViduError(
|
||||||
|
OpenViduErrorName.NO_INPUT_SOURCE_SET,
|
||||||
|
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time"
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE 2: MediaStreamTracks
|
// CASE 2: MediaStreamTracks
|
||||||
|
@ -775,7 +821,7 @@ export class OpenVidu {
|
||||||
height: {
|
height: {
|
||||||
ideal: idealHeight
|
ideal: idealHeight
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (!!publisherProperties.frameRate) {
|
if (!!publisherProperties.frameRate) {
|
||||||
(<MediaTrackConstraints>myConstraints.constraints!.video).frameRate = { ideal: publisherProperties.frameRate };
|
(<MediaTrackConstraints>myConstraints.constraints!.video).frameRate = { ideal: publisherProperties.frameRate };
|
||||||
|
@ -833,8 +879,8 @@ export class OpenVidu {
|
||||||
onMasterNodeCrashedNotification(response): void {
|
onMasterNodeCrashedNotification(response): void {
|
||||||
console.error('Master Node has crashed');
|
console.error('Master Node has crashed');
|
||||||
this.masterNodeHasCrashed = true;
|
this.masterNodeHasCrashed = true;
|
||||||
this.session.onLostConnection("nodeCrashed");
|
this.session.onLostConnection('nodeCrashed');
|
||||||
this.jsonRpcClient.close(4103, "Master Node has crashed");
|
this.jsonRpcClient.close(4103, 'Master Node has crashed');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -848,7 +894,7 @@ export class OpenVidu {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
closeWs(): void {
|
closeWs(): void {
|
||||||
this.jsonRpcClient.close(4102, "Connection closed by client");
|
this.jsonRpcClient.close(4102, 'Connection closed by client');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -905,7 +951,10 @@ export class OpenVidu {
|
||||||
case 'overconstrainederror':
|
case 'overconstrainederror':
|
||||||
if (error.constraint.toLowerCase() === 'deviceid') {
|
if (error.constraint.toLowerCase() === 'deviceid') {
|
||||||
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact + "' not found";
|
errorMessage =
|
||||||
|
"Audio input device with deviceId '" +
|
||||||
|
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact +
|
||||||
|
"' not found";
|
||||||
} else {
|
} else {
|
||||||
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
||||||
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
||||||
|
@ -914,7 +963,7 @@ export class OpenVidu {
|
||||||
case 'notreadableerror':
|
case 'notreadableerror':
|
||||||
errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
|
errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
|
||||||
errorMessage = error.toString();
|
errorMessage = error.toString();
|
||||||
return (new OpenViduError(errorName, errorMessage));
|
return new OpenViduError(errorName, errorMessage);
|
||||||
default:
|
default:
|
||||||
return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString());
|
return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString());
|
||||||
}
|
}
|
||||||
|
@ -943,7 +992,12 @@ export class OpenVidu {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
protected configureDeviceIdOrScreensharing(myConstraints: CustomMediaStreamConstraints, publisherProperties: PublisherProperties, resolve, reject) {
|
protected configureDeviceIdOrScreensharing(
|
||||||
|
myConstraints: CustomMediaStreamConstraints,
|
||||||
|
publisherProperties: PublisherProperties,
|
||||||
|
resolve,
|
||||||
|
reject
|
||||||
|
) {
|
||||||
const audioSource = publisherProperties.audioSource;
|
const audioSource = publisherProperties.audioSource;
|
||||||
const videoSource = publisherProperties.videoSource;
|
const videoSource = publisherProperties.videoSource;
|
||||||
if (typeof audioSource === 'string') {
|
if (typeof audioSource === 'string') {
|
||||||
|
@ -951,22 +1005,24 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof videoSource === 'string') {
|
if (typeof videoSource === 'string') {
|
||||||
|
|
||||||
if (!this.isScreenShare(videoSource)) {
|
if (!this.isScreenShare(videoSource)) {
|
||||||
this.setVideoSource(myConstraints, videoSource);
|
this.setVideoSource(myConstraints, videoSource);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Screen sharing
|
// Screen sharing
|
||||||
|
|
||||||
if (!this.checkScreenSharingCapabilities()) {
|
if (!this.checkScreenSharingCapabilities()) {
|
||||||
const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0), Edge (>= 80) or Electron. Detected client: ' + platform.getName() + ' ' + platform.getVersion());
|
const error = new OpenViduError(
|
||||||
|
OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED,
|
||||||
|
'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0), Edge (>= 80) or Electron. Detected client: ' +
|
||||||
|
platform.getName() +
|
||||||
|
' ' +
|
||||||
|
platform.getVersion()
|
||||||
|
);
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return reject(error);
|
return reject(error);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (platform.isElectron()) {
|
if (platform.isElectron()) {
|
||||||
const prefix = "screen:";
|
const prefix = 'screen:';
|
||||||
const videoSourceString: string = videoSource;
|
const videoSourceString: string = videoSource;
|
||||||
const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length);
|
const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length);
|
||||||
(<any>myConstraints.constraints!.video) = {
|
(<any>myConstraints.constraints!.video) = {
|
||||||
|
@ -976,29 +1032,45 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return resolve(myConstraints);
|
return resolve(myConstraints);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if (
|
||||||
if (!!this.advancedConfiguration.screenShareChromeExtension && !(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) && !navigator.mediaDevices['getDisplayMedia']) {
|
!!this.advancedConfiguration.screenShareChromeExtension &&
|
||||||
|
!(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) &&
|
||||||
|
!navigator.mediaDevices['getDisplayMedia']
|
||||||
|
) {
|
||||||
// Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia()
|
// Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia()
|
||||||
|
|
||||||
screenSharing.getScreenConstraints((error, screenConstraints) => {
|
screenSharing.getScreenConstraints((error, screenConstraints) => {
|
||||||
if (!!error || !!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen') {
|
if (
|
||||||
|
!!error ||
|
||||||
|
(!!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen')
|
||||||
|
) {
|
||||||
if (error === 'permission-denied' || error === 'PermissionDeniedError') {
|
if (error === 'permission-denied' || error === 'PermissionDeniedError') {
|
||||||
const error = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop');
|
const error = new OpenViduError(
|
||||||
|
OpenViduErrorName.SCREEN_CAPTURE_DENIED,
|
||||||
|
'You must allow access to one window of your desktop'
|
||||||
|
);
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return reject(error);
|
return reject(error);
|
||||||
} else {
|
} else {
|
||||||
const extensionId = this.advancedConfiguration.screenShareChromeExtension!.split('/').pop()!!.trim();
|
const extensionId = this.advancedConfiguration
|
||||||
screenSharing.getChromeExtensionStatus(extensionId, status => {
|
.screenShareChromeExtension!.split('/')
|
||||||
|
.pop()!!
|
||||||
|
.trim();
|
||||||
|
screenSharing.getChromeExtensionStatus(extensionId, (status) => {
|
||||||
if (status === 'installed-disabled') {
|
if (status === 'installed-disabled') {
|
||||||
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension');
|
const error = new OpenViduError(
|
||||||
|
OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
|
||||||
|
'You must enable the screen extension'
|
||||||
|
);
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
if (status === 'not-installed') {
|
if (status === 'not-installed') {
|
||||||
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, (<string>this.advancedConfiguration.screenShareChromeExtension));
|
const error = new OpenViduError(
|
||||||
|
OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED,
|
||||||
|
<string>this.advancedConfiguration.screenShareChromeExtension
|
||||||
|
);
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
@ -1012,32 +1084,44 @@ export class OpenVidu {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (navigator.mediaDevices['getDisplayMedia']) {
|
if (navigator.mediaDevices['getDisplayMedia']) {
|
||||||
// getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13)
|
// getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13)
|
||||||
return resolve(myConstraints);
|
return resolve(myConstraints);
|
||||||
} else {
|
} else {
|
||||||
// Default screen sharing extension for Chrome/Opera, or is Firefox < 66
|
// Default screen sharing extension for Chrome/Opera, or is Firefox < 66
|
||||||
const firefoxString = (platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) ? publisherProperties.videoSource : undefined;
|
const firefoxString =
|
||||||
|
platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()
|
||||||
|
? publisherProperties.videoSource
|
||||||
|
: undefined;
|
||||||
|
|
||||||
screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => {
|
screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => {
|
||||||
if (!!error) {
|
if (!!error) {
|
||||||
if (error === 'not-installed') {
|
if (error === 'not-installed') {
|
||||||
const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension ? this.advancedConfiguration.screenShareChromeExtension :
|
const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension
|
||||||
'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
|
? this.advancedConfiguration.screenShareChromeExtension
|
||||||
|
: 'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
|
||||||
const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl);
|
const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl);
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return reject(err);
|
return reject(err);
|
||||||
} else if (error === 'installed-disabled') {
|
} else if (error === 'installed-disabled') {
|
||||||
const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension');
|
const err = new OpenViduError(
|
||||||
|
OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
|
||||||
|
'You must enable the screen extension'
|
||||||
|
);
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return reject(err);
|
return reject(err);
|
||||||
} else if (error === 'permission-denied') {
|
} else if (error === 'permission-denied') {
|
||||||
const err = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop');
|
const err = new OpenViduError(
|
||||||
|
OpenViduErrorName.SCREEN_CAPTURE_DENIED,
|
||||||
|
'You must allow access to one window of your desktop'
|
||||||
|
);
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return reject(err);
|
return reject(err);
|
||||||
} else {
|
} else {
|
||||||
const err = new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Unknown error when accessing screen share');
|
const err = new OpenViduError(
|
||||||
|
OpenViduErrorName.GENERIC_ERROR,
|
||||||
|
'Unknown error when accessing screen share'
|
||||||
|
);
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return reject(err);
|
return reject(err);
|
||||||
|
@ -1066,7 +1150,6 @@ export class OpenVidu {
|
||||||
(<MediaTrackConstraints>myConstraints.constraints!.video)['deviceId'] = { exact: videoSource };
|
(<MediaTrackConstraints>myConstraints.constraints!.video)['deviceId'] = { exact: videoSource };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Private methods */
|
/* Private methods */
|
||||||
|
|
||||||
private disconnectCallback(): void {
|
private disconnectCallback(): void {
|
||||||
|
@ -1098,25 +1181,24 @@ export class OpenVidu {
|
||||||
// if life is greater, nodeCrashed
|
// if life is greater, nodeCrashed
|
||||||
this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => {
|
this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => {
|
||||||
if (!!error) {
|
if (!!error) {
|
||||||
|
|
||||||
if (this.isMasterNodeCrashed()) {
|
if (this.isMasterNodeCrashed()) {
|
||||||
|
|
||||||
logger.warn('Master Node has crashed!');
|
logger.warn('Master Node has crashed!');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
||||||
const notifyLostConnection = (reason, errorMsg) => {
|
const notifyLostConnection = (reason, errorMsg) => {
|
||||||
logger.warn(errorMsg);
|
logger.warn(errorMsg);
|
||||||
this.session.onLostConnection(reason);
|
this.session.onLostConnection(reason);
|
||||||
this.jsonRpcClient.close(4101, "Reconnection fault: " + errorMsg);
|
this.jsonRpcClient.close(4101, 'Reconnection fault: ' + errorMsg);
|
||||||
}
|
};
|
||||||
|
|
||||||
const rpcSessionStatus = () => {
|
const rpcSessionStatus = () => {
|
||||||
if (this.life === -1) {
|
if (this.life === -1) {
|
||||||
// Single Master
|
// Single Master
|
||||||
notifyLostConnection('networkDisconnect', 'WS successfully reconnected but the user was already evicted due to timeout');
|
notifyLostConnection(
|
||||||
|
'networkDisconnect',
|
||||||
|
'WS successfully reconnected but the user was already evicted due to timeout'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Multi Master
|
// Multi Master
|
||||||
// This RPC method is only required to find out the reason of the disconnection:
|
// This RPC method is only required to find out the reason of the disconnection:
|
||||||
|
@ -1127,10 +1209,16 @@ export class OpenVidu {
|
||||||
} else {
|
} else {
|
||||||
if (this.life === response.life) {
|
if (this.life === response.life) {
|
||||||
// If the life stored in the client matches the life stored in the server, it means that the client lost its network connection
|
// If the life stored in the client matches the life stored in the server, it means that the client lost its network connection
|
||||||
notifyLostConnection('networkDisconnect', 'WS successfully reconnected but the user was already evicted due to timeout');
|
notifyLostConnection(
|
||||||
|
'networkDisconnect',
|
||||||
|
'WS successfully reconnected but the user was already evicted due to timeout'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// If the life stored in the client is below the life stored in the server, it means that the Master Node has crashed
|
// If the life stored in the client is below the life stored in the server, it means that the Master Node has crashed
|
||||||
notifyLostConnection('nodeCrashed', 'WS successfully reconnected to OpenVidu Server but your Master Node crashed');
|
notifyLostConnection(
|
||||||
|
'nodeCrashed',
|
||||||
|
'WS successfully reconnected to OpenVidu Server but your Master Node crashed'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1148,7 +1236,6 @@ export class OpenVidu {
|
||||||
} else {
|
} else {
|
||||||
rpcSessionStatus();
|
rpcSessionStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.jsonRpcClient.resetPing();
|
this.jsonRpcClient.resetPing();
|
||||||
|
@ -1188,9 +1275,6 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
|
|
||||||
private isScreenShare(videoSource: string) {
|
private isScreenShare(videoSource: string) {
|
||||||
return videoSource === 'screen' ||
|
return videoSource === 'screen' || videoSource === 'window' || (platform.isElectron() && videoSource.startsWith('screen:'));
|
||||||
videoSource === 'window' ||
|
|
||||||
(platform.isElectron() && videoSource.startsWith('screen:'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ let platform: PlatformUtils;
|
||||||
* See available event listeners at [[PublisherEventMap]].
|
* See available event listeners at [[PublisherEventMap]].
|
||||||
*/
|
*/
|
||||||
export class Publisher extends StreamManager {
|
export class Publisher extends StreamManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the Publisher has been granted access to the requested input devices or not
|
* Whether the Publisher has been granted access to the requested input devices or not
|
||||||
*/
|
*/
|
||||||
|
@ -82,7 +81,13 @@ export class Publisher extends StreamManager {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
constructor(targEl: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) {
|
constructor(targEl: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) {
|
||||||
super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
|
super(
|
||||||
|
new Stream(!!openvidu.session ? openvidu.session : new Session(openvidu), {
|
||||||
|
publisherProperties: properties,
|
||||||
|
mediaConstraints: {}
|
||||||
|
}),
|
||||||
|
targEl
|
||||||
|
);
|
||||||
platform = PlatformUtils.getInstance();
|
platform = PlatformUtils.getInstance();
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.openvidu = openvidu;
|
this.openvidu = openvidu;
|
||||||
|
@ -95,7 +100,6 @@ export class Publisher extends StreamManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
|
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
|
||||||
*
|
*
|
||||||
|
@ -115,7 +119,9 @@ export class Publisher extends StreamManager {
|
||||||
*/
|
*/
|
||||||
publishAudio(enabled: boolean): void {
|
publishAudio(enabled: boolean): void {
|
||||||
if (this.stream.audioActive !== enabled) {
|
if (this.stream.audioActive !== enabled) {
|
||||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
|
||||||
|
? this.stream.localMediaStreamWhenSubscribedToRemote!
|
||||||
|
: this.stream.getMediaStream();
|
||||||
affectedMediaStream.getAudioTracks().forEach((track) => {
|
affectedMediaStream.getAudioTracks().forEach((track) => {
|
||||||
track.enabled = enabled;
|
track.enabled = enabled;
|
||||||
});
|
});
|
||||||
|
@ -132,18 +138,22 @@ export class Publisher extends StreamManager {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error("Error sending 'streamPropertyChanged' event", error);
|
logger.error("Error sending 'streamPropertyChanged' event", error);
|
||||||
} else {
|
} else {
|
||||||
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')]);
|
this.session.emitEvent('streamPropertyChanged', [
|
||||||
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')]);
|
new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
|
||||||
|
]);
|
||||||
|
this.emitEvent('streamPropertyChanged', [
|
||||||
|
new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
|
||||||
|
]);
|
||||||
this.session.sendVideoData(this.stream.streamManager);
|
this.session.sendVideoData(this.stream.streamManager);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.stream.audioActive = enabled;
|
this.stream.audioActive = enabled;
|
||||||
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
|
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
|
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
|
||||||
*
|
*
|
||||||
|
@ -169,12 +179,11 @@ export class Publisher extends StreamManager {
|
||||||
* will be used instead.
|
* will be used instead.
|
||||||
*/
|
*/
|
||||||
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
|
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
|
||||||
if (this.stream.videoActive !== enabled) {
|
if (this.stream.videoActive !== enabled) {
|
||||||
|
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
|
||||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
? this.stream.localMediaStreamWhenSubscribedToRemote!
|
||||||
|
: this.stream.getMediaStream();
|
||||||
let mustRestartMediaStream = false;
|
let mustRestartMediaStream = false;
|
||||||
affectedMediaStream.getVideoTracks().forEach((track) => {
|
affectedMediaStream.getVideoTracks().forEach((track) => {
|
||||||
track.enabled = enabled;
|
track.enabled = enabled;
|
||||||
|
@ -212,13 +221,16 @@ export class Publisher extends StreamManager {
|
||||||
delete this.stream.lastVBFilter;
|
delete this.stream.lastVBFilter;
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!!resource && resource instanceof MediaStreamTrack) {
|
if (!!resource && resource instanceof MediaStreamTrack) {
|
||||||
await replaceVideoTrack(resource);
|
await replaceVideoTrack(resource);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: this.stream.lastVideoTrackConstraints });
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: false,
|
||||||
|
video: this.stream.lastVideoTrackConstraints
|
||||||
|
});
|
||||||
await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
|
await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
|
@ -239,11 +251,23 @@ export class Publisher extends StreamManager {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error("Error sending 'streamPropertyChanged' event", error);
|
logger.error("Error sending 'streamPropertyChanged' event", error);
|
||||||
} else {
|
} else {
|
||||||
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
|
this.session.emitEvent('streamPropertyChanged', [
|
||||||
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
|
new StreamPropertyChangedEvent(
|
||||||
|
this.session,
|
||||||
|
this.stream,
|
||||||
|
'videoActive',
|
||||||
|
enabled,
|
||||||
|
!enabled,
|
||||||
|
'publishVideo'
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
this.emitEvent('streamPropertyChanged', [
|
||||||
|
new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
|
||||||
|
]);
|
||||||
this.session.sendVideoData(this.stream.streamManager);
|
this.session.sendVideoData(this.stream.streamManager);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.stream.videoActive = enabled;
|
this.stream.videoActive = enabled;
|
||||||
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
|
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
|
||||||
|
@ -252,22 +276,19 @@ export class Publisher extends StreamManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this method before [[Session.publish]] if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
|
* Call this method before [[Session.publish]] if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
|
||||||
*/
|
*/
|
||||||
subscribeToRemote(value?: boolean): void {
|
subscribeToRemote(value?: boolean): void {
|
||||||
value = (value !== undefined) ? value : true;
|
value = value !== undefined ? value : true;
|
||||||
this.isSubscribedToRemote = value;
|
this.isSubscribedToRemote = value;
|
||||||
this.stream.subscribeToMyRemote(value);
|
this.stream.subscribeToMyRemote(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [[EventDispatcher.on]]
|
* See [[EventDispatcher.on]]
|
||||||
*/
|
*/
|
||||||
on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
|
on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
|
||||||
|
|
||||||
super.on(<any>type, handler);
|
super.on(<any>type, handler);
|
||||||
|
|
||||||
if (type === 'streamCreated') {
|
if (type === 'streamCreated') {
|
||||||
|
@ -292,12 +313,10 @@ export class Publisher extends StreamManager {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [[EventDispatcher.once]]
|
* See [[EventDispatcher.once]]
|
||||||
*/
|
*/
|
||||||
once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
|
once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
|
||||||
|
|
||||||
super.once(<any>type, handler);
|
super.once(<any>type, handler);
|
||||||
|
|
||||||
if (type === 'streamCreated') {
|
if (type === 'streamCreated') {
|
||||||
|
@ -322,7 +341,6 @@ export class Publisher extends StreamManager {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [[EventDispatcher.off]]
|
* See [[EventDispatcher.off]]
|
||||||
*/
|
*/
|
||||||
|
@ -331,7 +349,6 @@ export class Publisher extends StreamManager {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one
|
* Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one
|
||||||
* without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one
|
* without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one
|
||||||
|
@ -359,7 +376,6 @@ export class Publisher extends StreamManager {
|
||||||
*/
|
*/
|
||||||
initialize(): Promise<void> {
|
initialize(): Promise<void> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
|
||||||
let constraints: MediaStreamConstraints = {};
|
let constraints: MediaStreamConstraints = {};
|
||||||
let constraintsAux: MediaStreamConstraints = {};
|
let constraintsAux: MediaStreamConstraints = {};
|
||||||
const timeForDialogEvent = 2000;
|
const timeForDialogEvent = 2000;
|
||||||
|
@ -368,7 +384,7 @@ export class Publisher extends StreamManager {
|
||||||
const errorCallback = (openViduError: OpenViduError) => {
|
const errorCallback = (openViduError: OpenViduError) => {
|
||||||
this.accessDenied = true;
|
this.accessDenied = true;
|
||||||
this.accessAllowed = false;
|
this.accessAllowed = false;
|
||||||
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`)
|
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
|
||||||
return reject(openViduError);
|
return reject(openViduError);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -378,21 +394,27 @@ export class Publisher extends StreamManager {
|
||||||
|
|
||||||
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
|
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
|
||||||
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
|
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
|
||||||
mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource));
|
mediaStream.addTrack(<MediaStreamTrack>this.properties.audioSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
|
if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
|
||||||
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
|
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
|
||||||
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource));
|
mediaStream.addTrack(<MediaStreamTrack>this.properties.videoSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
|
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
|
||||||
if (!!mediaStream.getAudioTracks()[0]) {
|
if (!!mediaStream.getAudioTracks()[0]) {
|
||||||
const enabled = (this.stream.audioActive !== undefined && this.stream.audioActive !== null) ? this.stream.audioActive : !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
|
const enabled =
|
||||||
|
this.stream.audioActive !== undefined && this.stream.audioActive !== null
|
||||||
|
? this.stream.audioActive
|
||||||
|
: !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
|
||||||
mediaStream.getAudioTracks()[0].enabled = enabled;
|
mediaStream.getAudioTracks()[0].enabled = enabled;
|
||||||
}
|
}
|
||||||
if (!!mediaStream.getVideoTracks()[0]) {
|
if (!!mediaStream.getVideoTracks()[0]) {
|
||||||
const enabled = (this.stream.videoActive !== undefined && this.stream.videoActive !== null) ? this.stream.videoActive : !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
|
const enabled =
|
||||||
|
this.stream.videoActive !== undefined && this.stream.videoActive !== null
|
||||||
|
? this.stream.videoActive
|
||||||
|
: !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
|
||||||
mediaStream.getVideoTracks()[0].enabled = enabled;
|
mediaStream.getVideoTracks()[0].enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,16 +433,16 @@ export class Publisher extends StreamManager {
|
||||||
// https://w3c.github.io/mst-content-hint/#video-content-hints
|
// https://w3c.github.io/mst-content-hint/#video-content-hints
|
||||||
switch (this.stream.typeOfVideo) {
|
switch (this.stream.typeOfVideo) {
|
||||||
case TypeOfVideo.SCREEN:
|
case TypeOfVideo.SCREEN:
|
||||||
track.contentHint = "detail";
|
track.contentHint = 'detail';
|
||||||
break;
|
break;
|
||||||
case TypeOfVideo.CUSTOM:
|
case TypeOfVideo.CUSTOM:
|
||||||
logger.warn("CUSTOM type video track was provided without Content Hint!");
|
logger.warn('CUSTOM type video track was provided without Content Hint!');
|
||||||
track.contentHint = "motion";
|
track.contentHint = 'motion';
|
||||||
break;
|
break;
|
||||||
case TypeOfVideo.CAMERA:
|
case TypeOfVideo.CAMERA:
|
||||||
case TypeOfVideo.IPCAM:
|
case TypeOfVideo.IPCAM:
|
||||||
default:
|
default:
|
||||||
track.contentHint = "motion";
|
track.contentHint = 'motion';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
logger.info(`Video track Content Hint set: '${track.contentHint}'`);
|
logger.info(`Video track Content Hint set: '${track.contentHint}'`);
|
||||||
|
@ -438,7 +460,7 @@ export class Publisher extends StreamManager {
|
||||||
|
|
||||||
if (this.stream.isSendVideo()) {
|
if (this.stream.isSendVideo()) {
|
||||||
// Has video track
|
// Has video track
|
||||||
this.getVideoDimensions().then(dimensions => {
|
this.getVideoDimensions().then((dimensions) => {
|
||||||
this.stream.videoDimensions = {
|
this.stream.videoDimensions = {
|
||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
height: dimensions.height
|
height: dimensions.height
|
||||||
|
@ -491,7 +513,6 @@ export class Publisher extends StreamManager {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
||||||
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
|
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
|
||||||
successCallback(mediaStream);
|
successCallback(mediaStream);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
||||||
mediaStream.getAudioTracks().forEach((track) => {
|
mediaStream.getAudioTracks().forEach((track) => {
|
||||||
|
@ -529,7 +550,6 @@ export class Publisher extends StreamManager {
|
||||||
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = error.toString();
|
errorMessage = error.toString();
|
||||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = error.toString();
|
errorMessage = error.toString();
|
||||||
|
@ -538,12 +558,13 @@ export class Publisher extends StreamManager {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'notallowederror':
|
case 'notallowederror':
|
||||||
errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED;
|
errorName = this.stream.isSendScreen()
|
||||||
|
? OpenViduErrorName.SCREEN_CAPTURE_DENIED
|
||||||
|
: OpenViduErrorName.DEVICE_ACCESS_DENIED;
|
||||||
errorMessage = error.toString();
|
errorMessage = error.toString();
|
||||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
break;
|
break;
|
||||||
case 'overconstrainederror':
|
case 'overconstrainederror':
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: false,
|
audio: false,
|
||||||
|
@ -554,20 +575,27 @@ export class Publisher extends StreamManager {
|
||||||
});
|
});
|
||||||
if (error.constraint.toLowerCase() === 'deviceid') {
|
if (error.constraint.toLowerCase() === 'deviceid') {
|
||||||
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact + "' not found";
|
errorMessage =
|
||||||
|
"Audio input device with deviceId '" +
|
||||||
|
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact +
|
||||||
|
"' not found";
|
||||||
} else {
|
} else {
|
||||||
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
||||||
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
errorMessage =
|
||||||
|
"Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
||||||
}
|
}
|
||||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.constraint.toLowerCase() === 'deviceid') {
|
if (error.constraint.toLowerCase() === 'deviceid') {
|
||||||
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = "Video input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
|
errorMessage =
|
||||||
|
"Video input device with deviceId '" +
|
||||||
|
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact +
|
||||||
|
"' not found";
|
||||||
} else {
|
} else {
|
||||||
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
||||||
errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
errorMessage =
|
||||||
|
"Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
||||||
}
|
}
|
||||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
}
|
}
|
||||||
|
@ -585,13 +613,15 @@ export class Publisher extends StreamManager {
|
||||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
|
const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
|
||||||
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack ||
|
if (
|
||||||
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
|
(!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
|
||||||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
|
(!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
|
||||||
|
(!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
|
||||||
|
) {
|
||||||
// No need to call getUserMedia at all. MediaStreamTracks already provided
|
// No need to call getUserMedia at all. MediaStreamTracks already provided
|
||||||
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
|
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
|
||||||
} else {
|
} else {
|
||||||
|
@ -603,7 +633,7 @@ export class Publisher extends StreamManager {
|
||||||
};
|
};
|
||||||
this.stream.setOutboundStreamOptions(outboundStreamOptions);
|
this.stream.setOutboundStreamOptions(outboundStreamOptions);
|
||||||
|
|
||||||
const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio);
|
const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
|
||||||
constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
|
constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
|
||||||
constraintsAux.video = constraints.video;
|
constraintsAux.video = constraints.video;
|
||||||
startTime = Date.now();
|
startTime = Date.now();
|
||||||
|
@ -664,9 +694,8 @@ export class Publisher extends StreamManager {
|
||||||
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
|
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
|
||||||
* use the HTMLVideoElement properties videoWidth and videoHeight
|
* use the HTMLVideoElement properties videoWidth and videoHeight
|
||||||
*/
|
*/
|
||||||
getVideoDimensions(): Promise<{ width: number, height: number }> {
|
getVideoDimensions(): Promise<{ width: number; height: number }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
|
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
|
||||||
const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari();
|
const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari();
|
||||||
|
|
||||||
|
@ -692,7 +721,7 @@ export class Publisher extends StreamManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve({ width, height });
|
return resolve({ width, height });
|
||||||
}
|
};
|
||||||
|
|
||||||
if (this.videoReference.readyState >= 1) {
|
if (this.videoReference.readyState >= 1) {
|
||||||
// The video already has metadata available
|
// The video already has metadata available
|
||||||
|
@ -739,7 +768,14 @@ export class Publisher extends StreamManager {
|
||||||
this.videoReference.muted = true;
|
this.videoReference.muted = true;
|
||||||
this.videoReference.autoplay = true;
|
this.videoReference.autoplay = true;
|
||||||
this.videoReference.controls = false;
|
this.videoReference.controls = false;
|
||||||
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) {
|
if (
|
||||||
|
platform.isSafariBrowser() ||
|
||||||
|
(platform.isIPhoneOrIPad() &&
|
||||||
|
(platform.isChromeMobileBrowser() ||
|
||||||
|
platform.isEdgeMobileBrowser() ||
|
||||||
|
platform.isOperaMobileBrowser() ||
|
||||||
|
platform.isFirefoxMobileBrowser()))
|
||||||
|
) {
|
||||||
this.videoReference.playsInline = true;
|
this.videoReference.playsInline = true;
|
||||||
}
|
}
|
||||||
this.stream.setMediaStream(mediaStream);
|
this.stream.setMediaStream(mediaStream);
|
||||||
|
@ -753,7 +789,9 @@ export class Publisher extends StreamManager {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
|
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
|
||||||
const mediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
const mediaStream: MediaStream = this.stream.displayMyRemote()
|
||||||
|
? this.stream.localMediaStreamWhenSubscribedToRemote!
|
||||||
|
: this.stream.getMediaStream();
|
||||||
let removedTrack: MediaStreamTrack;
|
let removedTrack: MediaStreamTrack;
|
||||||
if (track.kind === 'video') {
|
if (track.kind === 'video') {
|
||||||
removedTrack = mediaStream.getVideoTracks()[0];
|
removedTrack = mediaStream.getVideoTracks()[0];
|
||||||
|
@ -773,12 +811,12 @@ export class Publisher extends StreamManager {
|
||||||
};
|
};
|
||||||
if (track.kind === 'video' && updateLastConstraints) {
|
if (track.kind === 'video' && updateLastConstraints) {
|
||||||
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
|
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
|
||||||
this.openvidu.sendTrackChangedEvent(this,'trackReplaced', trackInfo.oldLabel, trackInfo.newLabel, 'videoActive');
|
this.openvidu.sendTrackChangedEvent(this, 'trackReplaced', trackInfo.oldLabel, trackInfo.newLabel, 'videoActive');
|
||||||
if(this.stream.isLocalStreamPublished) {
|
if (this.stream.isLocalStreamPublished) {
|
||||||
this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
|
this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
|
||||||
}
|
}
|
||||||
} else if(track.kind === 'audio' && updateLastConstraints) {
|
} else if (track.kind === 'audio' && updateLastConstraints) {
|
||||||
this.openvidu.sendTrackChangedEvent(this,'trackReplaced', trackInfo.oldLabel, trackInfo.newLabel, 'audioActive');
|
this.openvidu.sendTrackChangedEvent(this, 'trackReplaced', trackInfo.oldLabel, trackInfo.newLabel, 'audioActive');
|
||||||
}
|
}
|
||||||
if (track.kind === 'audio') {
|
if (track.kind === 'audio') {
|
||||||
this.stream.disableHarkSpeakingEvent(false);
|
this.stream.disableHarkSpeakingEvent(false);
|
||||||
|
@ -798,7 +836,7 @@ export class Publisher extends StreamManager {
|
||||||
|
|
||||||
private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
|
private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
|
||||||
clearTimeout(this.permissionDialogTimeout);
|
clearTimeout(this.permissionDialogTimeout);
|
||||||
if ((Date.now() - startTime) > waitTime) {
|
if (Date.now() - startTime > waitTime) {
|
||||||
// Permission dialog was shown and now is closed
|
// Permission dialog was shown and now is closed
|
||||||
this.emitEvent('accessDialogClosed', []);
|
this.emitEvent('accessDialogClosed', []);
|
||||||
}
|
}
|
||||||
|
@ -808,19 +846,18 @@ export class Publisher extends StreamManager {
|
||||||
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
|
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
|
||||||
let sender: RTCRtpSender | undefined;
|
let sender: RTCRtpSender | undefined;
|
||||||
if (track.kind === 'video') {
|
if (track.kind === 'video') {
|
||||||
sender = senders.find(s => !!s.track && s.track.kind === 'video');
|
sender = senders.find((s) => !!s.track && s.track.kind === 'video');
|
||||||
if (!sender) {
|
if (!sender) {
|
||||||
throw new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object');
|
throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
|
||||||
}
|
}
|
||||||
} else if (track.kind === 'audio') {
|
} else if (track.kind === 'audio') {
|
||||||
sender = senders.find(s => !!s.track && s.track.kind === 'audio');
|
sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
|
||||||
if (!sender) {
|
if (!sender) {
|
||||||
throw new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object');
|
throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown track kind ' + track.kind);
|
throw new Error('Unknown track kind ' + track.kind);
|
||||||
}
|
}
|
||||||
await (sender as RTCRtpSender).replaceTrack(track);
|
await (sender as RTCRtpSender).replaceTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -48,7 +48,6 @@ let platform: PlatformUtils;
|
||||||
* See available event listeners at [[StreamManagerEventMap]].
|
* See available event listeners at [[StreamManagerEventMap]].
|
||||||
*/
|
*/
|
||||||
export abstract class StreamManager extends EventDispatcher {
|
export abstract class StreamManager extends EventDispatcher {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Stream represented in the DOM by the Publisher/Subscriber
|
* The Stream represented in the DOM by the Publisher/Subscriber
|
||||||
*/
|
*/
|
||||||
|
@ -126,7 +125,14 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
id: '',
|
id: '',
|
||||||
canplayListenerAdded: false
|
canplayListenerAdded: false
|
||||||
};
|
};
|
||||||
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) {
|
if (
|
||||||
|
platform.isSafariBrowser() ||
|
||||||
|
(platform.isIPhoneOrIPad() &&
|
||||||
|
(platform.isChromeMobileBrowser() ||
|
||||||
|
platform.isEdgeMobileBrowser() ||
|
||||||
|
platform.isOperaMobileBrowser() ||
|
||||||
|
platform.isFirefoxMobileBrowser()))
|
||||||
|
) {
|
||||||
this.firstVideoElement.video.playsInline = true;
|
this.firstVideoElement.video.playsInline = true;
|
||||||
}
|
}
|
||||||
this.targetElement = targEl;
|
this.targetElement = targEl;
|
||||||
|
@ -144,7 +150,6 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* See [[EventDispatcher.on]]
|
* See [[EventDispatcher.on]]
|
||||||
*/
|
*/
|
||||||
on<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
|
on<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
|
||||||
|
|
||||||
super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
|
super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
|
||||||
|
|
||||||
if (type === 'videoElementCreated') {
|
if (type === 'videoElementCreated') {
|
||||||
|
@ -154,11 +159,14 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'streamPlaying') {
|
if (type === 'streamPlaying') {
|
||||||
if (this.videos[0] && this.videos[0].video &&
|
if (
|
||||||
|
this.videos[0] &&
|
||||||
|
this.videos[0].video &&
|
||||||
this.videos[0].video.currentTime > 0 &&
|
this.videos[0].video.currentTime > 0 &&
|
||||||
this.videos[0].video.paused === false &&
|
this.videos[0].video.paused === false &&
|
||||||
this.videos[0].video.ended === false &&
|
this.videos[0].video.ended === false &&
|
||||||
this.videos[0].video.readyState === 4) {
|
this.videos[0].video.readyState === 4
|
||||||
|
) {
|
||||||
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +188,6 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* See [[EventDispatcher.once]]
|
* See [[EventDispatcher.once]]
|
||||||
*/
|
*/
|
||||||
once<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
|
once<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
|
||||||
|
|
||||||
super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
|
super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
|
||||||
|
|
||||||
if (type === 'videoElementCreated') {
|
if (type === 'videoElementCreated') {
|
||||||
|
@ -189,11 +196,14 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'streamPlaying') {
|
if (type === 'streamPlaying') {
|
||||||
if (this.videos[0] && this.videos[0].video &&
|
if (
|
||||||
|
this.videos[0] &&
|
||||||
|
this.videos[0].video &&
|
||||||
this.videos[0].video.currentTime > 0 &&
|
this.videos[0].video.currentTime > 0 &&
|
||||||
this.videos[0].video.paused === false &&
|
this.videos[0].video.paused === false &&
|
||||||
this.videos[0].video.ended === false &&
|
this.videos[0].video.ended === false &&
|
||||||
this.videos[0].video.readyState === 4) {
|
this.videos[0].video.readyState === 4
|
||||||
|
) {
|
||||||
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,19 +225,20 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* See [[EventDispatcher.off]]
|
* See [[EventDispatcher.off]]
|
||||||
*/
|
*/
|
||||||
off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
|
off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
|
||||||
|
|
||||||
super.offAux(type, handler);
|
super.offAux(type, handler);
|
||||||
|
|
||||||
if (type === 'publisherStartSpeaking') {
|
if (type === 'publisherStartSpeaking') {
|
||||||
// Both StreamManager and Session can have "publisherStartSpeaking" event listeners
|
// Both StreamManager and Session can have "publisherStartSpeaking" event listeners
|
||||||
const remainingStartSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
|
const remainingStartSpeakingEventListeners =
|
||||||
|
this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
|
||||||
if (remainingStartSpeakingEventListeners === 0) {
|
if (remainingStartSpeakingEventListeners === 0) {
|
||||||
this.stream.disableHarkSpeakingEvent(false);
|
this.stream.disableHarkSpeakingEvent(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'publisherStopSpeaking') {
|
if (type === 'publisherStopSpeaking') {
|
||||||
// Both StreamManager and Session can have "publisherStopSpeaking" event listeners
|
// Both StreamManager and Session can have "publisherStopSpeaking" event listeners
|
||||||
const remainingStopSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
|
const remainingStopSpeakingEventListeners =
|
||||||
|
this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
|
||||||
if (remainingStopSpeakingEventListeners === 0) {
|
if (remainingStopSpeakingEventListeners === 0) {
|
||||||
this.stream.disableHarkStoppedSpeakingEvent(false);
|
this.stream.disableHarkStoppedSpeakingEvent(false);
|
||||||
}
|
}
|
||||||
|
@ -255,7 +266,6 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one.
|
* Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one.
|
||||||
*/
|
*/
|
||||||
addVideoElement(video: HTMLVideoElement): number {
|
addVideoElement(video: HTMLVideoElement): number {
|
||||||
|
|
||||||
this.initializeVideoProperties(video);
|
this.initializeVideoProperties(video);
|
||||||
|
|
||||||
if (!this.remote && this.stream.displayMyRemote()) {
|
if (!this.remote && this.stream.displayMyRemote()) {
|
||||||
|
@ -280,7 +290,7 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stream.session.streamManagers.forEach(streamManager => {
|
this.stream.session.streamManagers.forEach((streamManager) => {
|
||||||
streamManager.disassociateVideo(video);
|
streamManager.disassociateVideo(video);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -370,12 +380,22 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
|
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
|
||||||
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
|
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
|
||||||
*/
|
*/
|
||||||
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void {
|
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number; threshold?: number }): void {
|
||||||
const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
|
const currentHarkOptions = !!this.stream.harkOptions
|
||||||
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ?
|
? this.stream.harkOptions
|
||||||
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100);
|
: this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
|
||||||
const newThreshold = (typeof publisherSpeakingEventsOptions.threshold === 'number') ?
|
const newInterval =
|
||||||
publisherSpeakingEventsOptions.threshold : ((typeof currentHarkOptions.threshold === 'number') ? currentHarkOptions.threshold : -50);
|
typeof publisherSpeakingEventsOptions.interval === 'number'
|
||||||
|
? publisherSpeakingEventsOptions.interval
|
||||||
|
: typeof currentHarkOptions.interval === 'number'
|
||||||
|
? currentHarkOptions.interval
|
||||||
|
: 100;
|
||||||
|
const newThreshold =
|
||||||
|
typeof publisherSpeakingEventsOptions.threshold === 'number'
|
||||||
|
? publisherSpeakingEventsOptions.threshold
|
||||||
|
: typeof currentHarkOptions.threshold === 'number'
|
||||||
|
? currentHarkOptions.threshold
|
||||||
|
: -50;
|
||||||
this.stream.harkOptions = {
|
this.stream.harkOptions = {
|
||||||
interval: newInterval,
|
interval: newInterval,
|
||||||
threshold: newThreshold
|
threshold: newThreshold
|
||||||
|
@ -402,7 +422,14 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
video.autoplay = true;
|
video.autoplay = true;
|
||||||
video.controls = false;
|
video.controls = false;
|
||||||
|
|
||||||
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) {
|
if (
|
||||||
|
platform.isSafariBrowser() ||
|
||||||
|
(platform.isIPhoneOrIPad() &&
|
||||||
|
(platform.isChromeMobileBrowser() ||
|
||||||
|
platform.isEdgeMobileBrowser() ||
|
||||||
|
platform.isOperaMobileBrowser() ||
|
||||||
|
platform.isFirefoxMobileBrowser()))
|
||||||
|
) {
|
||||||
video.playsInline = true;
|
video.playsInline = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,7 +467,7 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.videos.forEach(streamManagerVideo => {
|
this.videos.forEach((streamManagerVideo) => {
|
||||||
// Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
|
// Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
|
||||||
if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
|
if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
|
||||||
streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
|
streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
|
||||||
|
@ -450,12 +477,14 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
// Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
|
// Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
|
||||||
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
|
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
|
||||||
streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
|
streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
|
||||||
this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')]);
|
this.ee.emitEvent('videoElementDestroyed', [
|
||||||
|
new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
// Remove srcObject from the video
|
// Remove srcObject from the video
|
||||||
this.removeSrcObject(streamManagerVideo);
|
this.removeSrcObject(streamManagerVideo);
|
||||||
// Remove from collection of videos every video managed by OpenVidu Browser
|
// Remove from collection of videos every video managed by OpenVidu Browser
|
||||||
this.videos.filter(v => !v.targetElement);
|
this.videos.filter((v) => !v.targetElement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,7 +509,7 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
addPlayEventToFirstVideo() {
|
addPlayEventToFirstVideo() {
|
||||||
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) {
|
if (!!this.videos[0] && !!this.videos[0].video && !this.videos[0].canplayListenerAdded) {
|
||||||
this.activateStreamPlayingEventExceptionTimeout();
|
this.activateStreamPlayingEventExceptionTimeout();
|
||||||
this.videos[0].video.addEventListener('canplay', this.canPlayListener);
|
this.videos[0].video.addEventListener('canplay', this.canPlayListener);
|
||||||
this.videos[0].canplayListenerAdded = true;
|
this.videos[0].canplayListenerAdded = true;
|
||||||
|
@ -491,7 +520,7 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
updateMediaStream(mediaStream: MediaStream) {
|
updateMediaStream(mediaStream: MediaStream) {
|
||||||
this.videos.forEach(streamManagerVideo => {
|
this.videos.forEach((streamManagerVideo) => {
|
||||||
streamManagerVideo.video.srcObject = mediaStream;
|
streamManagerVideo.video.srcObject = mediaStream;
|
||||||
if (platform.isIonicIos()) {
|
if (platform.isIonicIos()) {
|
||||||
// iOS Ionic. LIMITATION: must reinsert the video in the DOM for
|
// iOS Ionic. LIMITATION: must reinsert the video in the DOM for
|
||||||
|
@ -569,9 +598,18 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
|
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
|
||||||
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
|
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
|
||||||
this.streamPlayingEventExceptionTimeout = setTimeout(() => {
|
this.streamPlayingEventExceptionTimeout = setTimeout(() => {
|
||||||
const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms';
|
const msg =
|
||||||
|
'StreamManager of Stream ' +
|
||||||
|
this.stream.streamId +
|
||||||
|
' (' +
|
||||||
|
(this.remote ? 'Subscriber' : 'Publisher') +
|
||||||
|
') did not trigger "streamPlaying" event in ' +
|
||||||
|
msTimeout +
|
||||||
|
' ms';
|
||||||
logger.warn(msg);
|
logger.warn(msg);
|
||||||
this.stream.session.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)]);
|
this.stream.session.emitEvent('exception', [
|
||||||
|
new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)
|
||||||
|
]);
|
||||||
delete this.streamPlayingEventExceptionTimeout;
|
delete this.streamPlayingEventExceptionTimeout;
|
||||||
}, msTimeout);
|
}, msTimeout);
|
||||||
}
|
}
|
||||||
|
@ -580,5 +618,4 @@ export abstract class StreamManager extends EventDispatcher {
|
||||||
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
|
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
|
||||||
delete this.streamPlayingEventExceptionTimeout;
|
delete this.streamPlayingEventExceptionTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
* See available event listeners at [[StreamManagerEventMap]].
|
* See available event listeners at [[StreamManagerEventMap]].
|
||||||
*/
|
*/
|
||||||
export class Subscriber extends StreamManager {
|
export class Subscriber extends StreamManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -52,7 +51,10 @@ export class Subscriber extends StreamManager {
|
||||||
* @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it
|
* @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it
|
||||||
*/
|
*/
|
||||||
subscribeToAudio(value: boolean): Subscriber {
|
subscribeToAudio(value: boolean): Subscriber {
|
||||||
this.stream.getMediaStream().getAudioTracks().forEach((track) => {
|
this.stream
|
||||||
|
.getMediaStream()
|
||||||
|
.getAudioTracks()
|
||||||
|
.forEach((track) => {
|
||||||
track.enabled = value;
|
track.enabled = value;
|
||||||
});
|
});
|
||||||
this.stream.audioActive = value;
|
this.stream.audioActive = value;
|
||||||
|
@ -65,7 +67,10 @@ export class Subscriber extends StreamManager {
|
||||||
* @param value `true` to subscribe to the video stream, `false` to unsubscribe from it
|
* @param value `true` to subscribe to the video stream, `false` to unsubscribe from it
|
||||||
*/
|
*/
|
||||||
subscribeToVideo(value: boolean): Subscriber {
|
subscribeToVideo(value: boolean): Subscriber {
|
||||||
this.stream.getMediaStream().getVideoTracks().forEach((track) => {
|
this.stream
|
||||||
|
.getMediaStream()
|
||||||
|
.getVideoTracks()
|
||||||
|
.forEach((track) => {
|
||||||
track.enabled = value;
|
track.enabled = value;
|
||||||
});
|
});
|
||||||
this.stream.videoActive = value;
|
this.stream.videoActive = value;
|
||||||
|
@ -93,5 +98,4 @@ export class Subscriber extends StreamManager {
|
||||||
removedTrack.stop();
|
removedTrack.stop();
|
||||||
mediaStream.addTrack(track);
|
mediaStream.addTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,7 +19,6 @@
|
||||||
* Defines property [[OpenViduError.name]]
|
* Defines property [[OpenViduError.name]]
|
||||||
*/
|
*/
|
||||||
export enum OpenViduErrorName {
|
export enum OpenViduErrorName {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browser is not supported by OpenVidu.
|
* Browser is not supported by OpenVidu.
|
||||||
* Returned upon unsuccessful [[Session.connect]]
|
* Returned upon unsuccessful [[Session.connect]]
|
||||||
|
@ -38,7 +37,7 @@ export enum OpenViduErrorName {
|
||||||
* error occurred at the OS, browser or web page level, which prevented access to the device.
|
* error occurred at the OS, browser or web page level, which prevented access to the device.
|
||||||
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
|
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
|
||||||
*/
|
*/
|
||||||
DEVICE_ALREADY_IN_USE = "DEVICE_ALREADY_IN_USE",
|
DEVICE_ALREADY_IN_USE = 'DEVICE_ALREADY_IN_USE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user hasn't granted permissions to capture some desktop screen when the browser asked for them.
|
* The user hasn't granted permissions to capture some desktop screen when the browser asked for them.
|
||||||
|
@ -122,7 +121,6 @@ export enum OpenViduErrorName {
|
||||||
* Simple object to identify runtime errors on the client side
|
* Simple object to identify runtime errors on the client side
|
||||||
*/
|
*/
|
||||||
export class OpenViduError {
|
export class OpenViduError {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uniquely identifying name of the error
|
* Uniquely identifying name of the error
|
||||||
*/
|
*/
|
||||||
|
@ -140,5 +138,4 @@ export class OpenViduError {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,7 +19,6 @@
|
||||||
* How the video will be inserted in the DOM for Publishers and Subscribers. See [[PublisherProperties.insertMode]] and [[SubscriberProperties.insertMode]]
|
* How the video will be inserted in the DOM for Publishers and Subscribers. See [[PublisherProperties.insertMode]] and [[SubscriberProperties.insertMode]]
|
||||||
*/
|
*/
|
||||||
export enum VideoInsertMode {
|
export enum VideoInsertMode {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Video inserted after the target element (as next sibling)
|
* Video inserted after the target element (as next sibling)
|
||||||
*/
|
*/
|
||||||
|
@ -40,5 +39,4 @@ export enum VideoInsertMode {
|
||||||
* Video replaces target element
|
* Video replaces target element
|
||||||
*/
|
*/
|
||||||
REPLACE = 'REPLACE'
|
REPLACE = 'REPLACE'
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,14 +19,12 @@ import { Event } from './Event';
|
||||||
import { Connection } from '../../OpenVidu/Connection';
|
import { Connection } from '../../OpenVidu/Connection';
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered by:
|
* Triggered by:
|
||||||
* - [[connectionCreated]]
|
* - [[connectionCreated]]
|
||||||
* - [[connectionDestroyed]]
|
* - [[connectionDestroyed]]
|
||||||
*/
|
*/
|
||||||
export class ConnectionEvent extends Event {
|
export class ConnectionEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection object that was created or destroyed
|
* Connection object that was created or destroyed
|
||||||
*/
|
*/
|
||||||
|
@ -58,6 +56,5 @@ export class ConnectionEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -25,7 +25,6 @@ import { Event } from './Event';
|
||||||
* Triggered by [[connectionPropertyChanged]]
|
* Triggered by [[connectionPropertyChanged]]
|
||||||
*/
|
*/
|
||||||
export class ConnectionPropertyChangedEvent extends Event {
|
export class ConnectionPropertyChangedEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Connection whose property has changed
|
* The Connection whose property has changed
|
||||||
*/
|
*/
|
||||||
|
@ -61,6 +60,5 @@ export class ConnectionPropertyChangedEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
|
|
||||||
export abstract class Event {
|
export abstract class Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the event has a default behavior that may be prevented by calling [[Event.preventDefault]]
|
* Whether the event has a default behavior that may be prevented by calling [[Event.preventDefault]]
|
||||||
*/
|
*/
|
||||||
|
@ -73,7 +72,7 @@ export abstract class Event {
|
||||||
*/
|
*/
|
||||||
preventDefault() {
|
preventDefault() {
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
this.callDefaultBehavior = () => { };
|
this.callDefaultBehavior = () => {};
|
||||||
this.hasBeenPrevented = true;
|
this.hasBeenPrevented = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,5 +80,4 @@ export abstract class Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
abstract callDefaultBehavior();
|
abstract callDefaultBehavior();
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,4 +18,4 @@
|
||||||
/**
|
/**
|
||||||
* All OpenVidu Browser events inherit from this interface
|
* All OpenVidu Browser events inherit from this interface
|
||||||
*/
|
*/
|
||||||
export interface EventMap { }
|
export interface EventMap {}
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { StreamManagerEventMap } from './StreamManagerEventMap';
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface PublisherEventMap extends StreamManagerEventMap {
|
export interface PublisherEventMap extends StreamManagerEventMap {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event dispatched when the [[Publisher]] has been published to the session (see [[Session.publish]]).
|
* Event dispatched when the [[Publisher]] has been published to the session (see [[Session.publish]]).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -42,7 +42,6 @@ import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface SessionEventMap extends EventMap {
|
export interface SessionEventMap extends EventMap {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event dispatched when a new user has connected to the session.
|
* Event dispatched when a new user has connected to the session.
|
||||||
*
|
*
|
||||||
|
|
|
@ -36,7 +36,6 @@ import { VideoElementEvent } from '../VideoElementEvent';
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface StreamManagerEventMap extends EventMap {
|
export interface StreamManagerEventMap extends EventMap {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event dispatched when a new HTML video element has been inserted into DOM by OpenVidu Browser library. See
|
* Event dispatched when a new HTML video element has been inserted into DOM by OpenVidu Browser library. See
|
||||||
* [Manage video players](/en/stable/cheatsheet/manage-videos) section.
|
* [Manage video players](/en/stable/cheatsheet/manage-videos) section.
|
||||||
|
|
|
@ -20,12 +20,10 @@ import { Stream } from '../../OpenVidu/Stream';
|
||||||
import { Subscriber } from '../../OpenVidu/Subscriber';
|
import { Subscriber } from '../../OpenVidu/Subscriber';
|
||||||
import { Event } from './Event';
|
import { Event } from './Event';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines property [[ExceptionEvent.name]]
|
* Defines property [[ExceptionEvent.name]]
|
||||||
*/
|
*/
|
||||||
export enum ExceptionEventName {
|
export enum ExceptionEventName {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side.
|
* There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side.
|
||||||
*
|
*
|
||||||
|
@ -87,7 +85,6 @@ export enum ExceptionEventName {
|
||||||
* Triggered by [[SessionEventMap.exception]]
|
* Triggered by [[SessionEventMap.exception]]
|
||||||
*/
|
*/
|
||||||
export class ExceptionEvent extends Event {
|
export class ExceptionEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the exception
|
* Name of the exception
|
||||||
*/
|
*/
|
||||||
|
@ -126,6 +123,5 @@ export class ExceptionEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,12 +18,10 @@
|
||||||
import { Event } from './Event';
|
import { Event } from './Event';
|
||||||
import { Filter } from '../../OpenVidu/Filter';
|
import { Filter } from '../../OpenVidu/Filter';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines every event dispatched by audio/video stream filters. You can subscribe to filter events by calling [[Filter.addEventListener]]
|
* Defines every event dispatched by audio/video stream filters. You can subscribe to filter events by calling [[Filter.addEventListener]]
|
||||||
*/
|
*/
|
||||||
export class FilterEvent extends Event {
|
export class FilterEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data of the event
|
* Data of the event
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +39,5 @@ export class FilterEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,7 +23,6 @@ import { Connection } from '../../OpenVidu/Connection';
|
||||||
* Triggered by [[networkQualityLevelChanged]]
|
* Triggered by [[networkQualityLevelChanged]]
|
||||||
*/
|
*/
|
||||||
export class NetworkQualityLevelChangedEvent extends Event {
|
export class NetworkQualityLevelChangedEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New value of the network quality level
|
* New value of the network quality level
|
||||||
*/
|
*/
|
||||||
|
@ -37,7 +36,7 @@ export class NetworkQualityLevelChangedEvent extends Event {
|
||||||
/**
|
/**
|
||||||
* Connection for whom the network quality level changed
|
* Connection for whom the network quality level changed
|
||||||
*/
|
*/
|
||||||
connection: Connection
|
connection: Connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
@ -53,6 +52,5 @@ export class NetworkQualityLevelChangedEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,12 @@ import { Connection } from '../../OpenVidu/Connection';
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
import { StreamManager } from '../../OpenVidu/StreamManager';
|
import { StreamManager } from '../../OpenVidu/StreamManager';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered by:
|
* Triggered by:
|
||||||
* - `publisherStartSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStartSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStartSpeaking) objects)
|
* - `publisherStartSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStartSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStartSpeaking) objects)
|
||||||
* - `publisherStopSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStopSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStopSpeaking) objects)
|
* - `publisherStopSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStopSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStopSpeaking) objects)
|
||||||
*/
|
*/
|
||||||
export class PublisherSpeakingEvent extends Event {
|
export class PublisherSpeakingEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client that started or stopped speaking
|
* The client that started or stopped speaking
|
||||||
*/
|
*/
|
||||||
|
@ -52,6 +50,5 @@ export class PublisherSpeakingEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,14 +18,12 @@
|
||||||
import { Event } from './Event';
|
import { Event } from './Event';
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered by:
|
* Triggered by:
|
||||||
* - [[recordingStarted]]
|
* - [[recordingStarted]]
|
||||||
* - [[recordingStopped]]
|
* - [[recordingStopped]]
|
||||||
*/
|
*/
|
||||||
export class RecordingEvent extends Event {
|
export class RecordingEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recording ID generated in openvidu-server
|
* The recording ID generated in openvidu-server
|
||||||
*/
|
*/
|
||||||
|
@ -68,6 +66,5 @@ export class RecordingEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,12 +24,10 @@ import { OpenViduLogger } from '../Logger/OpenViduLogger';
|
||||||
*/
|
*/
|
||||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered by [[sessionDisconnected]]
|
* Triggered by [[sessionDisconnected]]
|
||||||
*/
|
*/
|
||||||
export class SessionDisconnectedEvent extends Event {
|
export class SessionDisconnectedEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - "disconnect": you have called `Session.disconnect()`
|
* - "disconnect": you have called `Session.disconnect()`
|
||||||
* - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()`
|
* - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()`
|
||||||
|
@ -56,13 +54,12 @@ export class SessionDisconnectedEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
callDefaultBehavior() {
|
callDefaultBehavior() {
|
||||||
|
|
||||||
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
|
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
|
||||||
|
|
||||||
const session = <Session>this.target;
|
const session = <Session>this.target;
|
||||||
|
|
||||||
// Dispose and delete all remote Connections
|
// Dispose and delete all remote Connections
|
||||||
session.remoteConnections.forEach(remoteConnection => {
|
session.remoteConnections.forEach((remoteConnection) => {
|
||||||
const connectionId = remoteConnection.connectionId;
|
const connectionId = remoteConnection.connectionId;
|
||||||
if (!!session.remoteConnections.get(connectionId)?.stream) {
|
if (!!session.remoteConnections.get(connectionId)?.stream) {
|
||||||
session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
|
session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
|
||||||
|
@ -79,5 +76,4 @@ export class SessionDisconnectedEvent extends Event {
|
||||||
session.remoteConnections.delete(connectionId);
|
session.remoteConnections.delete(connectionId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,12 +19,10 @@ import { Event } from './Event';
|
||||||
import { Connection } from '../../OpenVidu/Connection';
|
import { Connection } from '../../OpenVidu/Connection';
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered by [[SessionEventMap.signal]]
|
* Triggered by [[SessionEventMap.signal]]
|
||||||
*/
|
*/
|
||||||
export class SignalEvent extends Event {
|
export class SignalEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of signal. It is string `"signal"` for those signals sent with no [[SignalOptions.type]] property, and `"signal:type"` if was sent with a
|
* The type of signal. It is string `"signal"` for those signals sent with no [[SignalOptions.type]] property, and `"signal:type"` if was sent with a
|
||||||
* valid [[SignalOptions.type]] property.
|
* valid [[SignalOptions.type]] property.
|
||||||
|
@ -62,6 +60,5 @@ export class SignalEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
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)
|
* - `streamDestroyed` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamDestroyed) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamDestroyed) objects)
|
||||||
*/
|
*/
|
||||||
export class StreamEvent extends Event {
|
export class StreamEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream object that was created or destroyed
|
* Stream object that was created or destroyed
|
||||||
*/
|
*/
|
||||||
|
@ -68,7 +67,6 @@ export class StreamEvent extends Event {
|
||||||
*/
|
*/
|
||||||
callDefaultBehavior() {
|
callDefaultBehavior() {
|
||||||
if (this.type === 'streamDestroyed') {
|
if (this.type === 'streamDestroyed') {
|
||||||
|
|
||||||
if (this.target instanceof Session) {
|
if (this.target instanceof Session) {
|
||||||
// Remote Stream
|
// Remote Stream
|
||||||
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
|
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
|
||||||
|
@ -82,7 +80,7 @@ export class StreamEvent extends Event {
|
||||||
// Delete Publisher object from OpenVidu publishers array
|
// Delete Publisher object from OpenVidu publishers array
|
||||||
const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
|
const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
|
||||||
for (let i = 0; i < openviduPublishers.length; i++) {
|
for (let i = 0; i < openviduPublishers.length; i++) {
|
||||||
if (openviduPublishers[i] === (<Publisher>this.target)) {
|
if (openviduPublishers[i] === <Publisher>this.target) {
|
||||||
openviduPublishers.splice(i, 1);
|
openviduPublishers.splice(i, 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -109,8 +107,6 @@ export class StreamEvent extends Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,7 +24,6 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
|
||||||
* - [[streamAudioVolumeChange]]
|
* - [[streamAudioVolumeChange]]
|
||||||
*/
|
*/
|
||||||
export class StreamManagerEvent extends Event {
|
export class StreamManagerEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For `streamAudioVolumeChange` event:
|
* For `streamAudioVolumeChange` event:
|
||||||
* - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume).
|
* - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume).
|
||||||
|
@ -46,6 +45,5 @@ export class StreamManagerEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
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)
|
* Triggered by `streamPropertyChanged` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamPropertyChanged) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#streamPropertyChanged) objects)
|
||||||
*/
|
*/
|
||||||
export class StreamPropertyChangedEvent extends Event {
|
export class StreamPropertyChangedEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property [[Stream.connection]]
|
* The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property [[Stream.connection]]
|
||||||
*/
|
*/
|
||||||
|
@ -57,7 +56,14 @@ export class StreamPropertyChangedEvent extends Event {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
constructor(target: Session | StreamManager, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object, reason: string) {
|
constructor(
|
||||||
|
target: Session | StreamManager,
|
||||||
|
stream: Stream,
|
||||||
|
changedProperty: string,
|
||||||
|
newValue: Object,
|
||||||
|
oldValue: Object,
|
||||||
|
reason: string
|
||||||
|
) {
|
||||||
super(false, target, 'streamPropertyChanged');
|
super(false, target, 'streamPropertyChanged');
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.changedProperty = changedProperty;
|
this.changedProperty = changedProperty;
|
||||||
|
@ -70,6 +76,5 @@ export class StreamPropertyChangedEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,14 +18,12 @@
|
||||||
import { Event } from './Event';
|
import { Event } from './Event';
|
||||||
import { StreamManager } from '../../OpenVidu/StreamManager';
|
import { StreamManager } from '../../OpenVidu/StreamManager';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered by:
|
* Triggered by:
|
||||||
* - [[videoElementCreated]]
|
* - [[videoElementCreated]]
|
||||||
* - [[videoElementDestroyed]]
|
* - [[videoElementDestroyed]]
|
||||||
*/
|
*/
|
||||||
export class VideoElementEvent extends Event {
|
export class VideoElementEvent extends Event {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Video element that was created or destroyed
|
* Video element that was created or destroyed
|
||||||
*/
|
*/
|
||||||
|
@ -43,6 +41,5 @@ export class VideoElementEvent extends Event {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
callDefaultBehavior() { }
|
callDefaultBehavior() {}
|
||||||
|
|
||||||
}
|
}
|
|
@ -29,6 +29,6 @@ export interface InboundStreamOptions {
|
||||||
videoActive: boolean;
|
videoActive: boolean;
|
||||||
typeOfVideo: TypeOfVideo;
|
typeOfVideo: TypeOfVideo;
|
||||||
frameRate: number;
|
frameRate: number;
|
||||||
videoDimensions: { width: number, height: number };
|
videoDimensions: { width: number; height: number };
|
||||||
filter?: Filter;
|
filter?: Filter;
|
||||||
}
|
}
|
|
@ -19,7 +19,6 @@
|
||||||
* See [[Session.capabilities]]
|
* See [[Session.capabilities]]
|
||||||
*/
|
*/
|
||||||
export interface Capabilities {
|
export interface Capabilities {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `true` if the client can call [[Session.forceDisconnect]], `false` if not
|
* `true` if the client can call [[Session.forceDisconnect]], `false` if not
|
||||||
*/
|
*/
|
||||||
|
@ -39,5 +38,4 @@ export interface Capabilities {
|
||||||
* `true` if the client can call [[Session.subscribe]], `false` if not (true for every user for now)
|
* `true` if the client can call [[Session.subscribe]], `false` if not (true for every user for now)
|
||||||
*/
|
*/
|
||||||
subscribe: boolean;
|
subscribe: boolean;
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,7 +19,6 @@
|
||||||
* See [[OpenVidu.getDevices]]
|
* See [[OpenVidu.getDevices]]
|
||||||
*/
|
*/
|
||||||
export interface Device {
|
export interface Device {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `"videoinput"`, `"audioinput"`
|
* `"videoinput"`, `"audioinput"`
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
* See [[OpenVidu.setAdvancedConfiguration]]
|
* See [[OpenVidu.setAdvancedConfiguration]]
|
||||||
*/
|
*/
|
||||||
export interface OpenViduAdvancedConfiguration {
|
export interface OpenViduAdvancedConfiguration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of [RTCIceServer](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer) to be used by OpenVidu Browser. By default OpenVidu will generate the required credentials to use the COTURN server hosted along OpenVidu Server
|
* Array of [RTCIceServer](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer) to be used by OpenVidu Browser. By default OpenVidu will generate the required credentials to use the COTURN server hosted along OpenVidu Server
|
||||||
* You can also set this property to string 'freeice' to force the use of free STUN servers instead (got thanks to [freeice](https://github.com/DamonOehlman/freeice) library).
|
* You can also set this property to string 'freeice' to force the use of free STUN servers instead (got thanks to [freeice](https://github.com/DamonOehlman/freeice) library).
|
||||||
|
@ -70,5 +69,4 @@ export interface OpenViduAdvancedConfiguration {
|
||||||
* Default to `4000`.
|
* Default to `4000`.
|
||||||
*/
|
*/
|
||||||
noStreamPlayingEventExceptionTimeout?: number;
|
noStreamPlayingEventExceptionTimeout?: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import { VideoInsertMode } from '../../Enums/VideoInsertMode';
|
||||||
* See [[OpenVidu.initPublisher]]
|
* See [[OpenVidu.initPublisher]]
|
||||||
*/
|
*/
|
||||||
export interface PublisherProperties {
|
export interface PublisherProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which device should provide the audio source. Can be:
|
* Which device should provide the audio source. Can be:
|
||||||
* - Property `deviceId` of a [[Device]]
|
* - Property `deviceId` of a [[Device]]
|
||||||
|
@ -98,5 +97,4 @@ export interface PublisherProperties {
|
||||||
* Define a filter to apply in the Publisher's stream
|
* Define a filter to apply in the Publisher's stream
|
||||||
*/
|
*/
|
||||||
filter?: Filter;
|
filter?: Filter;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { Connection } from '../../../OpenVidu/Connection';
|
||||||
* See [[Session.signal]]
|
* See [[Session.signal]]
|
||||||
*/
|
*/
|
||||||
export interface SignalOptions {
|
export interface SignalOptions {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual message of the signal.
|
* The actual message of the signal.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,9 +17,7 @@
|
||||||
|
|
||||||
import { VideoInsertMode } from '../../Enums/VideoInsertMode';
|
import { VideoInsertMode } from '../../Enums/VideoInsertMode';
|
||||||
|
|
||||||
|
|
||||||
export interface StreamManagerVideo {
|
export interface StreamManagerVideo {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DOM video element displaying the StreamManager's stream
|
* DOM video element displaying the StreamManager's stream
|
||||||
*/
|
*/
|
||||||
|
@ -56,6 +54,4 @@ export interface StreamManagerVideo {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
canplayListenerAdded: boolean;
|
canplayListenerAdded: boolean;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,7 +21,6 @@ import { VideoInsertMode } from '../../Enums/VideoInsertMode';
|
||||||
* See [[Session.subscribe]]
|
* See [[Session.subscribe]]
|
||||||
*/
|
*/
|
||||||
export interface SubscriberProperties {
|
export interface SubscriberProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How the video element of the subscriber should be inserted in the DOM
|
* How the video element of the subscriber should be inserted in the DOM
|
||||||
* @default VideoInsertMode.APPEND
|
* @default VideoInsertMode.APPEND
|
||||||
|
@ -39,5 +38,4 @@ export interface SubscriberProperties {
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
subscribeToVideo?: boolean;
|
subscribeToVideo?: boolean;
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,61 +1,52 @@
|
||||||
function Mapper() {
|
function Mapper() {
|
||||||
var sources = {};
|
var sources = {};
|
||||||
|
|
||||||
|
|
||||||
this.forEach = function (callback) {
|
this.forEach = function (callback) {
|
||||||
for (var key in sources) {
|
for (var key in sources) {
|
||||||
var source = sources[key];
|
var source = sources[key];
|
||||||
|
|
||||||
for (var key2 in source)
|
for (var key2 in source) callback(source[key2]);
|
||||||
callback(source[key2]);
|
}
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.get = function (id, source) {
|
this.get = function (id, source) {
|
||||||
var ids = sources[source];
|
var ids = sources[source];
|
||||||
if (ids == undefined)
|
if (ids == undefined) return undefined;
|
||||||
return undefined;
|
|
||||||
|
|
||||||
return ids[id];
|
return ids[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
this.remove = function (id, source) {
|
this.remove = function (id, source) {
|
||||||
var ids = sources[source];
|
var ids = sources[source];
|
||||||
if (ids == undefined)
|
if (ids == undefined) return;
|
||||||
return;
|
|
||||||
|
|
||||||
delete ids[id];
|
delete ids[id];
|
||||||
|
|
||||||
// Check it's empty
|
// Check it's empty
|
||||||
for (var i in ids) {
|
for (var i in ids) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete sources[source];
|
delete sources[source];
|
||||||
};
|
};
|
||||||
|
|
||||||
this.set = function (value, id, source) {
|
this.set = function (value, id, source) {
|
||||||
if (value == undefined)
|
if (value == undefined) return this.remove(id, source);
|
||||||
return this.remove(id, source);
|
|
||||||
|
|
||||||
var ids = sources[source];
|
var ids = sources[source];
|
||||||
if (ids == undefined)
|
if (ids == undefined) sources[source] = ids = {};
|
||||||
sources[source] = ids = {};
|
|
||||||
|
|
||||||
ids[id] = value;
|
ids[id] = value;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
Mapper.prototype.pop = function (id, source) {
|
Mapper.prototype.pop = function (id, source) {
|
||||||
var value = this.get(id, source);
|
var value = this.get(id, source);
|
||||||
if (value == undefined)
|
if (value == undefined) return undefined;
|
||||||
return undefined;
|
|
||||||
|
|
||||||
this.remove(id, source);
|
this.remove(id, source);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = Mapper;
|
module.exports = Mapper;
|
|
@ -17,5 +17,4 @@
|
||||||
|
|
||||||
var JsonRpcClient = require('./jsonrpcclient');
|
var JsonRpcClient = require('./jsonrpcclient');
|
||||||
|
|
||||||
|
|
||||||
exports.JsonRpcClient = JsonRpcClient;
|
exports.JsonRpcClient = JsonRpcClient;
|
|
@ -19,9 +19,11 @@ var RpcBuilder = require('../');
|
||||||
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
|
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
|
||||||
var OpenViduLogger = require('../../../Logger/OpenViduLogger').OpenViduLogger;
|
var OpenViduLogger = require('../../../Logger/OpenViduLogger').OpenViduLogger;
|
||||||
|
|
||||||
Date.now = Date.now || function () {
|
Date.now =
|
||||||
return +new Date;
|
Date.now ||
|
||||||
};
|
function () {
|
||||||
|
return +new Date();
|
||||||
|
};
|
||||||
|
|
||||||
var PING_INTERVAL = 5000;
|
var PING_INTERVAL = 5000;
|
||||||
|
|
||||||
|
@ -51,7 +53,6 @@ var Logger = OpenViduLogger.getInstance();
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
function JsonRpcClient(configuration) {
|
function JsonRpcClient(configuration) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var wsConfig = configuration.ws;
|
var wsConfig = configuration.ws;
|
||||||
|
@ -71,13 +72,13 @@ function JsonRpcClient(configuration) {
|
||||||
var onerror = wsConfig.onerror;
|
var onerror = wsConfig.onerror;
|
||||||
|
|
||||||
configuration.rpc.pull = function (params, request) {
|
configuration.rpc.pull = function (params, request) {
|
||||||
request.reply(null, "push");
|
request.reply(null, 'push');
|
||||||
}
|
};
|
||||||
|
|
||||||
wsConfig.onreconnecting = function () {
|
wsConfig.onreconnecting = function () {
|
||||||
Logger.debug("--------- ONRECONNECTING -----------");
|
Logger.debug('--------- ONRECONNECTING -----------');
|
||||||
if (status === RECONNECTING) {
|
if (status === RECONNECTING) {
|
||||||
Logger.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it");
|
Logger.error('Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,12 +88,12 @@ function JsonRpcClient(configuration) {
|
||||||
if (onreconnecting) {
|
if (onreconnecting) {
|
||||||
onreconnecting();
|
onreconnecting();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
wsConfig.onreconnected = function () {
|
wsConfig.onreconnected = function () {
|
||||||
Logger.debug("--------- ONRECONNECTED -----------");
|
Logger.debug('--------- ONRECONNECTED -----------');
|
||||||
if (status === CONNECTED) {
|
if (status === CONNECTED) {
|
||||||
Logger.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it");
|
Logger.error('Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
status = CONNECTED;
|
status = CONNECTED;
|
||||||
|
@ -102,12 +103,12 @@ function JsonRpcClient(configuration) {
|
||||||
if (onreconnected) {
|
if (onreconnected) {
|
||||||
onreconnected();
|
onreconnected();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
wsConfig.onconnected = function () {
|
wsConfig.onconnected = function () {
|
||||||
Logger.debug("--------- ONCONNECTED -----------");
|
Logger.debug('--------- ONCONNECTED -----------');
|
||||||
if (status === CONNECTED) {
|
if (status === CONNECTED) {
|
||||||
Logger.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it");
|
Logger.error('Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
status = CONNECTED;
|
status = CONNECTED;
|
||||||
|
@ -118,10 +119,10 @@ function JsonRpcClient(configuration) {
|
||||||
if (onconnected) {
|
if (onconnected) {
|
||||||
onconnected();
|
onconnected();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
wsConfig.onerror = function (error) {
|
wsConfig.onerror = function (error) {
|
||||||
Logger.debug("--------- ONERROR -----------");
|
Logger.debug('--------- ONERROR -----------');
|
||||||
|
|
||||||
status = DISCONNECTED;
|
status = DISCONNECTED;
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ function JsonRpcClient(configuration) {
|
||||||
if (onerror) {
|
if (onerror) {
|
||||||
onerror(error);
|
onerror(error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
var ws = new WebSocketWithReconnection(wsConfig);
|
var ws = new WebSocketWithReconnection(wsConfig);
|
||||||
|
|
||||||
|
@ -141,16 +142,14 @@ function JsonRpcClient(configuration) {
|
||||||
ping_request_timeout: configuration.rpc.heartbeatRequestTimeout
|
ping_request_timeout: configuration.rpc.heartbeatRequestTimeout
|
||||||
};
|
};
|
||||||
|
|
||||||
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws,
|
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws, function (request) {
|
||||||
function (request) {
|
|
||||||
|
|
||||||
Logger.debug('Received request: ' + JSON.stringify(request));
|
Logger.debug('Received request: ' + JSON.stringify(request));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var func = configuration.rpc[request.method];
|
var func = configuration.rpc[request.method];
|
||||||
|
|
||||||
if (func === undefined) {
|
if (func === undefined) {
|
||||||
Logger.error("Method " + request.method + " not registered in client");
|
Logger.error('Method ' + request.method + ' not registered in client');
|
||||||
} else {
|
} else {
|
||||||
func(request.params, request);
|
func(request.params, request);
|
||||||
}
|
}
|
||||||
|
@ -161,17 +160,23 @@ function JsonRpcClient(configuration) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.send = function (method, params, callback) {
|
this.send = function (method, params, callback) {
|
||||||
|
|
||||||
var requestTime = Date.now();
|
var requestTime = Date.now();
|
||||||
|
|
||||||
rpc.encode(method, params, function (error, result) {
|
rpc.encode(method, params, function (error, result) {
|
||||||
if (error) {
|
if (error) {
|
||||||
try {
|
try {
|
||||||
Logger.error("ERROR:" + error.message + " in Request: method:" +
|
Logger.error(
|
||||||
method + " params:" + JSON.stringify(params) + " request:" +
|
'ERROR:' +
|
||||||
error.request);
|
error.message +
|
||||||
|
' in Request: method:' +
|
||||||
|
method +
|
||||||
|
' params:' +
|
||||||
|
JSON.stringify(params) +
|
||||||
|
' request:' +
|
||||||
|
error.request
|
||||||
|
);
|
||||||
if (error.data) {
|
if (error.data) {
|
||||||
Logger.error("ERROR DATA:" + JSON.stringify(error.data));
|
Logger.error('ERROR DATA:' + JSON.stringify(error.data));
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
error.requestTime = requestTime;
|
error.requestTime = requestTime;
|
||||||
|
@ -183,11 +188,10 @@ function JsonRpcClient(configuration) {
|
||||||
callback(error, result);
|
callback(error, result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function updateNotReconnectIfLessThan() {
|
function updateNotReconnectIfLessThan() {
|
||||||
Logger.debug("notReconnectIfNumLessThan = " + pingNextNum + ' (old=' +
|
Logger.debug('notReconnectIfNumLessThan = ' + pingNextNum + ' (old=' + notReconnectIfNumLessThan + ')');
|
||||||
notReconnectIfNumLessThan + ')');
|
|
||||||
notReconnectIfNumLessThan = pingNextNum;
|
notReconnectIfNumLessThan = pingNextNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,23 +205,25 @@ function JsonRpcClient(configuration) {
|
||||||
}
|
}
|
||||||
pingNextNum++;
|
pingNextNum++;
|
||||||
|
|
||||||
self.send('ping', params, (function (pingNum) {
|
self.send(
|
||||||
|
'ping',
|
||||||
|
params,
|
||||||
|
(function (pingNum) {
|
||||||
return function (error, result) {
|
return function (error, result) {
|
||||||
if (error) {
|
if (error) {
|
||||||
Logger.debug("Error in ping request #" + pingNum + " (" +
|
Logger.debug('Error in ping request #' + pingNum + ' (' + error.message + ')');
|
||||||
error.message + ")");
|
|
||||||
if (pingNum > notReconnectIfNumLessThan) {
|
if (pingNum > notReconnectIfNumLessThan) {
|
||||||
enabledPings = false;
|
enabledPings = false;
|
||||||
updateNotReconnectIfLessThan();
|
updateNotReconnectIfLessThan();
|
||||||
Logger.debug("Server did not respond to ping message #" +
|
Logger.debug('Server did not respond to ping message #' + pingNum + '. Reconnecting... ');
|
||||||
pingNum + ". Reconnecting... ");
|
|
||||||
ws.reconnectWs();
|
ws.reconnectWs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})(pingNextNum));
|
})(pingNextNum)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("Trying to send ping, but ping is not enabled");
|
Logger.debug('Trying to send ping, but ping is not enabled');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +233,7 @@ function JsonRpcClient(configuration) {
|
||||||
*/
|
*/
|
||||||
function usePing() {
|
function usePing() {
|
||||||
if (!pingPongStarted) {
|
if (!pingPongStarted) {
|
||||||
Logger.debug("Starting ping (if configured)")
|
Logger.debug('Starting ping (if configured)');
|
||||||
pingPongStarted = true;
|
pingPongStarted = true;
|
||||||
|
|
||||||
if (configuration.heartbeat != undefined) {
|
if (configuration.heartbeat != undefined) {
|
||||||
|
@ -246,30 +252,29 @@ function JsonRpcClient(configuration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.close = function (code, reason) {
|
this.close = function (code, reason) {
|
||||||
Logger.debug("Closing with code: " + code + " because: " + reason);
|
Logger.debug('Closing with code: ' + code + ' because: ' + reason);
|
||||||
if (pingInterval != undefined) {
|
if (pingInterval != undefined) {
|
||||||
Logger.debug("Clearing ping interval");
|
Logger.debug('Clearing ping interval');
|
||||||
clearInterval(pingInterval);
|
clearInterval(pingInterval);
|
||||||
}
|
}
|
||||||
pingPongStarted = false;
|
pingPongStarted = false;
|
||||||
enabledPings = false;
|
enabledPings = false;
|
||||||
ws.close(code, reason);
|
ws.close(code, reason);
|
||||||
}
|
};
|
||||||
|
|
||||||
this.reconnect = function () {
|
this.reconnect = function () {
|
||||||
ws.reconnectWs();
|
ws.reconnectWs();
|
||||||
}
|
};
|
||||||
|
|
||||||
this.resetPing = function () {
|
this.resetPing = function () {
|
||||||
enabledPings = true;
|
enabledPings = true;
|
||||||
pingNextNum = 0;
|
pingNextNum = 0;
|
||||||
usePing();
|
usePing();
|
||||||
}
|
};
|
||||||
|
|
||||||
this.getReadyState = function () {
|
this.getReadyState = function () {
|
||||||
return ws.getReadyState();
|
return ws.getReadyState();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = JsonRpcClient;
|
module.exports = JsonRpcClient;
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
'use strict';
|
||||||
|
|
||||||
var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
|
var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
|
||||||
var Logger = OpenViduLogger.getInstance();
|
var Logger = OpenViduLogger.getInstance();
|
||||||
|
@ -45,17 +45,14 @@ function WebSocketWithReconnection(config) {
|
||||||
var ws = new WebSocket(wsUri);
|
var ws = new WebSocket(wsUri);
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
Logger.debug("WebSocket connected to " + wsUri);
|
Logger.debug('WebSocket connected to ' + wsUri);
|
||||||
if (config.onconnected) {
|
if (config.onconnected) {
|
||||||
config.onconnected();
|
config.onconnected();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = error => {
|
ws.onerror = (error) => {
|
||||||
Logger.error(
|
Logger.error('Could not connect to ' + wsUri + ' (invoking onerror if defined)', error);
|
||||||
"Could not connect to " + wsUri + " (invoking onerror if defined)",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
if (config.onerror) {
|
if (config.onerror) {
|
||||||
config.onerror(error);
|
config.onerror(error);
|
||||||
}
|
}
|
||||||
|
@ -64,31 +61,27 @@ function WebSocketWithReconnection(config) {
|
||||||
var reconnectionOnClose = () => {
|
var reconnectionOnClose = () => {
|
||||||
if (ws.readyState === CLOSED) {
|
if (ws.readyState === CLOSED) {
|
||||||
if (closing) {
|
if (closing) {
|
||||||
Logger.debug("Connection closed by user");
|
Logger.debug('Connection closed by user');
|
||||||
} else {
|
} else {
|
||||||
if (config.ismasternodecrashed()) {
|
if (config.ismasternodecrashed()) {
|
||||||
Logger.error("Master Node has crashed. Stopping reconnection process");
|
Logger.error('Master Node has crashed. Stopping reconnection process');
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("Connection closed unexpectedly. Reconnecting...");
|
Logger.debug('Connection closed unexpectedly. Reconnecting...');
|
||||||
reconnect(MAX_RETRIES, 1);
|
reconnect(MAX_RETRIES, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("Close callback from previous websocket. Ignoring it");
|
Logger.debug('Close callback from previous websocket. Ignoring it');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = reconnectionOnClose;
|
ws.onclose = reconnectionOnClose;
|
||||||
|
|
||||||
function reconnect(maxRetries, numRetries) {
|
function reconnect(maxRetries, numRetries) {
|
||||||
Logger.debug(
|
Logger.debug('reconnect (attempt #' + numRetries + ', max=' + maxRetries + ')');
|
||||||
"reconnect (attempt #" + numRetries + ", max=" + maxRetries + ")"
|
|
||||||
);
|
|
||||||
if (numRetries === 1) {
|
if (numRetries === 1) {
|
||||||
if (reconnecting) {
|
if (reconnecting) {
|
||||||
Logger.warn(
|
Logger.warn('Trying to reconnect when already reconnecting... Ignoring this reconnection.');
|
||||||
"Trying to reconnect when already reconnecting... Ignoring this reconnection."
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
reconnecting = true;
|
reconnecting = true;
|
||||||
|
@ -101,24 +94,22 @@ function WebSocketWithReconnection(config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addReconnectionQueryParamsIfMissing(uriString) {
|
function addReconnectionQueryParamsIfMissing(uriString) {
|
||||||
var searchParams = new URLSearchParams((new URL(uriString)).search);
|
var searchParams = new URLSearchParams(new URL(uriString).search);
|
||||||
if (!searchParams.has("reconnect")) {
|
if (!searchParams.has('reconnect')) {
|
||||||
uriString = (Array.from(searchParams).length > 0) ? (uriString + '&reconnect=true') : (uriString + '?reconnect=true');
|
uriString = Array.from(searchParams).length > 0 ? uriString + '&reconnect=true' : uriString + '?reconnect=true';
|
||||||
}
|
}
|
||||||
return uriString;
|
return uriString;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reconnectAux(maxRetries, numRetries) {
|
function reconnectAux(maxRetries, numRetries) {
|
||||||
Logger.debug("Reconnection attempt #" + numRetries);
|
Logger.debug('Reconnection attempt #' + numRetries);
|
||||||
ws.close(4104, 'Connection closed for reconnection');
|
ws.close(4104, 'Connection closed for reconnection');
|
||||||
|
|
||||||
wsUri = addReconnectionQueryParamsIfMissing(wsUri);
|
wsUri = addReconnectionQueryParamsIfMissing(wsUri);
|
||||||
ws = new WebSocket(wsUri);
|
ws = new WebSocket(wsUri);
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
Logger.debug(
|
Logger.debug('Reconnected to ' + wsUri + ' after ' + numRetries + ' attempts...');
|
||||||
"Reconnected to " + wsUri + " after " + numRetries + " attempts..."
|
|
||||||
);
|
|
||||||
reconnecting = false;
|
reconnecting = false;
|
||||||
registerMessageHandler();
|
registerMessageHandler();
|
||||||
if (config.onreconnected()) {
|
if (config.onreconnected()) {
|
||||||
|
@ -127,8 +118,8 @@ function WebSocketWithReconnection(config) {
|
||||||
ws.onclose = reconnectionOnClose;
|
ws.onclose = reconnectionOnClose;
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = error => {
|
ws.onerror = (error) => {
|
||||||
Logger.warn("Reconnection error: ", error);
|
Logger.warn('Reconnection error: ', error);
|
||||||
if (numRetries === maxRetries) {
|
if (numRetries === maxRetries) {
|
||||||
if (config.ondisconnect) {
|
if (config.ondisconnect) {
|
||||||
config.ondisconnect();
|
config.ondisconnect();
|
||||||
|
@ -147,11 +138,11 @@ function WebSocketWithReconnection(config) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reconnectWs = () => {
|
this.reconnectWs = () => {
|
||||||
Logger.debug("reconnectWs");
|
Logger.debug('reconnectWs');
|
||||||
reconnect(MAX_RETRIES, 1);
|
reconnect(MAX_RETRIES, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.send = message => {
|
this.send = (message) => {
|
||||||
ws.send(message);
|
ws.send(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -164,7 +155,7 @@ function WebSocketWithReconnection(config) {
|
||||||
|
|
||||||
this.getReadyState = () => {
|
this.getReadyState = () => {
|
||||||
return ws.readyState;
|
return ws.readyState;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WebSocketWithReconnection;
|
module.exports = WebSocketWithReconnection;
|
|
@ -15,13 +15,12 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var defineProperty_IE8 = false;
|
||||||
var defineProperty_IE8 = false
|
|
||||||
if (Object.defineProperty) {
|
if (Object.defineProperty) {
|
||||||
try {
|
try {
|
||||||
Object.defineProperty({}, "x", {});
|
Object.defineProperty({}, 'x', {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
defineProperty_IE8 = true
|
defineProperty_IE8 = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,10 +37,7 @@ if (!Function.prototype.bind) {
|
||||||
fToBind = this,
|
fToBind = this,
|
||||||
fNOP = function () {},
|
fNOP = function () {},
|
||||||
fBound = function () {
|
fBound = function () {
|
||||||
return fToBind.apply(this instanceof fNOP && oThis ?
|
return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
|
||||||
this :
|
|
||||||
oThis,
|
|
||||||
aArgs.concat(Array.prototype.slice.call(arguments)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fNOP.prototype = this.prototype;
|
fNOP.prototype = this.prototype;
|
||||||
|
@ -51,7 +47,6 @@ if (!Function.prototype.bind) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
var inherits = require('inherits');
|
var inherits = require('inherits');
|
||||||
|
@ -59,10 +54,8 @@ var inherits = require('inherits');
|
||||||
var packers = require('./packers');
|
var packers = require('./packers');
|
||||||
var Mapper = require('./Mapper');
|
var Mapper = require('./Mapper');
|
||||||
|
|
||||||
|
|
||||||
var BASE_TIMEOUT = 5000;
|
var BASE_TIMEOUT = 5000;
|
||||||
|
|
||||||
|
|
||||||
function unifyResponseMethods(responseMethods) {
|
function unifyResponseMethods(responseMethods) {
|
||||||
if (!responseMethods) return {};
|
if (!responseMethods) return {};
|
||||||
|
|
||||||
|
@ -72,11 +65,11 @@ function unifyResponseMethods(responseMethods) {
|
||||||
if (typeof value == 'string')
|
if (typeof value == 'string')
|
||||||
responseMethods[key] = {
|
responseMethods[key] = {
|
||||||
response: value
|
response: value
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return responseMethods;
|
return responseMethods;
|
||||||
};
|
}
|
||||||
|
|
||||||
function unifyTransport(transport) {
|
function unifyTransport(transport) {
|
||||||
if (!transport) return;
|
if (!transport) return;
|
||||||
|
@ -88,8 +81,7 @@ function unifyTransport(transport) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// WebSocket & DataChannel
|
// WebSocket & DataChannel
|
||||||
if (transport.send instanceof Function)
|
if (transport.send instanceof Function) return transport;
|
||||||
return transport;
|
|
||||||
|
|
||||||
// Message API (Inter-window & WebWorker)
|
// Message API (Inter-window & WebWorker)
|
||||||
if (transport.postMessage instanceof Function) {
|
if (transport.postMessage instanceof Function) {
|
||||||
|
@ -107,9 +99,8 @@ function unifyTransport(transport) {
|
||||||
if (transport.onmessage !== undefined) return;
|
if (transport.onmessage !== undefined) return;
|
||||||
if (transport.pause instanceof Function) return;
|
if (transport.pause instanceof Function) return;
|
||||||
|
|
||||||
throw new SyntaxError("Transport is not a function nor a valid object");
|
throw new SyntaxError('Transport is not a function nor a valid object');
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a RPC notification
|
* Representation of a RPC notification
|
||||||
|
@ -123,8 +114,8 @@ function unifyTransport(transport) {
|
||||||
*/
|
*/
|
||||||
function RpcNotification(method, params) {
|
function RpcNotification(method, params) {
|
||||||
if (defineProperty_IE8) {
|
if (defineProperty_IE8) {
|
||||||
this.method = method
|
this.method = method;
|
||||||
this.params = params
|
this.params = params;
|
||||||
} else {
|
} else {
|
||||||
Object.defineProperty(this, 'method', {
|
Object.defineProperty(this, 'method', {
|
||||||
value: method,
|
value: method,
|
||||||
|
@ -135,8 +126,7 @@ function RpcNotification(method, params) {
|
||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
|
@ -154,56 +144,45 @@ function RpcNotification(method, params) {
|
||||||
function RpcBuilder(packer, options, transport, onRequest) {
|
function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!packer)
|
if (!packer) throw new SyntaxError('Packer is not defined');
|
||||||
throw new SyntaxError('Packer is not defined');
|
|
||||||
|
|
||||||
if (!packer.pack || !packer.unpack)
|
if (!packer.pack || !packer.unpack) throw new SyntaxError('Packer is invalid');
|
||||||
throw new SyntaxError('Packer is invalid');
|
|
||||||
|
|
||||||
var responseMethods = unifyResponseMethods(packer.responseMethods);
|
var responseMethods = unifyResponseMethods(packer.responseMethods);
|
||||||
|
|
||||||
|
|
||||||
if (options instanceof Function) {
|
if (options instanceof Function) {
|
||||||
if (transport != undefined)
|
if (transport != undefined) throw new SyntaxError("There can't be parameters after onRequest");
|
||||||
throw new SyntaxError("There can't be parameters after onRequest");
|
|
||||||
|
|
||||||
onRequest = options;
|
onRequest = options;
|
||||||
transport = undefined;
|
transport = undefined;
|
||||||
options = undefined;
|
options = undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (options && options.send instanceof Function) {
|
if (options && options.send instanceof Function) {
|
||||||
if (transport && !(transport instanceof Function))
|
if (transport && !(transport instanceof Function)) throw new SyntaxError('Only a function can be after transport');
|
||||||
throw new SyntaxError("Only a function can be after transport");
|
|
||||||
|
|
||||||
onRequest = transport;
|
onRequest = transport;
|
||||||
transport = options;
|
transport = options;
|
||||||
options = undefined;
|
options = undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (transport instanceof Function) {
|
if (transport instanceof Function) {
|
||||||
if (onRequest != undefined)
|
if (onRequest != undefined) throw new SyntaxError("There can't be parameters after onRequest");
|
||||||
throw new SyntaxError("There can't be parameters after onRequest");
|
|
||||||
|
|
||||||
onRequest = transport;
|
onRequest = transport;
|
||||||
transport = undefined;
|
transport = undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (transport && transport.send instanceof Function)
|
if (transport && transport.send instanceof Function)
|
||||||
if (onRequest && !(onRequest instanceof Function))
|
if (onRequest && !(onRequest instanceof Function)) throw new SyntaxError('Only a function can be after transport');
|
||||||
throw new SyntaxError("Only a function can be after transport");
|
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
|
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
if (onRequest)
|
if (onRequest) this.on('request', onRequest);
|
||||||
this.on('request', onRequest);
|
|
||||||
|
|
||||||
|
if (defineProperty_IE8) this.peerID = options.peerID;
|
||||||
if (defineProperty_IE8)
|
|
||||||
this.peerID = options.peerID
|
|
||||||
else
|
else
|
||||||
Object.defineProperty(this, 'peerID', {
|
Object.defineProperty(this, 'peerID', {
|
||||||
value: options.peerID
|
value: options.peerID
|
||||||
|
@ -211,55 +190,46 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
|
|
||||||
var max_retries = options.max_retries || 0;
|
var max_retries = options.max_retries || 0;
|
||||||
|
|
||||||
|
|
||||||
function transportMessage(event) {
|
function transportMessage(event) {
|
||||||
self.decode(event.data || event);
|
self.decode(event.data || event);
|
||||||
};
|
}
|
||||||
|
|
||||||
this.getTransport = function () {
|
this.getTransport = function () {
|
||||||
return transport;
|
return transport;
|
||||||
}
|
};
|
||||||
this.setTransport = function (value) {
|
this.setTransport = function (value) {
|
||||||
// Remove listener from old transport
|
// Remove listener from old transport
|
||||||
if (transport) {
|
if (transport) {
|
||||||
// W3C transports
|
// W3C transports
|
||||||
if (transport.removeEventListener)
|
if (transport.removeEventListener) transport.removeEventListener('message', transportMessage);
|
||||||
transport.removeEventListener('message', transportMessage);
|
|
||||||
|
|
||||||
// Node.js Streams API
|
// Node.js Streams API
|
||||||
else if (transport.removeListener)
|
else if (transport.removeListener) transport.removeListener('data', transportMessage);
|
||||||
transport.removeListener('data', transportMessage);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Set listener on new transport
|
// Set listener on new transport
|
||||||
if (value) {
|
if (value) {
|
||||||
// W3C transports
|
// W3C transports
|
||||||
if (value.addEventListener)
|
if (value.addEventListener) value.addEventListener('message', transportMessage);
|
||||||
value.addEventListener('message', transportMessage);
|
|
||||||
|
|
||||||
// Node.js Streams API
|
// Node.js Streams API
|
||||||
else if (value.addListener)
|
else if (value.addListener) value.addListener('data', transportMessage);
|
||||||
value.addListener('data', transportMessage);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
transport = unifyTransport(value);
|
transport = unifyTransport(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!defineProperty_IE8)
|
if (!defineProperty_IE8)
|
||||||
Object.defineProperty(this, 'transport', {
|
Object.defineProperty(this, 'transport', {
|
||||||
get: this.getTransport.bind(this),
|
get: this.getTransport.bind(this),
|
||||||
set: this.setTransport.bind(this)
|
set: this.setTransport.bind(this)
|
||||||
})
|
});
|
||||||
|
|
||||||
this.setTransport(transport);
|
this.setTransport(transport);
|
||||||
|
|
||||||
|
|
||||||
var request_timeout = options.request_timeout || BASE_TIMEOUT;
|
var request_timeout = options.request_timeout || BASE_TIMEOUT;
|
||||||
var ping_request_timeout = options.ping_request_timeout || request_timeout;
|
var ping_request_timeout = options.ping_request_timeout || request_timeout;
|
||||||
var response_timeout = options.response_timeout || BASE_TIMEOUT;
|
var response_timeout = options.response_timeout || BASE_TIMEOUT;
|
||||||
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
|
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
|
||||||
|
|
||||||
|
|
||||||
var requestID = 0;
|
var requestID = 0;
|
||||||
|
|
||||||
var requests = new Mapper();
|
var requests = new Mapper();
|
||||||
|
@ -268,7 +238,6 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
|
|
||||||
var message2Key = {};
|
var message2Key = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the response to prevent to process duplicate request later
|
* Store the response to prevent to process duplicate request later
|
||||||
*/
|
*/
|
||||||
|
@ -278,12 +247,11 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
/** Timeout to auto-clean old responses */
|
/** Timeout to auto-clean old responses */
|
||||||
timeout: setTimeout(function () {
|
timeout: setTimeout(function () {
|
||||||
responses.remove(id, dest);
|
responses.remove(id, dest);
|
||||||
},
|
}, response_timeout)
|
||||||
response_timeout)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.set(response, id, dest);
|
responses.set(response, id, dest);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the response to ignore duplicated messages later
|
* Store the response to ignore duplicated messages later
|
||||||
|
@ -291,12 +259,10 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
function storeProcessedResponse(ack, from) {
|
function storeProcessedResponse(ack, from) {
|
||||||
var timeout = setTimeout(function () {
|
var timeout = setTimeout(function () {
|
||||||
processedResponses.remove(ack, from);
|
processedResponses.remove(ack, from);
|
||||||
},
|
}, duplicates_timeout);
|
||||||
duplicates_timeout);
|
|
||||||
|
|
||||||
processedResponses.set(timeout, ack, from);
|
processedResponses.set(timeout, ack, from);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a RPC request
|
* Representation of a RPC request
|
||||||
|
@ -316,16 +282,16 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
|
|
||||||
this.getTransport = function () {
|
this.getTransport = function () {
|
||||||
return transport;
|
return transport;
|
||||||
}
|
};
|
||||||
this.setTransport = function (value) {
|
this.setTransport = function (value) {
|
||||||
transport = unifyTransport(value);
|
transport = unifyTransport(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!defineProperty_IE8)
|
if (!defineProperty_IE8)
|
||||||
Object.defineProperty(this, 'transport', {
|
Object.defineProperty(this, 'transport', {
|
||||||
get: this.getTransport.bind(this),
|
get: this.getTransport.bind(this),
|
||||||
set: this.setTransport.bind(this)
|
set: this.setTransport.bind(this)
|
||||||
})
|
});
|
||||||
|
|
||||||
var response = responses.get(id, from);
|
var response = responses.get(id, from);
|
||||||
|
|
||||||
|
@ -333,8 +299,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
* @constant {Boolean} duplicated
|
* @constant {Boolean} duplicated
|
||||||
*/
|
*/
|
||||||
if (!(transport || self.getTransport())) {
|
if (!(transport || self.getTransport())) {
|
||||||
if (defineProperty_IE8)
|
if (defineProperty_IE8) this.duplicated = Boolean(response);
|
||||||
this.duplicated = Boolean(response)
|
|
||||||
else
|
else
|
||||||
Object.defineProperty(this, 'duplicated', {
|
Object.defineProperty(this, 'duplicated', {
|
||||||
value: Boolean(response)
|
value: Boolean(response)
|
||||||
|
@ -343,7 +308,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
|
|
||||||
var responseMethod = responseMethods[method];
|
var responseMethod = responseMethods[method];
|
||||||
|
|
||||||
this.pack = packer.pack.bind(packer, this, id)
|
this.pack = packer.pack.bind(packer, this, id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a response to this request
|
* Generate a response to this request
|
||||||
|
@ -355,45 +320,37 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
*/
|
*/
|
||||||
this.reply = function (error, result, transport) {
|
this.reply = function (error, result, transport) {
|
||||||
// Fix optional parameters
|
// Fix optional parameters
|
||||||
if (error instanceof Function || error && error.send instanceof Function) {
|
if (error instanceof Function || (error && error.send instanceof Function)) {
|
||||||
if (result != undefined)
|
if (result != undefined) throw new SyntaxError("There can't be parameters after callback");
|
||||||
throw new SyntaxError("There can't be parameters after callback");
|
|
||||||
|
|
||||||
transport = error;
|
transport = error;
|
||||||
result = null;
|
result = null;
|
||||||
error = undefined;
|
error = undefined;
|
||||||
} else if (result instanceof Function ||
|
} else if (result instanceof Function || (result && result.send instanceof Function)) {
|
||||||
result && result.send instanceof Function) {
|
if (transport != undefined) throw new SyntaxError("There can't be parameters after callback");
|
||||||
if (transport != undefined)
|
|
||||||
throw new SyntaxError("There can't be parameters after callback");
|
|
||||||
|
|
||||||
transport = result;
|
transport = result;
|
||||||
result = null;
|
result = null;
|
||||||
};
|
}
|
||||||
|
|
||||||
transport = unifyTransport(transport);
|
transport = unifyTransport(transport);
|
||||||
|
|
||||||
// Duplicated request, remove old response timeout
|
// Duplicated request, remove old response timeout
|
||||||
if (response)
|
if (response) clearTimeout(response.timeout);
|
||||||
clearTimeout(response.timeout);
|
|
||||||
|
|
||||||
if (from != undefined) {
|
if (from != undefined) {
|
||||||
if (error)
|
if (error) error.dest = from;
|
||||||
error.dest = from;
|
|
||||||
|
|
||||||
if (result)
|
if (result) result.dest = from;
|
||||||
result.dest = from;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var message;
|
var message;
|
||||||
|
|
||||||
// New request or overriden one, create new response with provided data
|
// New request or overriden one, create new response with provided data
|
||||||
if (error || result != undefined) {
|
if (error || result != undefined) {
|
||||||
if (self.peerID != undefined) {
|
if (self.peerID != undefined) {
|
||||||
if (error)
|
if (error) error.from = self.peerID;
|
||||||
error.from = self.peerID;
|
else result.from = self.peerID;
|
||||||
else
|
|
||||||
result.from = self.peerID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol indicates that responses has own request methods
|
// Protocol indicates that responses has own request methods
|
||||||
|
@ -402,11 +359,8 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
message = {
|
message = {
|
||||||
error: error
|
error: error
|
||||||
};
|
};
|
||||||
|
|
||||||
else {
|
else {
|
||||||
var method = error ?
|
var method = error ? responseMethod.error : responseMethod.response;
|
||||||
responseMethod.error :
|
|
||||||
responseMethod.response;
|
|
||||||
|
|
||||||
message = {
|
message = {
|
||||||
method: method,
|
method: method,
|
||||||
|
@ -423,14 +377,15 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicate & not-overriden request, re-send old response
|
// Duplicate & not-overriden request, re-send old response
|
||||||
else if (response)
|
else if (response) message = response.message;
|
||||||
message = response.message;
|
|
||||||
|
|
||||||
// New empty reply, response null value
|
// New empty reply, response null value
|
||||||
else
|
else
|
||||||
message = packer.pack({
|
message = packer.pack(
|
||||||
|
{
|
||||||
result: null
|
result: null
|
||||||
}, id);
|
},
|
||||||
|
id
|
||||||
|
);
|
||||||
|
|
||||||
// Store the response to prevent to process a duplicated request later
|
// Store the response to prevent to process a duplicated request later
|
||||||
storeResponse(message, id, from);
|
storeResponse(message, id, from);
|
||||||
|
@ -438,15 +393,13 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
// Return the stored response so it can be directly send back
|
// Return the stored response so it can be directly send back
|
||||||
transport = transport || this.getTransport() || self.getTransport();
|
transport = transport || this.getTransport() || self.getTransport();
|
||||||
|
|
||||||
if (transport)
|
if (transport) return transport.send(message);
|
||||||
return transport.send(message);
|
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
inherits(RpcRequest, RpcNotification);
|
inherits(RpcRequest, RpcNotification);
|
||||||
|
|
||||||
|
|
||||||
function cancel(message) {
|
function cancel(message) {
|
||||||
var key = message2Key[message];
|
var key = message2Key[message];
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
@ -460,7 +413,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
|
|
||||||
// Start duplicated responses timeout
|
// Start duplicated responses timeout
|
||||||
storeProcessedResponse(key.id, key.dest);
|
storeProcessedResponse(key.id, key.dest);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow to cancel a request and don't wait for a response
|
* Allow to cancel a request and don't wait for a response
|
||||||
|
@ -470,16 +423,13 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
this.cancel = function (message) {
|
this.cancel = function (message) {
|
||||||
if (message) return cancel(message);
|
if (message) return cancel(message);
|
||||||
|
|
||||||
for (var message in message2Key)
|
for (var message in message2Key) cancel(message);
|
||||||
cancel(message);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.close = function () {
|
this.close = function () {
|
||||||
// Prevent to receive new messages
|
// Prevent to receive new messages
|
||||||
var transport = this.getTransport();
|
var transport = this.getTransport();
|
||||||
if (transport && transport.close)
|
if (transport && transport.close) transport.close(4003, 'Cancel request');
|
||||||
transport.close(4003, "Cancel request");
|
|
||||||
|
|
||||||
// Request & processed responses
|
// Request & processed responses
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
@ -492,7 +442,6 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates and encode a JsonRPC 2.0 message
|
* Generates and encode a JsonRPC 2.0 message
|
||||||
*
|
*
|
||||||
|
@ -508,39 +457,36 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
this.encode = function (method, params, dest, transport, callback) {
|
this.encode = function (method, params, dest, transport, callback) {
|
||||||
// Fix optional parameters
|
// Fix optional parameters
|
||||||
if (params instanceof Function) {
|
if (params instanceof Function) {
|
||||||
if (dest != undefined)
|
if (dest != undefined) throw new SyntaxError("There can't be parameters after callback");
|
||||||
throw new SyntaxError("There can't be parameters after callback");
|
|
||||||
|
|
||||||
callback = params;
|
callback = params;
|
||||||
transport = undefined;
|
transport = undefined;
|
||||||
dest = undefined;
|
dest = undefined;
|
||||||
params = undefined;
|
params = undefined;
|
||||||
} else if (dest instanceof Function) {
|
} else if (dest instanceof Function) {
|
||||||
if (transport != undefined)
|
if (transport != undefined) throw new SyntaxError("There can't be parameters after callback");
|
||||||
throw new SyntaxError("There can't be parameters after callback");
|
|
||||||
|
|
||||||
callback = dest;
|
callback = dest;
|
||||||
transport = undefined;
|
transport = undefined;
|
||||||
dest = undefined;
|
dest = undefined;
|
||||||
} else if (transport instanceof Function) {
|
} else if (transport instanceof Function) {
|
||||||
if (callback != undefined)
|
if (callback != undefined) throw new SyntaxError("There can't be parameters after callback");
|
||||||
throw new SyntaxError("There can't be parameters after callback");
|
|
||||||
|
|
||||||
callback = transport;
|
callback = transport;
|
||||||
transport = undefined;
|
transport = undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (self.peerID != undefined) {
|
if (self.peerID != undefined) {
|
||||||
params = params || {};
|
params = params || {};
|
||||||
|
|
||||||
params.from = self.peerID;
|
params.from = self.peerID;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (dest != undefined) {
|
if (dest != undefined) {
|
||||||
params = params || {};
|
params = params || {};
|
||||||
|
|
||||||
params.dest = dest;
|
params.dest = dest;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Encode message
|
// Encode message
|
||||||
var message = {
|
var message = {
|
||||||
|
@ -558,7 +504,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
self.cancel(message);
|
self.cancel(message);
|
||||||
|
|
||||||
callback(error, result);
|
callback(error, result);
|
||||||
};
|
}
|
||||||
|
|
||||||
var request = {
|
var request = {
|
||||||
message: message,
|
message: message,
|
||||||
|
@ -569,7 +515,7 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
var encode_transport = unifyTransport(transport);
|
var encode_transport = unifyTransport(transport);
|
||||||
|
|
||||||
function sendRequest(transport) {
|
function sendRequest(transport) {
|
||||||
var rt = (method === 'ping' ? ping_request_timeout : request_timeout);
|
var rt = method === 'ping' ? ping_request_timeout : request_timeout;
|
||||||
request.timeout = setTimeout(timeout, rt * Math.pow(2, retried++));
|
request.timeout = setTimeout(timeout, rt * Math.pow(2, retried++));
|
||||||
message2Key[message] = {
|
message2Key[message] = {
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -578,11 +524,10 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
requests.set(request, id, dest);
|
requests.set(request, id, dest);
|
||||||
|
|
||||||
transport = transport || encode_transport || self.getTransport();
|
transport = transport || encode_transport || self.getTransport();
|
||||||
if (transport)
|
if (transport) return transport.send(message);
|
||||||
return transport.send(message);
|
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
};
|
}
|
||||||
|
|
||||||
function retry(transport) {
|
function retry(transport) {
|
||||||
transport = unifyTransport(transport);
|
transport = unifyTransport(transport);
|
||||||
|
@ -593,29 +538,27 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
return sendRequest(transport);
|
return sendRequest(transport);
|
||||||
};
|
}
|
||||||
|
|
||||||
function timeout() {
|
function timeout() {
|
||||||
if (retried < max_retries)
|
if (retried < max_retries) return retry(transport);
|
||||||
return retry(transport);
|
|
||||||
|
|
||||||
var error = new Error('Request has timed out');
|
var error = new Error('Request has timed out');
|
||||||
error.request = message;
|
error.request = message;
|
||||||
|
|
||||||
error.retry = retry;
|
error.retry = retry;
|
||||||
|
|
||||||
dispatchCallback(error)
|
dispatchCallback(error);
|
||||||
};
|
}
|
||||||
|
|
||||||
return sendRequest(transport);
|
return sendRequest(transport);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Return the packed message
|
// Return the packed message
|
||||||
message = packer.pack(message);
|
message = packer.pack(message);
|
||||||
|
|
||||||
transport = transport || this.getTransport();
|
transport = transport || this.getTransport();
|
||||||
if (transport)
|
if (transport) return transport.send(message);
|
||||||
return transport.send(message);
|
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
|
@ -632,15 +575,14 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
* @throws {TypeError} - Message is not defined
|
* @throws {TypeError} - Message is not defined
|
||||||
*/
|
*/
|
||||||
this.decode = function (message, transport) {
|
this.decode = function (message, transport) {
|
||||||
if (!message)
|
if (!message) throw new TypeError('Message is not defined');
|
||||||
throw new TypeError("Message is not defined");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
message = packer.unpack(message);
|
message = packer.unpack(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore invalid messages
|
// Ignore invalid messages
|
||||||
return console.debug(e, message);
|
return console.debug(e, message);
|
||||||
};
|
}
|
||||||
|
|
||||||
var id = message.id;
|
var id = message.id;
|
||||||
var ack = message.ack;
|
var ack = message.ack;
|
||||||
|
@ -659,37 +601,34 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
|
|
||||||
if (self.emit('request', notification)) return;
|
if (self.emit('request', notification)) return;
|
||||||
return notification;
|
return notification;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
function processRequest() {
|
function processRequest() {
|
||||||
// If we have a transport and it's a duplicated request, reply inmediatly
|
// If we have a transport and it's a duplicated request, reply inmediatly
|
||||||
transport = unifyTransport(transport) || self.getTransport();
|
transport = unifyTransport(transport) || self.getTransport();
|
||||||
if (transport) {
|
if (transport) {
|
||||||
var response = responses.get(id, from);
|
var response = responses.get(id, from);
|
||||||
if (response)
|
if (response) return transport.send(response.message);
|
||||||
return transport.send(response.message);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var idAck = (id != undefined) ? id : ack;
|
var idAck = id != undefined ? id : ack;
|
||||||
var request = new RpcRequest(method, params, idAck, from, transport);
|
var request = new RpcRequest(method, params, idAck, from, transport);
|
||||||
|
|
||||||
if (self.emit('request', request)) return;
|
if (self.emit('request', request)) return;
|
||||||
return request;
|
return request;
|
||||||
};
|
}
|
||||||
|
|
||||||
function processResponse(request, error, result) {
|
function processResponse(request, error, result) {
|
||||||
request.callback(error, result);
|
request.callback(error, result);
|
||||||
};
|
}
|
||||||
|
|
||||||
function duplicatedResponse(timeout) {
|
function duplicatedResponse(timeout) {
|
||||||
console.warn("Response already processed", message);
|
console.warn('Response already processed', message);
|
||||||
|
|
||||||
// Update duplicated responses timeout
|
// Update duplicated responses timeout
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
storeProcessedResponse(ack, from);
|
storeProcessedResponse(ack, from);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
// Request, or response with own method
|
// Request, or response with own method
|
||||||
if (method) {
|
if (method) {
|
||||||
|
@ -699,23 +638,20 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
if (request) {
|
if (request) {
|
||||||
var responseMethods = request.responseMethods;
|
var responseMethods = request.responseMethods;
|
||||||
|
|
||||||
if (method == responseMethods.error)
|
if (method == responseMethods.error) return processResponse(request, params);
|
||||||
return processResponse(request, params);
|
|
||||||
|
|
||||||
if (method == responseMethods.response)
|
if (method == responseMethods.response) return processResponse(request, null, params);
|
||||||
return processResponse(request, null, params);
|
|
||||||
|
|
||||||
return processRequest();
|
return processRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var processed = processedResponses.get(ack, from);
|
var processed = processedResponses.get(ack, from);
|
||||||
if (processed)
|
if (processed) return duplicatedResponse(processed);
|
||||||
return duplicatedResponse(processed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request
|
// Request
|
||||||
return processRequest();
|
return processRequest();
|
||||||
};
|
}
|
||||||
|
|
||||||
var error = message.error;
|
var error = message.error;
|
||||||
var result = message.result;
|
var result = message.result;
|
||||||
|
@ -728,22 +664,19 @@ function RpcBuilder(packer, options, transport, onRequest) {
|
||||||
var request = requests.get(ack, from);
|
var request = requests.get(ack, from);
|
||||||
if (!request) {
|
if (!request) {
|
||||||
var processed = processedResponses.get(ack, from);
|
var processed = processedResponses.get(ack, from);
|
||||||
if (processed)
|
if (processed) return duplicatedResponse(processed);
|
||||||
return duplicatedResponse(processed);
|
|
||||||
|
|
||||||
return console.warn("No callback was defined for this message", message);
|
return console.warn('No callback was defined for this message', message);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Process response
|
// Process response
|
||||||
processResponse(request, error, result);
|
processResponse(request, error, result);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
inherits(RpcBuilder, EventEmitter);
|
inherits(RpcBuilder, EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
RpcBuilder.RpcNotification = RpcNotification;
|
RpcBuilder.RpcNotification = RpcNotification;
|
||||||
|
|
||||||
|
|
||||||
module.exports = RpcBuilder;
|
module.exports = RpcBuilder;
|
||||||
|
|
||||||
var clients = require('./clients');
|
var clients = require('./clients');
|
||||||
|
|
|
@ -12,38 +12,33 @@
|
||||||
*/
|
*/
|
||||||
function pack(message, id) {
|
function pack(message, id) {
|
||||||
var result = {
|
var result = {
|
||||||
jsonrpc: "2.0"
|
jsonrpc: '2.0'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Request
|
// Request
|
||||||
if (message.method) {
|
if (message.method) {
|
||||||
result.method = message.method;
|
result.method = message.method;
|
||||||
|
|
||||||
if (message.params)
|
if (message.params) result.params = message.params;
|
||||||
result.params = message.params;
|
|
||||||
|
|
||||||
// Request is a notification
|
// Request is a notification
|
||||||
if (id != undefined)
|
if (id != undefined) result.id = id;
|
||||||
result.id = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response
|
// Response
|
||||||
else if (id != undefined) {
|
else if (id != undefined) {
|
||||||
if (message.error) {
|
if (message.error) {
|
||||||
if (message.result !== undefined)
|
if (message.result !== undefined) throw new TypeError('Both result and error are defined');
|
||||||
throw new TypeError("Both result and error are defined");
|
|
||||||
|
|
||||||
result.error = message.error;
|
result.error = message.error;
|
||||||
} else if (message.result !== undefined)
|
} else if (message.result !== undefined) result.result = message.result;
|
||||||
result.result = message.result;
|
else throw new TypeError('No result or error is defined');
|
||||||
else
|
|
||||||
throw new TypeError("No result or error is defined");
|
|
||||||
|
|
||||||
result.id = id;
|
result.id = id;
|
||||||
};
|
}
|
||||||
|
|
||||||
return JSON.stringify(result);
|
return JSON.stringify(result);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unpack a JsonRPC 2.0 message
|
* Unpack a JsonRPC 2.0 message
|
||||||
|
@ -64,23 +59,19 @@ function unpack(message) {
|
||||||
// Check if it's a valid message
|
// Check if it's a valid message
|
||||||
|
|
||||||
var version = result.jsonrpc;
|
var version = result.jsonrpc;
|
||||||
if (version !== '2.0')
|
if (version !== '2.0') throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
|
||||||
throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
|
|
||||||
|
|
||||||
// Response
|
// Response
|
||||||
if (result.method == undefined) {
|
if (result.method == undefined) {
|
||||||
if (result.id == undefined)
|
if (result.id == undefined) throw new TypeError('Invalid message: ' + message);
|
||||||
throw new TypeError("Invalid message: " + message);
|
|
||||||
|
|
||||||
var result_defined = result.result !== undefined;
|
var result_defined = result.result !== undefined;
|
||||||
var error_defined = result.error !== undefined;
|
var error_defined = result.error !== undefined;
|
||||||
|
|
||||||
// Check only result or error is defined, not both or none
|
// Check only result or error is defined, not both or none
|
||||||
if (result_defined && error_defined)
|
if (result_defined && error_defined) throw new TypeError('Both result and error are defined: ' + message);
|
||||||
throw new TypeError("Both result and error are defined: " + message);
|
|
||||||
|
|
||||||
if (!result_defined && !error_defined)
|
if (!result_defined && !error_defined) throw new TypeError('No result or error is defined: ' + message);
|
||||||
throw new TypeError("No result or error is defined: " + message);
|
|
||||||
|
|
||||||
result.ack = result.id;
|
result.ack = result.id;
|
||||||
delete result.id;
|
delete result.id;
|
||||||
|
@ -88,8 +79,7 @@ function unpack(message) {
|
||||||
|
|
||||||
// Return unpacked message
|
// Return unpacked message
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
exports.pack = pack;
|
exports.pack = pack;
|
||||||
exports.unpack = unpack;
|
exports.unpack = unpack;
|
|
@ -1,10 +1,10 @@
|
||||||
function pack(message) {
|
function pack(message) {
|
||||||
throw new TypeError("Not yet implemented");
|
throw new TypeError('Not yet implemented');
|
||||||
};
|
}
|
||||||
|
|
||||||
function unpack(message) {
|
function unpack(message) {
|
||||||
throw new TypeError("Not yet implemented");
|
throw new TypeError('Not yet implemented');
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.pack = pack;
|
exports.pack = pack;
|
||||||
exports.unpack = unpack;
|
exports.unpack = unpack;
|
|
@ -1,6 +1,5 @@
|
||||||
var JsonRPC = require('./JsonRPC');
|
var JsonRPC = require('./JsonRPC');
|
||||||
var XmlRPC = require('./XmlRPC');
|
var XmlRPC = require('./XmlRPC');
|
||||||
|
|
||||||
|
|
||||||
exports.JsonRPC = JsonRPC;
|
exports.JsonRPC = JsonRPC;
|
||||||
exports.XmlRPC = XmlRPC;
|
exports.XmlRPC = XmlRPC;
|
|
@ -1,42 +1,41 @@
|
||||||
type ConsoleFunction = (...data: any) => void;
|
type ConsoleFunction = (...data: any) => void;
|
||||||
export class ConsoleLogger {
|
export class ConsoleLogger {
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
logger: Console;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
logger: Console
|
log: ConsoleFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
log: ConsoleFunction
|
info: ConsoleFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
info: ConsoleFunction
|
debug: ConsoleFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
debug: ConsoleFunction
|
warn: ConsoleFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
warn: ConsoleFunction
|
error: ConsoleFunction;
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
error: ConsoleFunction
|
|
||||||
|
|
||||||
constructor(console: Console) {
|
constructor(console: Console) {
|
||||||
this.logger = console;
|
this.logger = console;
|
||||||
this.log = console.log,
|
(this.log = console.log),
|
||||||
this.info = console.info,
|
(this.info = console.info),
|
||||||
this.debug = console.debug,
|
(this.debug = console.debug),
|
||||||
this.warn = console.warn,
|
(this.warn = console.warn),
|
||||||
this.error = console.error
|
(this.error = console.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { JL } from 'jsnlog'
|
import { JL } from 'jsnlog';
|
||||||
import { OpenVidu } from "../../OpenVidu/OpenVidu";
|
import { OpenVidu } from '../../OpenVidu/OpenVidu';
|
||||||
import { ConsoleLogger } from './ConsoleLogger';
|
import { ConsoleLogger } from './ConsoleLogger';
|
||||||
import { OpenViduLoggerConfiguration } from "./OpenViduLoggerConfiguration";
|
import { OpenViduLoggerConfiguration } from './OpenViduLoggerConfiguration';
|
||||||
|
|
||||||
export class OpenViduLogger {
|
export class OpenViduLogger {
|
||||||
|
|
||||||
private static instance: OpenViduLogger;
|
private static instance: OpenViduLogger;
|
||||||
|
|
||||||
private JSNLOG_URL: string = "/openvidu/elk/openvidu-browser-logs";
|
private JSNLOG_URL: string = '/openvidu/elk/openvidu-browser-logs';
|
||||||
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100;
|
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100;
|
||||||
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
|
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
|
||||||
private MAX_LENGTH_STRING_JSON: number = 1000;
|
private MAX_LENGTH_STRING_JSON: number = 1000;
|
||||||
|
@ -29,14 +28,16 @@ export class OpenViduLogger {
|
||||||
static configureJSNLog(openVidu: OpenVidu, token: string) {
|
static configureJSNLog(openVidu: OpenVidu, token: string) {
|
||||||
try {
|
try {
|
||||||
// If dev mode or...
|
// If dev mode or...
|
||||||
if ((globalThis['LOG_JSNLOG_RESULTS']) ||
|
if (
|
||||||
|
globalThis['LOG_JSNLOG_RESULTS'] ||
|
||||||
// If instance is created and it is OpenVidu Pro
|
// If instance is created and it is OpenVidu Pro
|
||||||
(this.instance && openVidu.isAtLeastPro
|
(this.instance &&
|
||||||
|
openVidu.isAtLeastPro &&
|
||||||
// If logs are enabled
|
// If logs are enabled
|
||||||
&& this.instance.isOpenViduBrowserLogsDebugActive(openVidu)
|
this.instance.isOpenViduBrowserLogsDebugActive(openVidu) &&
|
||||||
// Only reconfigure it if session or finalUserId has changed
|
// Only reconfigure it if session or finalUserId has changed
|
||||||
&& this.instance.canConfigureJSNLog(openVidu, this.instance))) {
|
this.instance.canConfigureJSNLog(openVidu, this.instance))
|
||||||
|
) {
|
||||||
// Check if app logs can be sent
|
// Check if app logs can be sent
|
||||||
// and replace console.log function to send
|
// and replace console.log function to send
|
||||||
// logs of the application
|
// logs of the application
|
||||||
|
@ -46,7 +47,7 @@ export class OpenViduLogger {
|
||||||
|
|
||||||
// isJSNLogSetup will not be true until completed setup
|
// isJSNLogSetup will not be true until completed setup
|
||||||
this.instance.isJSNLogSetup = false;
|
this.instance.isJSNLogSetup = false;
|
||||||
this.instance.info("Configuring JSNLogs.");
|
this.instance.info('Configuring JSNLogs.');
|
||||||
|
|
||||||
const finalUserId = openVidu.finalUserId;
|
const finalUserId = openVidu.finalUserId;
|
||||||
const sessionId = openVidu.session.sessionId;
|
const sessionId = openVidu.session.sessionId;
|
||||||
|
@ -57,22 +58,22 @@ export class OpenViduLogger {
|
||||||
const parentReadyStateFunction = xhr.onreadystatechange;
|
const parentReadyStateFunction = xhr.onreadystatechange;
|
||||||
xhr.onreadystatechange = () => {
|
xhr.onreadystatechange = () => {
|
||||||
if (this.isInvalidResponse(xhr)) {
|
if (this.isInvalidResponse(xhr)) {
|
||||||
Object.defineProperty(xhr, "readyState", { value: 4 });
|
Object.defineProperty(xhr, 'readyState', { value: 4 });
|
||||||
Object.defineProperty(xhr, "status", { value: 200 });
|
Object.defineProperty(xhr, 'status', { value: 200 });
|
||||||
// Disable JSNLog too to not send periodically errors
|
// Disable JSNLog too to not send periodically errors
|
||||||
this.instance.disableLogger();
|
this.instance.disableLogger();
|
||||||
}
|
}
|
||||||
parentReadyStateFunction();
|
parentReadyStateFunction();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Headers to identify and authenticate logs
|
// Headers to identify and authenticate logs
|
||||||
xhr.setRequestHeader('Authorization', "Basic " + btoa(`${finalUserId}%/%${sessionId}` + ":" + token));
|
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(`${finalUserId}%/%${sessionId}` + ':' + token));
|
||||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||||
// Additional headers for OpenVidu
|
// Additional headers for OpenVidu
|
||||||
xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
|
xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
|
||||||
xhr.setRequestHeader('OV-Session-Id', sessionId);
|
xhr.setRequestHeader('OV-Session-Id', sessionId);
|
||||||
xhr.setRequestHeader('OV-Token', token);
|
xhr.setRequestHeader('OV-Token', token);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Creation of the appender.
|
// Creation of the appender.
|
||||||
this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
|
this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
|
||||||
|
@ -88,7 +89,7 @@ export class OpenViduLogger {
|
||||||
const getCircularReplacer = () => {
|
const getCircularReplacer = () => {
|
||||||
const seen = new WeakSet();
|
const seen = new WeakSet();
|
||||||
return (key, value) => {
|
return (key, value) => {
|
||||||
if (typeof value === "object" && value != null) {
|
if (typeof value === 'object' && value != null) {
|
||||||
if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) {
|
if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -123,11 +124,11 @@ export class OpenViduLogger {
|
||||||
|
|
||||||
this.instance.isJSNLogSetup = true;
|
this.instance.isJSNLogSetup = true;
|
||||||
this.instance.loggingSessionId = sessionId;
|
this.instance.loggingSessionId = sessionId;
|
||||||
this.instance.info("JSNLog configured.");
|
this.instance.info('JSNLog configured.');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Print error
|
// Print error
|
||||||
console.error("Error configuring JSNLog: ");
|
console.error('Error configuring JSNLog: ');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
// Restore defaults values just in case any exception happen-
|
// Restore defaults values just in case any exception happen-
|
||||||
this.instance.disableLogger();
|
this.instance.disableLogger();
|
||||||
|
@ -149,17 +150,19 @@ export class OpenViduLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean {
|
private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean {
|
||||||
return openVidu.session.sessionId != logger.loggingSessionId
|
return openVidu.session.sessionId != logger.loggingSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) {
|
private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) {
|
||||||
return openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
|
return (
|
||||||
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app;
|
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
|
||||||
|
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return console functions with jsnlog integration
|
// Return console functions with jsnlog integration
|
||||||
private getConsoleWithJSNLog() {
|
private getConsoleWithJSNLog() {
|
||||||
return function (openViduLogger: OpenViduLogger) {
|
return (function (openViduLogger: OpenViduLogger) {
|
||||||
return {
|
return {
|
||||||
log: function (...args) {
|
log: function (...args) {
|
||||||
openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
||||||
|
@ -189,7 +192,7 @@ export class OpenViduLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}(this);
|
})(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceWindowConsole() {
|
private replaceWindowConsole() {
|
||||||
|
@ -279,5 +282,4 @@ export class OpenViduLogger {
|
||||||
enableProdMode() {
|
enableProdMode() {
|
||||||
this.isProdMode = true;
|
this.isProdMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,11 @@ globalThis.getScreenId = function (firefoxString, callback, custom_parameter) {
|
||||||
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
|
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
|
||||||
callback('permission-denied');
|
callback('permission-denied');
|
||||||
} else {
|
} else {
|
||||||
callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId, event.data.canRequestAudioTrack));
|
callback(
|
||||||
|
null,
|
||||||
|
event.data.chromeMediaSourceId,
|
||||||
|
getScreenConstraints(null, event.data.chromeMediaSourceId, event.data.canRequestAudioTrack)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this event listener is no more needed
|
// this event listener is no more needed
|
||||||
|
@ -71,8 +75,7 @@ globalThis.getScreenId = function (firefoxString, callback, custom_parameter) {
|
||||||
|
|
||||||
if (!custom_parameter) {
|
if (!custom_parameter) {
|
||||||
setTimeout(postGetSourceIdMessage, 100);
|
setTimeout(postGetSourceIdMessage, 100);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
postGetSourceIdMessage(custom_parameter);
|
postGetSourceIdMessage(custom_parameter);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -95,7 +98,7 @@ function getScreenConstraints(error, sourceId, canRequestAudioTrack) {
|
||||||
if (!!canRequestAudioTrack) {
|
if (!!canRequestAudioTrack) {
|
||||||
screen_constraints.audio = {
|
screen_constraints.audio = {
|
||||||
mandatory: {
|
mandatory: {
|
||||||
chromeMediaSource: error ? 'screen' : 'desktop',
|
chromeMediaSource: error ? 'screen' : 'desktop'
|
||||||
// echoCancellation: true
|
// echoCancellation: true
|
||||||
},
|
},
|
||||||
optional: []
|
optional: []
|
||||||
|
@ -129,19 +132,26 @@ function postGetSourceIdMessage(custom_parameter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!custom_parameter) {
|
if (!custom_parameter) {
|
||||||
iframe.contentWindow.postMessage({
|
iframe.contentWindow.postMessage(
|
||||||
|
{
|
||||||
captureSourceId: true
|
captureSourceId: true
|
||||||
}, '*');
|
},
|
||||||
}
|
'*'
|
||||||
else if (!!custom_parameter.forEach) {
|
);
|
||||||
iframe.contentWindow.postMessage({
|
} else if (!!custom_parameter.forEach) {
|
||||||
|
iframe.contentWindow.postMessage(
|
||||||
|
{
|
||||||
captureCustomSourceId: custom_parameter
|
captureCustomSourceId: custom_parameter
|
||||||
}, '*');
|
},
|
||||||
}
|
'*'
|
||||||
else {
|
);
|
||||||
iframe.contentWindow.postMessage({
|
} else {
|
||||||
|
iframe.contentWindow.postMessage(
|
||||||
|
{
|
||||||
captureSourceIdWithAudio: true
|
captureSourceIdWithAudio: true
|
||||||
}, '*');
|
},
|
||||||
|
'*'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,9 +222,12 @@ function postGetChromeExtensionStatusMessage() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe.contentWindow.postMessage({
|
iframe.contentWindow.postMessage(
|
||||||
|
{
|
||||||
getChromeExtensionStatus: true
|
getChromeExtensionStatus: true
|
||||||
}, '*');
|
},
|
||||||
|
'*'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getScreenId = globalThis.getScreenId;
|
exports.getScreenId = globalThis.getScreenId;
|
||||||
|
|
|
@ -3,7 +3,7 @@ var chromeMediaSource = 'screen';
|
||||||
var sourceId;
|
var sourceId;
|
||||||
var screenCallback;
|
var screenCallback;
|
||||||
|
|
||||||
if(typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined'){
|
if (typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined') {
|
||||||
var isFirefox = typeof window.InstallTrigger !== 'undefined';
|
var isFirefox = typeof window.InstallTrigger !== 'undefined';
|
||||||
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
|
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
|
||||||
var isChrome = !!window.chrome && !isOpera;
|
var isChrome = !!window.chrome && !isOpera;
|
||||||
|
@ -20,10 +20,8 @@ if(typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof n
|
||||||
function onMessageCallback(data) {
|
function onMessageCallback(data) {
|
||||||
// "cancel" button is clicked
|
// "cancel" button is clicked
|
||||||
if (data == 'PermissionDeniedError') {
|
if (data == 'PermissionDeniedError') {
|
||||||
if (screenCallback)
|
if (screenCallback) return screenCallback('PermissionDeniedError');
|
||||||
return screenCallback('PermissionDeniedError');
|
else throw new Error('PermissionDeniedError');
|
||||||
else
|
|
||||||
throw new Error('PermissionDeniedError');
|
|
||||||
}
|
}
|
||||||
// extension notified his presence
|
// extension notified his presence
|
||||||
if (data == 'rtcmulticonnection-extension-loaded') {
|
if (data == 'rtcmulticonnection-extension-loaded') {
|
||||||
|
@ -31,7 +29,7 @@ function onMessageCallback(data) {
|
||||||
}
|
}
|
||||||
// extension shared temp sourceId
|
// extension shared temp sourceId
|
||||||
if (data.sourceId && screenCallback) {
|
if (data.sourceId && screenCallback) {
|
||||||
screenCallback(sourceId = data.sourceId, data.canRequestAudioTrack === true);
|
screenCallback((sourceId = data.sourceId), data.canRequestAudioTrack === true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +49,8 @@ function isChromeExtensionAvailable(callback) {
|
||||||
|
|
||||||
// this function can be used to get "source-id" from the extension
|
// this function can be used to get "source-id" from the extension
|
||||||
function getSourceId(callback) {
|
function getSourceId(callback) {
|
||||||
if (!callback)
|
if (!callback) throw '"callback" parameter is mandatory.';
|
||||||
throw '"callback" parameter is mandatory.';
|
if (sourceId) return callback(sourceId);
|
||||||
if (sourceId)
|
|
||||||
return callback(sourceId);
|
|
||||||
screenCallback = callback;
|
screenCallback = callback;
|
||||||
window.postMessage('get-sourceId', '*');
|
window.postMessage('get-sourceId', '*');
|
||||||
}
|
}
|
||||||
|
@ -67,9 +63,12 @@ function getCustomSourceId(arr, callback) {
|
||||||
if (sourceId) return callback(sourceId);
|
if (sourceId) return callback(sourceId);
|
||||||
|
|
||||||
screenCallback = callback;
|
screenCallback = callback;
|
||||||
window.postMessage({
|
window.postMessage(
|
||||||
|
{
|
||||||
'get-custom-sourceId': arr
|
'get-custom-sourceId': arr
|
||||||
}, '*');
|
},
|
||||||
|
'*'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function can be used to get "source-id" from the extension
|
// this function can be used to get "source-id" from the extension
|
||||||
|
@ -82,8 +81,7 @@ function getSourceIdWithAudio(callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChromeExtensionStatus(extensionid, callback) {
|
function getChromeExtensionStatus(extensionid, callback) {
|
||||||
if (isFirefox)
|
if (isFirefox) return callback('not-chrome');
|
||||||
return callback('not-chrome');
|
|
||||||
if (arguments.length != 2) {
|
if (arguments.length != 2) {
|
||||||
callback = extensionid;
|
callback = extensionid;
|
||||||
extensionid = 'lfcgfepafnobdloecchnfaclibenjold'; // default extension-id
|
extensionid = 'lfcgfepafnobdloecchnfaclibenjold'; // default extension-id
|
||||||
|
@ -96,8 +94,7 @@ function getChromeExtensionStatus(extensionid, callback) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (chromeMediaSource == 'screen') {
|
if (chromeMediaSource == 'screen') {
|
||||||
callback('installed-disabled');
|
callback('installed-disabled');
|
||||||
} else
|
} else callback('installed-enabled');
|
||||||
callback('installed-enabled');
|
|
||||||
}, 2000);
|
}, 2000);
|
||||||
};
|
};
|
||||||
image.onerror = function () {
|
image.onerror = function () {
|
||||||
|
@ -116,8 +113,7 @@ function getScreenConstraints(callback, captureSourceIdWithAudio) {
|
||||||
mozMediaSource: 'window',
|
mozMediaSource: 'window',
|
||||||
mediaSource: 'window'
|
mediaSource: 'window'
|
||||||
};
|
};
|
||||||
if (isFirefox)
|
if (isFirefox) return callback(null, firefoxScreenConstraints);
|
||||||
return callback(null, firefoxScreenConstraints);
|
|
||||||
// this statement defines getUserMedia constraints
|
// this statement defines getUserMedia constraints
|
||||||
// that will be used to capture content of screen
|
// that will be used to capture content of screen
|
||||||
var screen_constraints = {
|
var screen_constraints = {
|
||||||
|
@ -141,8 +137,7 @@ function getScreenConstraints(callback, captureSourceIdWithAudio) {
|
||||||
}
|
}
|
||||||
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
|
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
getSourceId(function (sourceId) {
|
getSourceId(function (sourceId) {
|
||||||
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
|
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
|
||||||
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
|
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import platform = require('platform');
|
||||||
|
|
||||||
export class PlatformUtils {
|
export class PlatformUtils {
|
||||||
protected static instance: PlatformUtils;
|
protected static instance: PlatformUtils;
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
static getInstance(): PlatformUtils {
|
static getInstance(): PlatformUtils {
|
||||||
if (!this.instance) {
|
if (!this.instance) {
|
||||||
|
@ -12,49 +12,49 @@ export class PlatformUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public isChromeBrowser(): boolean {
|
public isChromeBrowser(): boolean {
|
||||||
return platform.name === "Chrome";
|
return platform.name === 'Chrome';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isSafariBrowser(): boolean {
|
public isSafariBrowser(): boolean {
|
||||||
return platform.name === "Safari";
|
return platform.name === 'Safari';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isChromeMobileBrowser(): boolean {
|
public isChromeMobileBrowser(): boolean {
|
||||||
return platform.name === "Chrome Mobile";
|
return platform.name === 'Chrome Mobile';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isFirefoxBrowser(): boolean {
|
public isFirefoxBrowser(): boolean {
|
||||||
return platform.name === "Firefox";
|
return platform.name === 'Firefox';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isFirefoxMobileBrowser(): boolean {
|
public isFirefoxMobileBrowser(): boolean {
|
||||||
return platform.name === "Firefox Mobile" || platform.name === "Firefox for iOS";
|
return platform.name === 'Firefox Mobile' || platform.name === 'Firefox for iOS';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isOperaBrowser(): boolean {
|
public isOperaBrowser(): boolean {
|
||||||
return platform.name === "Opera";
|
return platform.name === 'Opera';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isOperaMobileBrowser(): boolean {
|
public isOperaMobileBrowser(): boolean {
|
||||||
return platform.name === "Opera Mobile";
|
return platform.name === 'Opera Mobile';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,7 +62,7 @@ export class PlatformUtils {
|
||||||
*/
|
*/
|
||||||
public isEdgeBrowser(): boolean {
|
public isEdgeBrowser(): boolean {
|
||||||
const version = platform?.version ? parseFloat(platform.version) : -1;
|
const version = platform?.version ? parseFloat(platform.version) : -1;
|
||||||
return platform.name === "Microsoft Edge" && version >= 80;
|
return platform.name === 'Microsoft Edge' && version >= 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,38 +70,35 @@ export class PlatformUtils {
|
||||||
*/
|
*/
|
||||||
public isEdgeMobileBrowser(): boolean {
|
public isEdgeMobileBrowser(): boolean {
|
||||||
const version = platform?.version ? parseFloat(platform.version) : -1;
|
const version = platform?.version ? parseFloat(platform.version) : -1;
|
||||||
return platform.name === "Microsoft Edge" && (platform.os?.family === 'Android' || platform.os?.family === 'iOS') && version > 45;
|
return platform.name === 'Microsoft Edge' && (platform.os?.family === 'Android' || platform.os?.family === 'iOS') && version > 45;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isAndroidBrowser(): boolean {
|
public isAndroidBrowser(): boolean {
|
||||||
return platform.name === "Android Browser";
|
return platform.name === 'Android Browser';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isElectron(): boolean {
|
public isElectron(): boolean {
|
||||||
return platform.name === "Electron";
|
return platform.name === 'Electron';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isNodeJs(): boolean {
|
public isNodeJs(): boolean {
|
||||||
return platform.name === "Node.js";
|
return platform.name === 'Node.js';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isSamsungBrowser(): boolean {
|
public isSamsungBrowser(): boolean {
|
||||||
return (
|
return platform.name === 'Samsung Internet Mobile' || platform.name === 'Samsung Internet';
|
||||||
platform.name === "Samsung Internet Mobile" ||
|
|
||||||
platform.name === "Samsung Internet"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,12 +106,9 @@ export class PlatformUtils {
|
||||||
*/
|
*/
|
||||||
public isIPhoneOrIPad(): boolean {
|
public isIPhoneOrIPad(): boolean {
|
||||||
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
||||||
const isTouchable = "ontouchend" in document;
|
const isTouchable = 'ontouchend' in document;
|
||||||
const isIPad = /\b(\w*Macintosh\w*)\b/.test(userAgent) && isTouchable;
|
const isIPad = /\b(\w*Macintosh\w*)\b/.test(userAgent) && isTouchable;
|
||||||
const isIPhone =
|
const isIPhone = /\b(\w*iPhone\w*)\b/.test(userAgent) && /\b(\w*Mobile\w*)\b/.test(userAgent) && isTouchable;
|
||||||
/\b(\w*iPhone\w*)\b/.test(userAgent) &&
|
|
||||||
/\b(\w*Mobile\w*)\b/.test(userAgent) &&
|
|
||||||
isTouchable;
|
|
||||||
return isIPad || isIPhone;
|
return isIPad || isIPhone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +117,8 @@ export class PlatformUtils {
|
||||||
*/
|
*/
|
||||||
public isIOSWithSafari(): boolean {
|
public isIOSWithSafari(): boolean {
|
||||||
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
||||||
return this.isIPhoneOrIPad() && (
|
return (
|
||||||
|
this.isIPhoneOrIPad() &&
|
||||||
/\b(\w*Apple\w*)\b/.test(navigator.vendor) &&
|
/\b(\w*Apple\w*)\b/.test(navigator.vendor) &&
|
||||||
/\b(\w*Safari\w*)\b/.test(userAgent) &&
|
/\b(\w*Safari\w*)\b/.test(userAgent) &&
|
||||||
!/\b(\w*CriOS\w*)\b/.test(userAgent) &&
|
!/\b(\w*CriOS\w*)\b/.test(userAgent) &&
|
||||||
|
@ -135,23 +130,21 @@ export class PlatformUtils {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isIonicIos(): boolean {
|
public isIonicIos(): boolean {
|
||||||
return this.isIPhoneOrIPad() && platform.ua!!.indexOf("Safari") === -1;
|
return this.isIPhoneOrIPad() && platform.ua!!.indexOf('Safari') === -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isIonicAndroid(): boolean {
|
public isIonicAndroid(): boolean {
|
||||||
return (
|
return platform.os!!.family === 'Android' && platform.name == 'Android Browser';
|
||||||
platform.os!!.family === "Android" && platform.name == "Android Browser"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isMobileDevice(): boolean {
|
public isMobileDevice(): boolean {
|
||||||
return platform.os!!.family === "iOS" || platform.os!!.family === "Android";
|
return platform.os!!.family === 'iOS' || platform.os!!.family === 'Android';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -165,12 +158,18 @@ export class PlatformUtils {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public isChromium(): boolean {
|
public isChromium(): boolean {
|
||||||
return this.isChromeBrowser() || this.isChromeMobileBrowser() ||
|
return (
|
||||||
this.isOperaBrowser() || this.isOperaMobileBrowser() ||
|
this.isChromeBrowser() ||
|
||||||
this.isEdgeBrowser() || this.isEdgeMobileBrowser() ||
|
this.isChromeMobileBrowser() ||
|
||||||
|
this.isOperaBrowser() ||
|
||||||
|
this.isOperaMobileBrowser() ||
|
||||||
|
this.isEdgeBrowser() ||
|
||||||
|
this.isEdgeMobileBrowser() ||
|
||||||
this.isSamsungBrowser() ||
|
this.isSamsungBrowser() ||
|
||||||
this.isIonicAndroid() || this.isIonicIos() ||
|
this.isIonicAndroid() ||
|
||||||
this.isElectron();
|
this.isIonicIos() ||
|
||||||
|
this.isElectron()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,27 +195,27 @@ export class PlatformUtils {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
return platform.name || "";
|
return platform.name || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public getVersion(): string {
|
public getVersion(): string {
|
||||||
return platform.version || "";
|
return platform.version || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public getFamily(): string {
|
public getFamily(): string {
|
||||||
return platform.os!!.family || "";
|
return platform.os!!.family || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
public getDescription(): string {
|
public getDescription(): string {
|
||||||
return platform.description || "";
|
return platform.description || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,24 +63,17 @@ export class WebRtcPeer {
|
||||||
|
|
||||||
this.configuration = {
|
this.configuration = {
|
||||||
...configuration,
|
...configuration,
|
||||||
iceServers:
|
iceServers: !!configuration.iceServers && configuration.iceServers.length > 0 ? configuration.iceServers : freeice(),
|
||||||
!!configuration.iceServers &&
|
mediaStream: configuration.mediaStream !== undefined ? configuration.mediaStream : null,
|
||||||
configuration.iceServers.length > 0
|
mode: !!configuration.mode ? configuration.mode : 'sendrecv',
|
||||||
? configuration.iceServers
|
id: !!configuration.id ? configuration.id : this.generateUniqueId()
|
||||||
: freeice(),
|
|
||||||
mediaStream:
|
|
||||||
configuration.mediaStream !== undefined
|
|
||||||
? configuration.mediaStream
|
|
||||||
: null,
|
|
||||||
mode: !!configuration.mode ? configuration.mode : "sendrecv",
|
|
||||||
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
|
|
||||||
};
|
};
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
logger.debug(`[WebRtcPeer] configuration:\n${JSON.stringify(this.configuration, null, 2)}`);
|
logger.debug(`[WebRtcPeer] configuration:\n${JSON.stringify(this.configuration, null, 2)}`);
|
||||||
|
|
||||||
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
|
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
|
||||||
|
|
||||||
this.pc.addEventListener("icecandidate", (event: RTCPeerConnectionIceEvent) => {
|
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
|
||||||
if (event.candidate !== null) {
|
if (event.candidate !== null) {
|
||||||
// `RTCPeerConnectionIceEvent.candidate` is supposed to be an RTCIceCandidate:
|
// `RTCPeerConnectionIceEvent.candidate` is supposed to be an RTCIceCandidate:
|
||||||
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate
|
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate
|
||||||
|
@ -140,11 +133,11 @@ export class WebRtcPeer {
|
||||||
const hasVideo = this.configuration.mediaConstraints.video;
|
const hasVideo = this.configuration.mediaConstraints.video;
|
||||||
|
|
||||||
const options: RTCOfferOptions = {
|
const options: RTCOfferOptions = {
|
||||||
offerToReceiveAudio: this.configuration.mode !== "sendonly" && hasAudio,
|
offerToReceiveAudio: this.configuration.mode !== 'sendonly' && hasAudio,
|
||||||
offerToReceiveVideo: this.configuration.mode !== "sendonly" && hasVideo,
|
offerToReceiveVideo: this.configuration.mode !== 'sendonly' && hasVideo
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug("[createOfferLegacy] RTCPeerConnection.createOffer() options:", JSON.stringify(options));
|
logger.debug('[createOfferLegacy] RTCPeerConnection.createOffer() options:', JSON.stringify(options));
|
||||||
|
|
||||||
return this.pc.createOffer(options);
|
return this.pc.createOffer(options);
|
||||||
}
|
}
|
||||||
|
@ -156,18 +149,18 @@ export class WebRtcPeer {
|
||||||
async createOffer(): Promise<RTCSessionDescriptionInit> {
|
async createOffer(): Promise<RTCSessionDescriptionInit> {
|
||||||
// TODO: Delete this conditional when all supported browsers are
|
// TODO: Delete this conditional when all supported browsers are
|
||||||
// modern enough to implement the Transceiver methods.
|
// modern enough to implement the Transceiver methods.
|
||||||
if (!("addTransceiver" in this.pc)) {
|
if (!('addTransceiver' in this.pc)) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}"
|
'[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}'
|
||||||
);
|
);
|
||||||
return this.createOfferLegacy();
|
return this.createOfferLegacy();
|
||||||
} else {
|
} else {
|
||||||
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it");
|
logger.debug('[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spec doc: https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver
|
// Spec doc: https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver
|
||||||
|
|
||||||
if (this.configuration.mode !== "recvonly") {
|
if (this.configuration.mode !== 'recvonly') {
|
||||||
// To send media, assume that all desired media tracks have been
|
// To send media, assume that all desired media tracks have been
|
||||||
// already added by higher level code to our MediaStream.
|
// already added by higher level code to our MediaStream.
|
||||||
|
|
||||||
|
@ -180,24 +173,18 @@ export class WebRtcPeer {
|
||||||
for (const track of this.configuration.mediaStream.getTracks()) {
|
for (const track of this.configuration.mediaStream.getTracks()) {
|
||||||
const tcInit: RTCRtpTransceiverInit = {
|
const tcInit: RTCRtpTransceiverInit = {
|
||||||
direction: this.configuration.mode,
|
direction: this.configuration.mode,
|
||||||
streams: [this.configuration.mediaStream],
|
streams: [this.configuration.mediaStream]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (track.kind === "video" && this.configuration.simulcast) {
|
if (track.kind === 'video' && this.configuration.simulcast) {
|
||||||
// Check if the requested size is enough to ask for 3 layers.
|
// Check if the requested size is enough to ask for 3 layers.
|
||||||
const trackSettings = track.getSettings();
|
const trackSettings = track.getSettings();
|
||||||
const trackConsts = track.getConstraints();
|
const trackConsts = track.getConstraints();
|
||||||
|
|
||||||
const trackWidth: number =
|
const trackWidth: number =
|
||||||
trackSettings.width ??
|
trackSettings.width ?? (trackConsts.width as ConstrainULongRange).ideal ?? (trackConsts.width as number) ?? 0;
|
||||||
(trackConsts.width as ConstrainULongRange).ideal ??
|
|
||||||
(trackConsts.width as number) ??
|
|
||||||
0;
|
|
||||||
const trackHeight: number =
|
const trackHeight: number =
|
||||||
trackSettings.height ??
|
trackSettings.height ?? (trackConsts.height as ConstrainULongRange).ideal ?? (trackConsts.height as number) ?? 0;
|
||||||
(trackConsts.height as ConstrainULongRange).ideal ??
|
|
||||||
(trackConsts.height as number) ??
|
|
||||||
0;
|
|
||||||
logger.info(`[createOffer] Video track dimensions: ${trackWidth}x${trackHeight}`);
|
logger.info(`[createOffer] Video track dimensions: ${trackWidth}x${trackHeight}`);
|
||||||
|
|
||||||
const trackPixels = trackWidth * trackHeight;
|
const trackPixels = trackWidth * trackHeight;
|
||||||
|
@ -215,13 +202,13 @@ export class WebRtcPeer {
|
||||||
const layerDiv = 2 ** (maxLayers - l - 1);
|
const layerDiv = 2 ** (maxLayers - l - 1);
|
||||||
|
|
||||||
const encoding: RTCRtpEncodingParameters = {
|
const encoding: RTCRtpEncodingParameters = {
|
||||||
rid: "rdiv" + layerDiv.toString(),
|
rid: 'rdiv' + layerDiv.toString(),
|
||||||
|
|
||||||
// @ts-ignore -- Property missing from DOM types.
|
// @ts-ignore -- Property missing from DOM types.
|
||||||
scalabilityMode: "L1T1",
|
scalabilityMode: 'L1T1'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (["detail", "text"].includes(track.contentHint)) {
|
if (['detail', 'text'].includes(track.contentHint)) {
|
||||||
// Prioritize best resolution, for maximum picture detail.
|
// Prioritize best resolution, for maximum picture detail.
|
||||||
encoding.scaleResolutionDownBy = 1.0;
|
encoding.scaleResolutionDownBy = 1.0;
|
||||||
|
|
||||||
|
@ -237,22 +224,20 @@ export class WebRtcPeer {
|
||||||
|
|
||||||
const tc = this.pc.addTransceiver(track, tcInit);
|
const tc = this.pc.addTransceiver(track, tcInit);
|
||||||
|
|
||||||
if (track.kind === "video") {
|
if (track.kind === 'video') {
|
||||||
let sendParams = tc.sender.getParameters();
|
let sendParams = tc.sender.getParameters();
|
||||||
let needSetParams = false;
|
let needSetParams = false;
|
||||||
|
|
||||||
if (!sendParams.degradationPreference?.length) {
|
if (!sendParams.degradationPreference?.length) {
|
||||||
// degradationPreference for video: "balanced", "maintain-framerate", "maintain-resolution".
|
// degradationPreference for video: "balanced", "maintain-framerate", "maintain-resolution".
|
||||||
// https://www.w3.org/TR/2018/CR-webrtc-20180927/#dom-rtcdegradationpreference
|
// https://www.w3.org/TR/2018/CR-webrtc-20180927/#dom-rtcdegradationpreference
|
||||||
if (["detail", "text"].includes(track.contentHint)) {
|
if (['detail', 'text'].includes(track.contentHint)) {
|
||||||
sendParams.degradationPreference = "maintain-resolution";
|
sendParams.degradationPreference = 'maintain-resolution';
|
||||||
} else {
|
} else {
|
||||||
sendParams.degradationPreference = "balanced";
|
sendParams.degradationPreference = 'balanced';
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(`[createOffer] Video sender Degradation Preference set: ${sendParams.degradationPreference}`);
|
||||||
`[createOffer] Video sender Degradation Preference set: ${sendParams.degradationPreference}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Firefox implements degradationPreference on each individual encoding!
|
// FIXME: Firefox implements degradationPreference on each individual encoding!
|
||||||
// (set it on every element of the sendParams.encodings array)
|
// (set it on every element of the sendParams.encodings array)
|
||||||
|
@ -310,7 +295,7 @@ export class WebRtcPeer {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// To just receive media, create new recvonly transceivers.
|
// To just receive media, create new recvonly transceivers.
|
||||||
for (const kind of ["audio", "video"]) {
|
for (const kind of ['audio', 'video']) {
|
||||||
// Check if the media kind should be used.
|
// Check if the media kind should be used.
|
||||||
if (!this.configuration.mediaConstraints[kind]) {
|
if (!this.configuration.mediaConstraints[kind]) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -319,7 +304,7 @@ export class WebRtcPeer {
|
||||||
this.configuration.mediaStream = new MediaStream();
|
this.configuration.mediaStream = new MediaStream();
|
||||||
this.pc.addTransceiver(kind, {
|
this.pc.addTransceiver(kind, {
|
||||||
direction: this.configuration.mode,
|
direction: this.configuration.mode,
|
||||||
streams: [this.configuration.mediaStream],
|
streams: [this.configuration.mediaStream]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,23 +337,21 @@ export class WebRtcPeer {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// TODO: Delete this conditional when all supported browsers are
|
// TODO: Delete this conditional when all supported browsers are
|
||||||
// modern enough to implement the Transceiver methods.
|
// modern enough to implement the Transceiver methods.
|
||||||
if ("getTransceivers" in this.pc) {
|
if ('getTransceivers' in this.pc) {
|
||||||
logger.debug("[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it");
|
logger.debug('[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it');
|
||||||
|
|
||||||
// Ensure that the PeerConnection already contains one Transceiver
|
// Ensure that the PeerConnection already contains one Transceiver
|
||||||
// for each kind of media.
|
// for each kind of media.
|
||||||
// The Transceivers should have been already created internally by
|
// The Transceivers should have been already created internally by
|
||||||
// the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called.
|
// the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called.
|
||||||
|
|
||||||
for (const kind of ["audio", "video"]) {
|
for (const kind of ['audio', 'video']) {
|
||||||
// Check if the media kind should be used.
|
// Check if the media kind should be used.
|
||||||
if (!this.configuration.mediaConstraints[kind]) {
|
if (!this.configuration.mediaConstraints[kind]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tc = this.pc
|
let tc = this.pc.getTransceivers().find((tc) => tc.receiver.track.kind === kind);
|
||||||
.getTransceivers()
|
|
||||||
.find((tc) => tc.receiver.track.kind === kind);
|
|
||||||
|
|
||||||
if (tc) {
|
if (tc) {
|
||||||
// Enforce our desired direction.
|
// Enforce our desired direction.
|
||||||
|
@ -382,27 +365,25 @@ export class WebRtcPeer {
|
||||||
.createAnswer()
|
.createAnswer()
|
||||||
.then((sdpAnswer) => resolve(sdpAnswer))
|
.then((sdpAnswer) => resolve(sdpAnswer))
|
||||||
.catch((error) => reject(error));
|
.catch((error) => reject(error));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// TODO: Delete else branch when all supported browsers are
|
// TODO: Delete else branch when all supported browsers are
|
||||||
// modern enough to implement the Transceiver methods
|
// modern enough to implement the Transceiver methods
|
||||||
|
|
||||||
let offerAudio, offerVideo = true;
|
let offerAudio,
|
||||||
|
offerVideo = true;
|
||||||
if (!!this.configuration.mediaConstraints) {
|
if (!!this.configuration.mediaConstraints) {
|
||||||
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ?
|
offerAudio =
|
||||||
this.configuration.mediaConstraints.audio : true;
|
typeof this.configuration.mediaConstraints.audio === 'boolean' ? this.configuration.mediaConstraints.audio : true;
|
||||||
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ?
|
offerVideo =
|
||||||
this.configuration.mediaConstraints.video : true;
|
typeof this.configuration.mediaConstraints.video === 'boolean' ? this.configuration.mediaConstraints.video : true;
|
||||||
const constraints: RTCOfferOptions = {
|
const constraints: RTCOfferOptions = {
|
||||||
offerToReceiveAudio: offerAudio,
|
offerToReceiveAudio: offerAudio,
|
||||||
offerToReceiveVideo: offerVideo
|
offerToReceiveVideo: offerVideo
|
||||||
};
|
};
|
||||||
this.pc!.createAnswer(constraints)
|
this.pc!.createAnswer(constraints)
|
||||||
.then(sdpAnswer => resolve(sdpAnswer))
|
.then((sdpAnswer) => resolve(sdpAnswer))
|
||||||
.catch(error => reject(error));
|
.catch((error) => reject(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// else, there is nothing to do; the legacy createAnswer() options do
|
// else, there is nothing to do; the legacy createAnswer() options do
|
||||||
|
@ -415,7 +396,8 @@ export class WebRtcPeer {
|
||||||
*/
|
*/
|
||||||
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
|
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.pc.setLocalDescription(offer)
|
this.pc
|
||||||
|
.setLocalDescription(offer)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const localDescription = this.pc.localDescription;
|
const localDescription = this.pc.localDescription;
|
||||||
if (!!localDescription) {
|
if (!!localDescription) {
|
||||||
|
@ -425,7 +407,7 @@ export class WebRtcPeer {
|
||||||
return reject('Local description is not defined');
|
return reject('Local description is not defined');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => reject(error));
|
.catch((error) => reject(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,7 +427,7 @@ export class WebRtcPeer {
|
||||||
}
|
}
|
||||||
this.setRemoteDescription(offer)
|
this.setRemoteDescription(offer)
|
||||||
.then(() => resolve())
|
.then(() => resolve())
|
||||||
.catch(error => reject(error));
|
.catch((error) => reject(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,9 +440,10 @@ export class WebRtcPeer {
|
||||||
if (this.pc.signalingState === 'closed') {
|
if (this.pc.signalingState === 'closed') {
|
||||||
return reject('RTCPeerConnection is closed when trying to set local description');
|
return reject('RTCPeerConnection is closed when trying to set local description');
|
||||||
}
|
}
|
||||||
this.pc.setLocalDescription(answer)
|
this.pc
|
||||||
|
.setLocalDescription(answer)
|
||||||
.then(() => resolve())
|
.then(() => resolve())
|
||||||
.catch(error => reject(error));
|
.catch((error) => reject(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,7 +496,10 @@ export class WebRtcPeer {
|
||||||
break;
|
break;
|
||||||
case 'stable':
|
case 'stable':
|
||||||
if (!!this.pc.remoteDescription) {
|
if (!!this.pc.remoteDescription) {
|
||||||
this.pc.addIceCandidate(iceCandidate).then(() => resolve()).catch(error => reject(error));
|
this.pc
|
||||||
|
.addIceCandidate(iceCandidate)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch((error) => reject(error));
|
||||||
} else {
|
} else {
|
||||||
this.iceCandidateList.push(iceCandidate);
|
this.iceCandidateList.push(iceCandidate);
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -532,7 +518,12 @@ export class WebRtcPeer {
|
||||||
switch (iceConnectionState) {
|
switch (iceConnectionState) {
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
// Possible network disconnection
|
// Possible network disconnection
|
||||||
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
|
const msg1 =
|
||||||
|
'IceConnectionState of RTCPeerConnection ' +
|
||||||
|
this.configuration.id +
|
||||||
|
' (' +
|
||||||
|
otherId +
|
||||||
|
') change to "disconnected". Possible network disconnection';
|
||||||
logger.warn(msg1);
|
logger.warn(msg1);
|
||||||
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
||||||
break;
|
break;
|
||||||
|
@ -542,19 +533,27 @@ export class WebRtcPeer {
|
||||||
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
||||||
break;
|
break;
|
||||||
case 'closed':
|
case 'closed':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
|
logger.log(
|
||||||
|
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"'
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'new':
|
case 'new':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"');
|
||||||
break;
|
break;
|
||||||
case 'checking':
|
case 'checking':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"');
|
logger.log(
|
||||||
|
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"'
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"');
|
logger.log(
|
||||||
|
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"'
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'completed':
|
case 'completed':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"');
|
logger.log(
|
||||||
|
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"'
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -566,10 +565,8 @@ export class WebRtcPeer {
|
||||||
generateUniqueId(): string {
|
generateUniqueId(): string {
|
||||||
return uuidv4();
|
return uuidv4();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class WebRtcPeerRecvonly extends WebRtcPeer {
|
export class WebRtcPeerRecvonly extends WebRtcPeer {
|
||||||
constructor(configuration: WebRtcPeerConfiguration) {
|
constructor(configuration: WebRtcPeerConfiguration) {
|
||||||
configuration.mode = 'recvonly';
|
configuration.mode = 'recvonly';
|
||||||
|
|
|
@ -30,18 +30,18 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||||
let platform: PlatformUtils;
|
let platform: PlatformUtils;
|
||||||
|
|
||||||
interface WebrtcStatsConfig {
|
interface WebrtcStatsConfig {
|
||||||
interval: number,
|
interval: number;
|
||||||
httpEndpoint: string
|
httpEndpoint: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JSONStatsResponse {
|
interface JSONStatsResponse {
|
||||||
'@timestamp': string,
|
'@timestamp': string;
|
||||||
participant_id: string,
|
participant_id: string;
|
||||||
session_id: string,
|
session_id: string;
|
||||||
platform: string,
|
platform: string;
|
||||||
platform_description: string,
|
platform_description: string;
|
||||||
stream: string,
|
stream: string;
|
||||||
webrtc_stats: IWebrtcStats
|
webrtc_stats: IWebrtcStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,55 +49,62 @@ interface JSONStatsResponse {
|
||||||
*/
|
*/
|
||||||
interface IWebrtcStats {
|
interface IWebrtcStats {
|
||||||
inbound?: {
|
inbound?: {
|
||||||
audio: {
|
audio:
|
||||||
bytesReceived: number,
|
| {
|
||||||
packetsReceived: number,
|
bytesReceived: number;
|
||||||
packetsLost: number,
|
packetsReceived: number;
|
||||||
jitter: number
|
packetsLost: number;
|
||||||
} | {},
|
jitter: number;
|
||||||
video: {
|
|
||||||
bytesReceived: number,
|
|
||||||
packetsReceived: number,
|
|
||||||
packetsLost: number,
|
|
||||||
jitter?: number, // Firefox
|
|
||||||
jitterBufferDelay?: number, // Chrome
|
|
||||||
framesDecoded: number,
|
|
||||||
firCount: number,
|
|
||||||
nackCount: number,
|
|
||||||
pliCount: number,
|
|
||||||
frameHeight?: number, // Chrome
|
|
||||||
frameWidth?: number, // Chrome
|
|
||||||
framesDropped?: number, // Chrome
|
|
||||||
framesReceived?: number // Chrome
|
|
||||||
} | {}
|
|
||||||
},
|
|
||||||
outbound?: {
|
|
||||||
audio: {
|
|
||||||
bytesSent: number,
|
|
||||||
packetsSent: number,
|
|
||||||
} | {},
|
|
||||||
video: {
|
|
||||||
bytesSent: number,
|
|
||||||
packetsSent: number,
|
|
||||||
firCount: number,
|
|
||||||
framesEncoded: number,
|
|
||||||
nackCount: number,
|
|
||||||
pliCount: number,
|
|
||||||
qpSum: number,
|
|
||||||
frameHeight?: number, // Chrome
|
|
||||||
frameWidth?: number, // Chrome
|
|
||||||
framesSent?: number // Chrome
|
|
||||||
} | {}
|
|
||||||
},
|
|
||||||
candidatepair?: {
|
|
||||||
currentRoundTripTime?: number // Chrome
|
|
||||||
availableOutgoingBitrate?: number //Chrome
|
|
||||||
// availableIncomingBitrate?: number // No support for any browsers (https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableIncomingBitrate)
|
|
||||||
}
|
}
|
||||||
};
|
| {};
|
||||||
|
video:
|
||||||
|
| {
|
||||||
|
bytesReceived: number;
|
||||||
|
packetsReceived: number;
|
||||||
|
packetsLost: number;
|
||||||
|
jitter?: number; // Firefox
|
||||||
|
jitterBufferDelay?: number; // Chrome
|
||||||
|
framesDecoded: number;
|
||||||
|
firCount: number;
|
||||||
|
nackCount: number;
|
||||||
|
pliCount: number;
|
||||||
|
frameHeight?: number; // Chrome
|
||||||
|
frameWidth?: number; // Chrome
|
||||||
|
framesDropped?: number; // Chrome
|
||||||
|
framesReceived?: number; // Chrome
|
||||||
|
}
|
||||||
|
| {};
|
||||||
|
};
|
||||||
|
outbound?: {
|
||||||
|
audio:
|
||||||
|
| {
|
||||||
|
bytesSent: number;
|
||||||
|
packetsSent: number;
|
||||||
|
}
|
||||||
|
| {};
|
||||||
|
video:
|
||||||
|
| {
|
||||||
|
bytesSent: number;
|
||||||
|
packetsSent: number;
|
||||||
|
firCount: number;
|
||||||
|
framesEncoded: number;
|
||||||
|
nackCount: number;
|
||||||
|
pliCount: number;
|
||||||
|
qpSum: number;
|
||||||
|
frameHeight?: number; // Chrome
|
||||||
|
frameWidth?: number; // Chrome
|
||||||
|
framesSent?: number; // Chrome
|
||||||
|
}
|
||||||
|
| {};
|
||||||
|
};
|
||||||
|
candidatepair?: {
|
||||||
|
currentRoundTripTime?: number; // Chrome
|
||||||
|
availableOutgoingBitrate?: number; //Chrome
|
||||||
|
// availableIncomingBitrate?: number // No support for any browsers (https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableIncomingBitrate)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class WebRtcStats {
|
export class WebRtcStats {
|
||||||
|
|
||||||
private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
|
private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
|
||||||
|
|
||||||
private webRtcStatsEnabled = false;
|
private webRtcStatsEnabled = false;
|
||||||
|
@ -114,14 +121,15 @@ export class WebRtcStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
public initWebRtcStats(): void {
|
public initWebRtcStats(): void {
|
||||||
|
|
||||||
const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
|
const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
|
||||||
|
|
||||||
if (!!webrtcObj) {
|
if (!!webrtcObj) {
|
||||||
this.webRtcStatsEnabled = true;
|
this.webRtcStatsEnabled = true;
|
||||||
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
|
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
|
||||||
// webrtc object found in local storage
|
// webrtc object found in local storage
|
||||||
logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
|
logger.warn(
|
||||||
|
'WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId
|
||||||
|
);
|
||||||
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
|
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
|
||||||
|
|
||||||
this.POST_URL = webrtcStatsConfig.httpEndpoint;
|
this.POST_URL = webrtcStatsConfig.httpEndpoint;
|
||||||
|
@ -130,7 +138,6 @@ export class WebRtcStats {
|
||||||
this.webRtcStatsIntervalId = setInterval(async () => {
|
this.webRtcStatsIntervalId = setInterval(async () => {
|
||||||
await this.sendStatsToHttpEndpoint();
|
await this.sendStatsToHttpEndpoint();
|
||||||
}, this.statsInterval * 1000);
|
}, this.statsInterval * 1000);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.debug('WebRtc stats not enabled');
|
logger.debug('WebRtc stats not enabled');
|
||||||
}
|
}
|
||||||
|
@ -206,7 +213,6 @@ export class WebRtcStats {
|
||||||
// - ¿React Native?
|
// - ¿React Native?
|
||||||
public getSelectedIceCandidateInfo(): Promise<any> {
|
public getSelectedIceCandidateInfo(): Promise<any> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
|
||||||
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
|
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
|
||||||
let transportStat;
|
let transportStat;
|
||||||
const candidatePairs: Map<string, any> = new Map();
|
const candidatePairs: Map<string, any> = new Map();
|
||||||
|
@ -230,7 +236,7 @@ export class WebRtcStats {
|
||||||
});
|
});
|
||||||
let selectedCandidatePair;
|
let selectedCandidatePair;
|
||||||
if (transportStat != null) {
|
if (transportStat != null) {
|
||||||
const selectedCandidatePairId = transportStat.selectedCandidatePairId
|
const selectedCandidatePairId = transportStat.selectedCandidatePairId;
|
||||||
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
|
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
|
||||||
} else {
|
} else {
|
||||||
// This is basically Firefox
|
// This is basically Firefox
|
||||||
|
@ -250,9 +256,11 @@ export class WebRtcStats {
|
||||||
if (!!finalLocalCandidate) {
|
if (!!finalLocalCandidate) {
|
||||||
const candList = this.stream.getLocalIceCandidateList();
|
const candList = this.stream.getLocalIceCandidateList();
|
||||||
const cand = candList.filter((c: RTCIceCandidate) => {
|
const cand = candList.filter((c: RTCIceCandidate) => {
|
||||||
return (!!c.candidate &&
|
return (
|
||||||
|
!!c.candidate &&
|
||||||
(c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
|
(c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
|
||||||
c.candidate.indexOf(finalLocalCandidate.port) >= 0);
|
c.candidate.indexOf(finalLocalCandidate.port) >= 0
|
||||||
|
);
|
||||||
});
|
});
|
||||||
finalLocalCandidate.raw = [];
|
finalLocalCandidate.raw = [];
|
||||||
for (let c of cand) {
|
for (let c of cand) {
|
||||||
|
@ -266,9 +274,11 @@ export class WebRtcStats {
|
||||||
if (!!finalRemoteCandidate) {
|
if (!!finalRemoteCandidate) {
|
||||||
const candList = this.stream.getRemoteIceCandidateList();
|
const candList = this.stream.getRemoteIceCandidateList();
|
||||||
const cand = candList.filter((c: RTCIceCandidate) => {
|
const cand = candList.filter((c: RTCIceCandidate) => {
|
||||||
return (!!c.candidate &&
|
return (
|
||||||
|
!!c.candidate &&
|
||||||
(c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
|
(c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
|
||||||
c.candidate.indexOf(finalRemoteCandidate.port) >= 0);
|
c.candidate.indexOf(finalRemoteCandidate.port) >= 0
|
||||||
|
);
|
||||||
});
|
});
|
||||||
finalRemoteCandidate.raw = [];
|
finalRemoteCandidate.raw = [];
|
||||||
for (let c of cand) {
|
for (let c of cand) {
|
||||||
|
@ -288,7 +298,9 @@ export class WebRtcStats {
|
||||||
public stopWebRtcStats() {
|
public stopWebRtcStats() {
|
||||||
if (this.webRtcStatsEnabled) {
|
if (this.webRtcStatsEnabled) {
|
||||||
clearInterval(this.webRtcStatsIntervalId);
|
clearInterval(this.webRtcStatsIntervalId);
|
||||||
logger.warn('WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
|
logger.warn(
|
||||||
|
'WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,10 +311,9 @@ export class WebRtcStats {
|
||||||
'Content-type': 'application/json'
|
'Content-type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(response),
|
body: JSON.stringify(response),
|
||||||
method: 'POST',
|
method: 'POST'
|
||||||
};
|
};
|
||||||
await fetch(url, configuration);
|
await fetch(url, configuration);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`sendStats error: ${JSON.stringify(error)}`);
|
logger.error(`sendStats error: ${JSON.stringify(error)}`);
|
||||||
}
|
}
|
||||||
|
@ -350,9 +361,7 @@ export class WebRtcStats {
|
||||||
// - ¿Ionic?
|
// - ¿Ionic?
|
||||||
// - ¿React Native?
|
// - ¿React Native?
|
||||||
public async getCommonStats(): Promise<IWebrtcStats> {
|
public async getCommonStats(): Promise<IWebrtcStats> {
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
|
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
|
||||||
const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
|
const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
|
||||||
|
@ -360,24 +369,23 @@ export class WebRtcStats {
|
||||||
const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
|
const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
|
||||||
|
|
||||||
statsReport.forEach((stat: any) => {
|
statsReport.forEach((stat: any) => {
|
||||||
|
|
||||||
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
|
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
|
||||||
const addStat = (direction: string, key: string): void => {
|
const addStat = (direction: string, key: string): void => {
|
||||||
if (stat[key] != null && response[direction] != null) {
|
if (stat[key] != null && response[direction] != null) {
|
||||||
if (!mediaType && (videoTrackStats.indexOf(key) > -1)) {
|
if (!mediaType && videoTrackStats.indexOf(key) > -1) {
|
||||||
mediaType = 'video';
|
mediaType = 'video';
|
||||||
}
|
}
|
||||||
if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
|
if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
|
||||||
response[direction][mediaType][key] = Number(stat[key]);
|
response[direction][mediaType][key] = Number(stat[key]);
|
||||||
} else if(direction != null && key != null && candidatePairStats.includes(key)) {
|
} else if (direction != null && key != null && candidatePairStats.includes(key)) {
|
||||||
// candidate-pair-stats
|
// candidate-pair-stats
|
||||||
response[direction][key] = Number(stat[key]);
|
response[direction][key] = Number(stat[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
switch (stat.type) {
|
switch (stat.type) {
|
||||||
case "outbound-rtp":
|
case 'outbound-rtp':
|
||||||
addStat('outbound', 'bytesSent');
|
addStat('outbound', 'bytesSent');
|
||||||
addStat('outbound', 'packetsSent');
|
addStat('outbound', 'packetsSent');
|
||||||
addStat('outbound', 'framesEncoded');
|
addStat('outbound', 'framesEncoded');
|
||||||
|
@ -386,7 +394,7 @@ export class WebRtcStats {
|
||||||
addStat('outbound', 'pliCount');
|
addStat('outbound', 'pliCount');
|
||||||
addStat('outbound', 'qpSum');
|
addStat('outbound', 'qpSum');
|
||||||
break;
|
break;
|
||||||
case "inbound-rtp":
|
case 'inbound-rtp':
|
||||||
addStat('inbound', 'bytesReceived');
|
addStat('inbound', 'bytesReceived');
|
||||||
addStat('inbound', 'packetsReceived');
|
addStat('inbound', 'packetsReceived');
|
||||||
addStat('inbound', 'packetsLost');
|
addStat('inbound', 'packetsLost');
|
||||||
|
@ -412,7 +420,7 @@ export class WebRtcStats {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete candidatepair from response if null
|
// Delete candidatepair from response if null
|
||||||
if(!response?.candidatepair || Object.keys(<Object>response.candidatepair).length === 0){
|
if (!response?.candidatepair || Object.keys(<Object>response.candidatepair).length === 0) {
|
||||||
delete response.candidatepair;
|
delete response.candidatepair;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +429,6 @@ export class WebRtcStats {
|
||||||
logger.error('Error getting common stats: ', error);
|
logger.error('Error getting common stats: ', error);
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,5 +462,4 @@ export class WebRtcStats {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -45,4 +45,4 @@ export { StreamManagerEventMap } from './OpenViduInternal/Events/EventMap/Stream
|
||||||
export { PublisherEventMap } from './OpenViduInternal/Events/EventMap/PublisherEventMap';
|
export { PublisherEventMap } from './OpenViduInternal/Events/EventMap/PublisherEventMap';
|
||||||
|
|
||||||
// Disable jsnlog when library is loaded
|
// Disable jsnlog when library is loaded
|
||||||
JL.setOptions({ enabled: false })
|
JL.setOptions({ enabled: false });
|
||||||
|
|
Loading…
Reference in New Issue