diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index c5ee097f..99c91774 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -77,6 +77,7 @@ export class Publisher extends StreamManager { this.openvidu = openvidu; this.stream.ee.on('local-stream-destroyed', (reason: string) => { + this.stream.isLocalStreamPublished = false; const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason); this.emitEvent('streamDestroyed', [streamEvent]); streamEvent.callDefaultBehavior(); diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index b38d9560..8914082f 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -330,7 +330,7 @@ export class Session implements EventDispatcher { publisher.session = this; publisher.stream.session = this; - if (!publisher.stream.isLocalStreamPublished) { + if (!publisher.stream.publishedOnce) { // 'Session.unpublish(Publisher)' has NOT been called this.connection.addStream(publisher.stream); publisher.stream.publish() @@ -413,6 +413,88 @@ export class Session implements EventDispatcher { } + /** + * Forces some user to leave the session + * + * #### Events dispatched + * + * The behavior is the same as when some user calls [[Session.disconnect]], but `reason` property in all events will be `"forceDisconnectByUser"`. + * + * The local [[Session]] object will dispatch: + * - A `streamDestroyed` event if the evicted user was publishing a stream, with property `reason` set to `"forceDisconnectByUser"` + * - A `connectionDestroyed` event for the evicted user, with property `reason` set to `"forceDisconnectByUser"` + * + * The remote [[Session]] object of every other participant will dispatch: + * - A `streamDestroyed` event if the evicted user was publishing a stream, with property `reason` set to `"forceDisconnectByUser"` + * - A `connectionDestroyed` event for the evicted user, with property `reason` set to `"forceDisconnectByUser"` + * + * If any, the [[Publisher]] object of the evicted participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"` + * + * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the participant has been successfully evicted from the session and rejected with an Error object if not + */ + forceDisconnect(connection: Connection): Promise { + return new Promise((resolve, reject) => { + console.info('Forcing disconnect for connection ' + connection.connectionId); + this.openvidu.sendRequest( + 'forceDisconnect', + { connectionId: connection.connectionId }, + (error, response) => { + if (error) { + console.error('Error forcing disconnect for Connection ' + connection.connectionId, error); + if (error.code === 401) { + reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to force a disconnection")); + } else { + reject(error); + } + } else { + console.info('Forcing disconnect correctly for Connection ' + connection.connectionId); + resolve(); + } + } + ); + }); + } + + + /** + * Forces some user to unpublish a Stream + * + * #### Events dispatched + * + * The behavior is the same as when some user calls [[Session.unpublish]], but `reason` property in all events will be `"forceUnpublishByUser"`. + * + * The local [[Session]] object will dispatch a `streamDestroyed` event with property `reason` set to `"forceUnpublishByUser"` + * + * The remote [[Session]] object of every other participant will dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"` + * + * The [[Publisher]] object of the affected participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"` + * + * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the remote Stream has been successfully unpublished from the session and rejected with an Error object if not + */ + forceUnpublish(stream: Stream): Promise { + return new Promise((resolve, reject) => { + console.info('Forcing unpublish for stream ' + stream.streamId); + this.openvidu.sendRequest( + 'forceUnpublish', + { streamId: stream.streamId }, + (error, response) => { + if (error) { + console.error('Error forcing unpublish for Stream ' + stream.streamId, error); + if (error.code === 401) { + reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to force an unpublishing")); + } else { + reject(error); + } + } else { + console.info('Forcing unpublish correctly for Stream ' + stream.streamId); + resolve(); + } + } + ); + }); + } + + /** * Sends one signal. `signal` object has the following optional properties: * ```json @@ -663,27 +745,6 @@ export class Session implements EventDispatcher { if (!!this.sessionId && !this.connection.disposed) { this.leave(true, msg.reason); } - } else { - // Other user has been evicted from the session - this.getRemoteConnection(msg.connectionId, 'Remote connection ' + msg.connectionId + " unknown when 'onParticipantEvicted'. " + - 'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections))) - - .then(connection => { - if (!!connection.stream) { - const stream = connection.stream; - - const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, msg.reason); - this.ee.emitEvent('streamDestroyed', [streamEvent]); - streamEvent.callDefaultBehavior(); - - delete this.remoteStreamsCreated[stream.streamId]; - } - delete this.remoteConnections[connection.connectionId]; - this.ee.emitEvent('connectionDestroyed', [new ConnectionEvent(false, this, 'connectionDestroyed', connection, msg.reason)]); - }) - .catch(openViduError => { - console.error(openViduError); - }); } } @@ -904,7 +965,9 @@ export class Session implements EventDispatcher { // Initialize capabilities object with the role this.capabilities = { subscribe: true, - publish: this.openvidu.role !== 'SUBSCRIBER' + publish: this.openvidu.role !== 'SUBSCRIBER', + forceUnpublish: this.openvidu.role === 'MODERATOR', + forceDisconnect: this.openvidu.role === 'MODERATOR' }; // Initialize local Connection object with values returned by openvidu-server diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index 58cf4bf3..1a46bf85 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -26,6 +26,7 @@ import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpea import EventEmitter = require('wolfy87-eventemitter'); import hark = require('hark'); +import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; /** @@ -118,6 +119,10 @@ export class Stream { * @hidden */ isLocalStreamPublished = false; + /** + * @hidden + */ + publishedOnce = false; /** * @hidden */ @@ -465,12 +470,17 @@ export class Stream { videoDimensions: JSON.stringify(this.videoDimensions) }, (error, response) => { if (error) { - reject('Error on publishVideo: ' + JSON.stringify(error)); + if (error.code === 401) { + reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish")); + } else { + reject('Error on publishVideo: ' + JSON.stringify(error)); + } } else { this.webRtcPeer.processAnswer(response.sdpAnswer) .then(() => { this.streamId = response.id; this.isLocalStreamPublished = true; + this.publishedOnce = true; if (this.displayMyRemote()) { this.remotePeerSuccessfullyEstablished(); } diff --git a/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts b/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts index 26f5a1e5..5c64469b 100644 --- a/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts +++ b/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts @@ -82,7 +82,8 @@ export enum OpenViduErrorName { PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR', /** - * _Not in use yet_ + * The client tried to call a method without the required permissions. This can occur for methods [[Session.publish]], + * [[Session.forceUnpublish]] and [[Session.forceDisconnect]] */ OPENVIDU_PERMISSION_DENIED = 'OPENVIDU_PERMISSION_DENIED', diff --git a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts index bc0040c6..28d8c868 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts @@ -35,6 +35,8 @@ export class ConnectionEvent extends Event { /** * For 'connectionDestroyed' event: * - "disconnect": the remote user has called `Session.disconnect()` + * - "forceDisconnectByUser": the remote user has been evicted from the Session by other user calling `Session.forceDisconnect()` + * - "forceDisconnectByServer": the remote user has been evicted from the Session by the application * - "networkDisconnect": the remote user network connection has dropped * * For 'connectionCreated' empty string diff --git a/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts index 6b1ff6d8..1d88bb11 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts @@ -34,8 +34,8 @@ export class RecordingEvent extends Event { /** * The recording name you supplied to openvidu-server. For example, to name your recording file MY_RECORDING: * - With **API REST**: POST to `/api/recordings/start` passing JSON body `{"session":"sessionId","name":"MY_RECORDING"}` - * - With **openvidu-java-client**: `OpenVidu.startRecording(sessionId, MY_RECORDING)` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name(MY_RECORDING).build())` - * - With **openvidu-node-client**: `OpenVidu.startRecording(sessionId, MY_RECORDING)` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name(MY_RECORDING).build())` + * - With **openvidu-java-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name("MY_RECORDING").build())` + * - With **openvidu-node-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, {name: "MY_RECORDING"})` * * If no name is supplied, this property will be undefined and the recorded file will be named after property [[id]] */ diff --git a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts index 49933af6..0887f7ca 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts @@ -26,6 +26,9 @@ export class SessionDisconnectedEvent extends Event { /** * - "disconnect": you have called `Session.disconnect()` + * - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()` + * - "forceDisconnectByServer": you have been evicted from the Session by the application + * - "sessionClosedByServer": the Session has been closed by the application * - "networkDisconnect": your network connection has dropped */ reason: string; diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts index 90b6186b..a0408aa6 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts @@ -37,6 +37,11 @@ export class StreamEvent extends Event { * For 'streamDestroyed' event: * - "unpublish": method `Session.unpublish()` has been called * - "disconnect": method `Session.disconnect()` has been called + * - "forceUnpublishByUser": some user has called `Session.forceUnpublish()` over the Stream + * - "forceDisconnectByUser": some user has called `Session.forceDisconnect()` over the Stream + * - "forceUnpublishByServer": the user's stream has been unpublished from the Session by the application + * - "forceDisconnectByServer": the user has been evicted from the Session by the application + * - "sessionClosedByServer": the Session has been closed by the application * - "networkDisconnect": the user's network connection has dropped * * For 'streamCreated' empty string diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/Capabilities.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/Capabilities.ts index e55c805c..1112735e 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/Capabilities.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/Capabilities.ts @@ -20,6 +20,16 @@ */ export interface Capabilities { + /** + * `true` if the client can call [[Session.forceDisconnect]], `false` if not + */ + forceDisconnect: boolean; + + /** + * `true` if the client can call [[Session.forceUnpublish]], `false` if not + */ + forceUnpublish: boolean; + /** * `true` if the client can call [[Session.publish]], `false` if not */ diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts index adbf93e8..3e936471 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts @@ -35,22 +35,22 @@ export interface StreamManagerVideo { /** * The DOM HTMLElement assigned as target element when creating a video for the StreamManager. This property is defined when: * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter. - * - [[SessionManager.createVideoElement]] has been called. + * - [[StreamManager.createVideoElement]] has been called. * * This property is undefined when: * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter. - * - [[SessionManager.addVideoElement]] has been called. + * - [[StreamManager.addVideoElement]] has been called. */ targetElement?: HTMLElement; /** * How the DOM video element should be inserted with respect to `targetElement`. This property is defined when: * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter. - * - [[SessionManager.createVideoElement]] has been called. + * - [[StreamManager.createVideoElement]] has been called. * * This property is undefined when: * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter. - * - [[SessionManager.addVideoElement]] has been called. + * - [[StreamManager.addVideoElement]] has been called. */ insertMode?: VideoInsertMode;