diff --git a/openvidu-browser/config/typedoc/typedoc.js b/openvidu-browser/config/typedoc/typedoc.js index 457b6812..73fe02e3 100644 --- a/openvidu-browser/config/typedoc/typedoc.js +++ b/openvidu-browser/config/typedoc/typedoc.js @@ -13,8 +13,7 @@ module.exports = { exclude: [ "**/OpenViduInternal/Interfaces/Private/**", "**/OpenViduInternal/WebRtcStats/WebRtcStats.ts", - "**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts", - "**/OpenViduInternal/Events/StreamPropertyChangedEvent.ts" + "**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts" ], excludeExternals: true, excludePrivate: true, diff --git a/openvidu-browser/src/OpenVidu/Connection.ts b/openvidu-browser/src/OpenVidu/Connection.ts index d635d4e4..ab3903e0 100644 --- a/openvidu-browser/src/OpenVidu/Connection.ts +++ b/openvidu-browser/src/OpenVidu/Connection.ts @@ -123,10 +123,13 @@ export class Connection { const streamOptions: InboundStreamOptions = { id: opts.id, connection: this, + hasAudio: opts.hasAudio, + hasVideo: opts.hasVideo, + audioActive: opts.audioActive, + videoActive: opts.videoActive, + typeOfVideo: opts.typeOfVideo, frameRate: opts.frameRate, - recvAudio: opts.audioActive, - recvVideo: opts.videoActive, - typeOfVideo: opts.typeOfVideo + videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined }; const stream = new Stream(this.session, streamOptions); diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index 988b0243..3ca53bcb 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -19,6 +19,7 @@ import { LocalRecorder } from './LocalRecorder'; import { Publisher } from './Publisher'; import { Session } from './Session'; import { Stream } from './Stream'; +import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { Device } from '../OpenViduInternal/Interfaces/Public/Device'; import { OpenViduAdvancedConfiguration } from '../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration'; import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties'; @@ -44,6 +45,10 @@ export class OpenVidu { * @hidden */ session: Session; + /** + * @hidden + */ + publishers: Publisher[] = []; /** * @hidden */ @@ -71,6 +76,64 @@ export class OpenVidu { constructor() { console.info("'OpenVidu' initialized"); + + if (platform.name!!.toLowerCase().indexOf('mobile') !== -1) { + // Listen to orientationchange only on mobile browsers + (window).onorientationchange = () => { + this.publishers.forEach(publisher => { + if (!!publisher.stream && !!publisher.stream.hasVideo && !!publisher.stream.streamManager.videos[0]) { + + let attempts = 0; + + const oldWidth = publisher.stream.videoDimensions.width; + const oldHeight = publisher.stream.videoDimensions.height; + // New resolution got from different places for Chrome and Firefox. Chrome needs a videoWidth and videoHeight of a videoElement. + // Firefox needs getSettings from the videoTrack + let firefoxSettings = publisher.stream.getMediaStream().getVideoTracks()[0].getSettings(); + let newWidth = (platform.name!!.toLowerCase().indexOf('firefox') !== -1) ? firefoxSettings.width : publisher.videoReference.videoWidth; + let newHeight = (platform.name!!.toLowerCase().indexOf('firefox') !== -1) ? firefoxSettings.height : publisher.videoReference.videoHeight; + + const repeatUntilChange = setInterval(() => { + firefoxSettings = publisher.stream.getMediaStream().getVideoTracks()[0].getSettings(); + newWidth = (platform.name!!.toLowerCase().indexOf('firefox') !== -1) ? firefoxSettings.width : publisher.videoReference.videoWidth; + newHeight = (platform.name!!.toLowerCase().indexOf('firefox') !== -1) ? firefoxSettings.height : publisher.videoReference.videoHeight; + sendStreamPropertyChangedEvent(oldWidth, oldHeight, newWidth, newHeight); + }, 100); + + const sendStreamPropertyChangedEvent = (oldWidth, oldHeight, newWidth, newHeight) => { + attempts++; + if (attempts > 4) { + clearTimeout(repeatUntilChange); + } + if (newWidth !== oldWidth || newHeight !== oldHeight) { + publisher.stream.videoDimensions = { + width: newWidth || 0, + height: newHeight || 0 + }; + const newValue = JSON.stringify(publisher.stream.videoDimensions); + this.sendRequest( + 'streamPropertyChanged', + { + streamId: publisher.stream.streamId, + property: 'videoDimensions', + newValue, + reason: 'deviceRotated' + }, + (error, response) => { + if (error) { + console.error("Error sending 'streamPropertyChanged' event", error); + } else { + this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, 'videoDimensions', newValue, { width: oldWidth, height: oldHeight }, 'deviceRotated')]); + publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, 'videoDimensions', newValue, { width: oldWidth, height: oldHeight }, 'deviceRotated')]); + } + }); + clearTimeout(repeatUntilChange); + } + }; + } + }); + }; + } } @@ -163,6 +226,7 @@ export class OpenVidu { publisher.emitEvent('accessDenied', []); }); + this.publishers.push(publisher); return publisher; } @@ -510,6 +574,7 @@ export class OpenVidu { recordingStarted: this.session.onRecordingStarted.bind(this.session), recordingStopped: this.session.onRecordingStopped.bind(this.session), sendMessage: this.session.onNewMessage.bind(this.session), + streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session), iceCandidate: this.session.recvIceCandidate.bind(this.session), mediaError: this.session.onMediaError.bind(this.session) } diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index a311c6d6..37c5d161 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -23,10 +23,13 @@ import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDisp import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties'; import { Event } from '../OpenViduInternal/Events/Event'; import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent'; +import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; +import platform = require('platform'); + /** * Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method @@ -52,47 +55,121 @@ export class Publisher extends StreamManager { private properties: PublisherProperties; private permissionDialogTimeout: NodeJS.Timer; + /** + * hidden + */ + openvidu: OpenVidu; /** * @hidden */ - constructor(targEl: string | HTMLElement, properties: PublisherProperties, private openvidu: OpenVidu) { + videoReference: HTMLVideoElement; + /** + * @hidden + */ + screenShareResizeInterval: NodeJS.Timer; + + /** + * @hidden + */ + constructor(targEl: string | HTMLElement, properties: PublisherProperties, openvidu: OpenVidu) { super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl); this.properties = properties; + this.openvidu = openvidu; this.stream.ee.on('local-stream-destroyed-by-disconnect', (reason: string) => { const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason); - this.ee.emitEvent('streamDestroyed', [streamEvent]); - streamEvent.callDefaultBehaviour(); + this.emitEvent('streamDestroyed', [streamEvent]); + streamEvent.callDefaultBehavior(); }); } /** * Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same value will have no effect + * + * #### Events dispatched + * + * The [[Session]] object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"` + * The [[Publisher]] object of the local participant will also dispatch the exact same event + * + * The [[Session]] object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"` + * The respective [[Subscriber]] object of every other participant receiving this Publisher's stream will also dispatch the exact same event + * + * See [[StreamPropertyChangedEvent]] to learn more. + * * @param value `true` to publish the audio stream, `false` to unpublish it */ publishAudio(value: boolean): void { - this.stream.getMediaStream().getAudioTracks().forEach((track) => { - track.enabled = value; - }); - console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its audio stream'); + if (this.stream.audioActive !== value) { + this.stream.getMediaStream().getAudioTracks().forEach((track) => { + track.enabled = value; + }); + this.session.openvidu.sendRequest( + 'streamPropertyChanged', + { + streamId: this.stream.streamId, + property: 'audioActive', + newValue: value, + reason: 'publishAudio' + }, + (error, response) => { + if (error) { + console.error("Error sending 'streamPropertyChanged' event", error); + } else { + this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', value, !value, 'publishAudio')]); + this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'audioActive', value, !value, 'publishAudio')]); + } + }); + this.stream.audioActive = value; + console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its audio stream'); + } } /** * Publish or unpublish the video stream (if available). Calling this method twice in a row passing same value will have no effect + * + * #### Events dispatched + * + * The [[Session]] object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"` + * The [[Publisher]] object of the local participant will also dispatch the exact same event + * + * The [[Session]] object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"` + * The respective [[Subscriber]] object of every other participant receiving this Publisher's stream will also dispatch the exact same event + * + * See [[StreamPropertyChangedEvent]] to learn more. + * * @param value `true` to publish the video stream, `false` to unpublish it */ publishVideo(value: boolean): void { - this.stream.getMediaStream().getVideoTracks().forEach((track) => { - track.enabled = value; - }); - console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its video stream'); + if (this.stream.videoActive !== value) { + this.stream.getMediaStream().getVideoTracks().forEach((track) => { + track.enabled = value; + }); + this.session.openvidu.sendRequest( + 'streamPropertyChanged', + { + streamId: this.stream.streamId, + property: 'videoActive', + newValue: value, + reason: 'publishVideo' + }, + (error, response) => { + if (error) { + console.error("Error sending 'streamPropertyChanged' event", error); + } else { + this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', value, !value, 'publishVideo')]); + this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', value, !value, 'publishVideo')]); + } + }); + this.stream.videoActive = value; + console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its video stream'); + } } /** - * Call this method before [[Session.publish]] 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 { value = (value !== undefined) ? value : true; @@ -108,10 +185,10 @@ export class Publisher extends StreamManager { super.on(type, handler); if (type === 'streamCreated') { if (!!this.stream && this.stream.isLocalStreamPublished) { - this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); + this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); } else { this.stream.ee.on('stream-created-by-publisher', () => { - this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); + this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); }); } } @@ -121,17 +198,17 @@ export class Publisher extends StreamManager { this.videos[0].video.paused === false && this.videos[0].video.ended === false && this.videos[0].video.readyState === 4) { - this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); + this.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); } } if (type === 'accessAllowed') { if (this.accessAllowed) { - this.ee.emitEvent('accessAllowed'); + this.emitEvent('accessAllowed', []); } } if (type === 'accessDenied') { if (this.accessDenied) { - this.ee.emitEvent('accessDenied'); + this.emitEvent('accessDenied', []); } } return this; @@ -145,10 +222,10 @@ export class Publisher extends StreamManager { super.once(type, handler); if (type === 'streamCreated') { if (!!this.stream && this.stream.isLocalStreamPublished) { - this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); + this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); } else { this.stream.ee.once('stream-created-by-publisher', () => { - this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); + this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); }); } } @@ -158,17 +235,17 @@ export class Publisher extends StreamManager { this.videos[0].video.paused === false && this.videos[0].video.ended === false && this.videos[0].video.readyState === 4) { - this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); + this.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); } } if (type === 'accessAllowed') { if (this.accessAllowed) { - this.ee.emitEvent('accessAllowed'); + this.emitEvent('accessAllowed', []); } } if (type === 'accessDenied') { if (this.accessDenied) { - this.ee.emitEvent('accessDenied'); + this.emitEvent('accessDenied', []); } } return this; @@ -217,14 +294,76 @@ export class Publisher extends StreamManager { // avoid early 'streamPlaying' event this.stream.updateMediaStreamInVideos(); } - this.stream.isLocalStreamReadyToPublish = true; - this.stream.ee.emitEvent('stream-ready-to-publish', []); if (!!this.firstVideoElement) { this.createVideoElement(this.firstVideoElement.targetElement, this.properties.insertMode); } delete this.firstVideoElement; + if (!this.stream.isSendScreen() && !!mediaStream.getVideoTracks()[0]) { + // With no screen share, video dimension can be set directly from MediaStream (getSettings) + // Orientation must be checked for mobile devices (width and height are reversed) + const { width, height } = mediaStream.getVideoTracks()[0].getSettings(); + + if (platform.name!!.toLowerCase().indexOf('mobile') !== -1 && (window.innerHeight > window.innerWidth)) { + // Mobile portrait mode + this.stream.videoDimensions = { + width: height || 0, + height: width || 0 + }; + } else { + this.stream.videoDimensions = { + width: width || 0, + height: height || 0 + }; + } + + this.stream.isLocalStreamReadyToPublish = true; + this.stream.ee.emitEvent('stream-ready-to-publish', []); + } else { + // With screen share, video dimension must be got from a video element (onloadedmetadata event) + this.videoReference = document.createElement('video'); + this.videoReference.srcObject = mediaStream; + this.videoReference.onloadedmetadata = () => { + this.stream.videoDimensions = { + width: this.videoReference.videoWidth, + height: this.videoReference.videoHeight + }; + this.screenShareResizeInterval = setInterval(() => { + const firefoxSettings = mediaStream.getVideoTracks()[0].getSettings(); + const newWidth = (platform.name === 'Chrome') ? this.videoReference.videoWidth : firefoxSettings.width; + const newHeight = (platform.name === 'Chrome') ? this.videoReference.videoHeight : firefoxSettings.height; + if (this.stream.isLocalStreamPublished && + (newWidth !== this.stream.videoDimensions.width || + newHeight !== this.stream.videoDimensions.height)) { + const oldValue = { width: this.stream.videoDimensions.width, height: this.stream.videoDimensions.height }; + this.stream.videoDimensions = { + width: newWidth || 0, + height: newHeight || 0 + }; + const newValue = JSON.stringify(this.stream.videoDimensions); + this.session.openvidu.sendRequest( + 'streamPropertyChanged', + { + streamId: this.stream.streamId, + property: 'videoDimensions', + newValue, + reason: 'screenResized' + }, + (error, response) => { + if (error) { + console.error("Error sending 'streamPropertyChanged' event", error); + } else { + this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoDimensions', newValue, oldValue, 'screenResized')]); + this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoDimensions', newValue, oldValue, 'screenResized')]); + } + }); + } + }, 500); + this.stream.isLocalStreamReadyToPublish = true; + this.stream.ee.emitEvent('stream-ready-to-publish', []); + }; + } resolve(); }; @@ -371,13 +510,6 @@ export class Publisher extends StreamManager { this.stream.session = session; } - /** - * @hidden - */ - emitEvent(type: string, eventArray: any[]): void { - this.ee.emitEvent(type, eventArray); - } - /** * @hidden */ @@ -392,7 +524,7 @@ export class Publisher extends StreamManager { private setPermissionDialogTimer(waitTime: number): void { this.permissionDialogTimeout = setTimeout(() => { - this.ee.emitEvent('accessDialogOpened', []); + this.emitEvent('accessDialogOpened', []); }, waitTime); } @@ -400,7 +532,7 @@ export class Publisher extends StreamManager { clearTimeout(this.permissionDialogTimeout); if ((Date.now() - startTime) > waitTime) { // Permission dialog was shown and now is closed - this.ee.emitEvent('accessDialogClosed', []); + this.emitEvent('accessDialogClosed', []); } } diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index 5e2dd8d1..33d13959 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -33,6 +33,7 @@ import { RecordingEvent } from '../OpenViduInternal/Events/RecordingEvent'; import { SessionDisconnectedEvent } from '../OpenViduInternal/Events/SessionDisconnectedEvent'; import { SignalEvent } from '../OpenViduInternal/Events/SignalEvent'; import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent'; +import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; @@ -156,20 +157,20 @@ export class Session implements EventDispatcher { * This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to each Subscriber (only those [created by OpenVidu Browser](/docs/how-do-i/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, each Subscriber object will dispatch a `videoElementDestroyed` event. - * Call `event.preventDefault()` uppon event `sessionDisconnected` to avoid this behaviour and take care of disposing and cleaning all the Subscriber objects yourself. + * Call `event.preventDefault()` upon event `sessionDisconnected` to avoid this behavior and take care of disposing and cleaning all the Subscriber objects yourself. * See [[SessionDisconnectedEvent]] and [[VideoElementEvent]] to learn more to learn more. * * The [[Publisher]] object of the local participant will dispatch a `streamDestroyed` event if there is a [[Publisher]] object publishing to the session. * This event will automatically stop all media tracks and delete any HTML video element associated to it (only those [created by OpenVidu Browser](/docs/how-do-i/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, the Publisher object will dispatch a `videoElementDestroyed` event. - * Call `event.preventDefault()` uppon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session (to do so it is a mandatory requirement to call `Session.unpublish()` + * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session (to do so it is a mandatory requirement to call `Session.unpublish()` * or/and `Session.disconnect()` in the previous session). See [[StreamEvent]] and [[VideoElementEvent]] to learn more. * * The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event if the disconnected participant was publishing. * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to that Subscriber (only those [created by OpenVidu Browser](/docs/how-do-i/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event. - * Call `event.preventDefault()` uppon event `streamDestroyed` to avoid this default behaviour and take care of disposing and cleaning the Subscriber object yourself. + * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object yourself. * See [[StreamEvent]] and [[VideoElementEvent]] to learn more. * * The [[Session]] object of every other participant connected to the session will dispatch a `connectionDestroyed` event in any case. See [[ConnectionEvent]] to learn more. @@ -362,13 +363,13 @@ export class Session implements EventDispatcher { * This event will automatically stop all media tracks and delete any HTML video element associated to this Publisher * (only those videos [created by OpenVidu Browser](/docs/how-do-i/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, the Publisher object will dispatch a `videoElementDestroyed` event. - * Call `event.preventDefault()` uppon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session. + * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session. * * The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event. * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) and * delete any HTML video element associated to it (only those [created by OpenVidu Browser](/docs/how-do-i/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event. - * Call `event.preventDefault()` uppon event `streamDestroyed` to avoid this default behaviour and take care of disposing and cleaning the Subscriber object on your own. + * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object on your own. * * See [[StreamEvent]] and [[VideoElementEvent]] to learn more. */ @@ -400,7 +401,7 @@ export class Session implements EventDispatcher { const streamEvent = new StreamEvent(true, publisher, 'streamDestroyed', publisher.stream, 'unpublish'); publisher.emitEvent('streamDestroyed', [streamEvent]); - streamEvent.callDefaultBehaviour(); + streamEvent.callDefaultBehavior(); } } @@ -566,7 +567,7 @@ export class Session implements EventDispatcher { const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, msg.reason); this.ee.emitEvent('streamDestroyed', [streamEvent]); - streamEvent.callDefaultBehaviour(); + streamEvent.callDefaultBehavior(); delete this.remoteStreamsCreated[stream.streamId]; } @@ -629,7 +630,7 @@ export class Session implements EventDispatcher { const streamEvent = new StreamEvent(true, this, 'streamDestroyed', connection.stream, msg.reason); this.ee.emitEvent('streamDestroyed', [streamEvent]); - streamEvent.callDefaultBehaviour(); + streamEvent.callDefaultBehavior(); // Deleting the remote stream const streamId: string = connection.stream.streamId; @@ -654,7 +655,7 @@ export class Session implements EventDispatcher { const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, 'forceDisconnect'); this.ee.emitEvent('streamDestroyed', [streamEvent]); - streamEvent.callDefaultBehaviour(); + streamEvent.callDefaultBehavior(); delete this.remoteStreamsCreated[stream.streamId]; } @@ -686,6 +687,46 @@ export class Session implements EventDispatcher { }); } + /** + * @hidden + */ + onStreamPropertyChanged(msg): void { + this.getRemoteConnection(msg.connectionId, 'Remote connection ' + msg.connectionId + " unknown when 'onStreamPropertyChanged'. " + + 'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections))) + + .then(connection => { + if (!!connection.stream && connection.stream.streamId === msg.streamId) { + const stream = connection.stream; + let oldValue; + switch (msg.property) { + case 'audioActive': + oldValue = stream.audioActive; + msg.newValue = msg.newValue === 'true'; + stream.audioActive = msg.newValue; + break; + case 'videoActive': + oldValue = stream.videoActive; + msg.newValue = msg.newValue === 'true'; + stream.videoActive = msg.newValue; + break; + case 'videoDimensions': + oldValue = stream.videoDimensions; + msg.newValue = JSON.parse(JSON.parse(msg.newValue)); + stream.videoDimensions = msg.newValue; + break; + } + + this.ee.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, stream, msg.property, msg.newValue, oldValue, msg.reason)]); + stream.streamManager.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(stream.streamManager, stream, msg.property, msg.newValue, oldValue, msg.reason)]); + } else { + console.error("No stream with streamId '" + msg.streamId + "' found for connection '" + msg.connectionId + "' on 'streamPropertyChanged' event"); + } + }) + .catch(openViduError => { + console.error(openViduError); + }); + } + /** * @hidden */ @@ -818,7 +859,7 @@ export class Session implements EventDispatcher { // Make Session object dispatch 'sessionDisconnected' event (if it is not already disposed) const sessionDisconnectEvent = new SessionDisconnectedEvent(this, reason); this.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]); - sessionDisconnectEvent.callDefaultBehaviour(); + sessionDisconnectEvent.callDefaultBehavior(); } } else { console.warn('You were not connected to the session ' + this.sessionId); @@ -952,7 +993,7 @@ export class Session implements EventDispatcher { { urls: [stunUrl] }, { urls: [turnUrl1, turnUrl2], username: turnUsername, credential: turnCredential } ]; - console.log('TURN temp credentials [' + turnUsername + ':' + turnCredential + ']') + console.log('TURN temp credentials [' + turnUsername + ':' + turnCredential + ']'); } if (!!role) { this.openvidu.role = role; diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index 2b716e85..58cf4bf3 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -56,13 +56,30 @@ export class Stream { */ hasAudio: boolean; + /** + * Whether the stream has the video track muted or unmuted. If [[hasVideo]] is false, this property is undefined. + * + * This property may change if the Publisher publishing the stream calls [[Publisher.publishVideo]]. Whenever this happens a [[StreamPropertyChangedEvent]] will be dispatched + * by the Session object as well as by the affected Subscriber/Publisher object + */ + videoActive: boolean; + + /** + * Whether the stream has the audio track muted or unmuted. If [[hasAudio]] is false, this property is undefined + * + * This property may change if the Publisher publishing the stream calls [[Publisher.publishAudio]]. Whenever this happens a [[StreamPropertyChangedEvent]] will be dispatched + * by the Session object as well as by the affected Subscriber/Publisher object + */ + audioActive: boolean; + /** * Unique identifier of the stream */ streamId: string; /** - * `"CAMERA"` or `"SCREEN"`. *undefined* if stream is audio-only + * `"CAMERA"`, `"SCREEN"` or `"CUSTOM"` (the latter when [[PublisherProperties.videoSource]] is a MediaStreamTrack when calling [[OpenVidu.initPublisher]]). + * If [[hasVideo]] is false, this property is undefined */ typeOfVideo?: string; @@ -71,6 +88,17 @@ export class Stream { */ streamManager: StreamManager; + /** + * Width and height in pixels of the encoded video stream. If [[hasVideo]] is false, this property is undefined + * + * This property may change if the Publisher that is publishing: + * - If it is a mobile device, whenever the user rotates the device. + * - If it is screen-sharing, whenever the user changes the size of the captured window. + * + * Whenever this happens a [[StreamPropertyChangedEvent]] will be dispatched by the Session object as well as by the affected Subscriber/Publisher object + */ + videoDimensions: { width: number, height: number }; + /** * @hidden */ @@ -119,26 +147,36 @@ export class Stream { // InboundStreamOptions: stream belongs to a Subscriber this.inboundStreamOpts = options; this.streamId = this.inboundStreamOpts.id; - this.hasAudio = this.inboundStreamOpts.recvAudio; - this.hasVideo = this.inboundStreamOpts.recvVideo; - this.typeOfVideo = (!this.inboundStreamOpts.typeOfVideo) ? undefined : this.inboundStreamOpts.typeOfVideo; - this.frameRate = (this.inboundStreamOpts.frameRate === -1) ? undefined : this.inboundStreamOpts.frameRate; + this.hasAudio = this.inboundStreamOpts.hasAudio; + this.hasVideo = this.inboundStreamOpts.hasVideo; + if (this.hasAudio) { + this.audioActive = this.inboundStreamOpts.audioActive; + } + if (this.hasVideo) { + this.videoActive = this.inboundStreamOpts.videoActive; + this.typeOfVideo = (!this.inboundStreamOpts.typeOfVideo) ? undefined : this.inboundStreamOpts.typeOfVideo; + this.frameRate = (this.inboundStreamOpts.frameRate === -1) ? undefined : this.inboundStreamOpts.frameRate; + this.videoDimensions = this.inboundStreamOpts.videoDimensions; + } } else { // OutboundStreamOptions: stream belongs to a Publisher this.outboundStreamOpts = options; - if (this.isSendVideo()) { - if (this.isSendScreen()) { - this.typeOfVideo = 'SCREEN'; - } else { - this.typeOfVideo = 'CAMERA'; - } - this.frameRate = this.outboundStreamOpts.publisherProperties.frameRate; - } else { - delete this.typeOfVideo; - } this.hasAudio = this.isSendAudio(); this.hasVideo = this.isSendVideo(); + + if (this.hasAudio) { + this.audioActive = !!this.outboundStreamOpts.publisherProperties.publishAudio; + } + if (this.hasVideo) { + this.videoActive = !!this.outboundStreamOpts.publisherProperties.publishVideo; + this.frameRate = this.outboundStreamOpts.publisherProperties.frameRate; + if (this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack) { + this.typeOfVideo = 'CUSTOM'; + } else { + this.typeOfVideo = this.isSendScreen() ? 'SCREEN' : 'CAMERA'; + } + } } this.ee.on('mediastream-updated', () => { @@ -410,13 +448,21 @@ export class Stream { console.debug('Sending SDP offer to publish as ' + this.streamId, sdpOfferParam); + let typeOfVideo = ''; + if (this.isSendVideo()) { + typeOfVideo = this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack ? 'CUSTOM' : (this.isSendScreen() ? 'SCREEN' : 'CAMERA'); + } + this.session.openvidu.sendRequest('publishVideo', { sdpOffer: sdpOfferParam, doLoopback: this.displayMyRemote() || false, - audioActive: this.isSendAudio(), - videoActive: this.isSendVideo(), - typeOfVideo: ((this.isSendVideo()) ? (this.isSendScreen() ? 'SCREEN' : 'CAMERA') : ''), - frameRate: !!this.frameRate ? this.frameRate : -1 + hasAudio: this.isSendAudio(), + hasVideo: this.isSendVideo(), + audioActive: this.audioActive, + videoActive: this.videoActive, + typeOfVideo, + frameRate: !!this.frameRate ? this.frameRate : -1, + videoDimensions: JSON.stringify(this.videoDimensions) }, (error, response) => { if (error) { reject('Error on publishVideo: ' + JSON.stringify(error)); @@ -426,7 +472,7 @@ export class Stream { this.streamId = response.id; this.isLocalStreamPublished = true; if (this.displayMyRemote()) { - this.remotePeerSuccesfullyEstablished(); + this.remotePeerSuccessfullyEstablished(); } this.ee.emitEvent('stream-created-by-publisher'); this.initWebRtcStats(); @@ -457,8 +503,8 @@ export class Stream { return new Promise((resolve, reject) => { const offerConstraints = { - audio: this.inboundStreamOpts.recvAudio, - video: this.inboundStreamOpts.recvVideo + audio: this.inboundStreamOpts.hasAudio, + video: this.inboundStreamOpts.hasVideo }; console.debug("'Session.subscribe(Stream)' called. Constraints of generate SDP offer", offerConstraints); @@ -480,7 +526,7 @@ export class Stream { reject(new Error('Error on recvVideoFrom: ' + JSON.stringify(error))); } else { this.webRtcPeer.processAnswer(response.sdpAnswer).then(() => { - this.remotePeerSuccesfullyEstablished(); + this.remotePeerSuccessfullyEstablished(); this.initWebRtcStats(); resolve(); }).catch(error => { @@ -501,7 +547,7 @@ export class Stream { }); } - private remotePeerSuccesfullyEstablished(): void { + private remotePeerSuccessfullyEstablished(): void { this.mediaStream = this.webRtcPeer.pc.getRemoteStreams()[0]; console.debug('Peer remote stream', this.mediaStream); diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts index c2529f57..93fcedd2 100644 --- a/openvidu-browser/src/OpenVidu/StreamManager.ts +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -139,9 +139,9 @@ export class StreamManager implements EventDispatcher { on(type: string, handler: (event: Event) => void): EventDispatcher { this.ee.on(type, event => { if (event) { - console.info("Event '" + type + "' triggered", event); + console.info("Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", event); } else { - console.info("Event '" + type + "' triggered"); + console.info("Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'"); } handler(event); }); @@ -399,6 +399,13 @@ export class StreamManager implements EventDispatcher { }); } + /** + * @hidden + */ + emitEvent(type: string, eventArray: any[]): void { + this.ee.emitEvent(type, eventArray); + } + private pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) { this.videos.push(streamManagerVideo); this.addPlayEventToFirstVideo(); diff --git a/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts b/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts index 8206b0b8..26f5a1e5 100644 --- a/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts +++ b/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts @@ -22,49 +22,49 @@ export enum OpenViduErrorName { /** * Browser is not supported by OpenVidu. - * Returned uppon unsuccessful [[Session.connect]] + * Returned upon unsuccessful [[Session.connect]] */ BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED', /** * The user hasn't granted permissions to the required input device when the browser asked for them. - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] */ DEVICE_ACCESS_DENIED = 'DEVICE_ACCESS_DENIED', /** * The user hasn't granted permissions to capture some desktop screen when the browser asked for them. - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] */ SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED', /** * Browser does not support screen sharing. - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] */ SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED', /** * Only for Chrome, there's no screen sharing extension installed - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] */ SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED', /** * Only for Chrome, the screen sharing extension is installed but is disabled - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] */ SCREEN_EXTENSION_DISABLED = 'SCREEN_EXTENSION_DISABLED', /** * No video input device found with the provided deviceId (property [[PublisherProperties.videoSource]]) - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] */ INPUT_VIDEO_DEVICE_NOT_FOUND = 'INPUT_VIDEO_DEVICE_NOT_FOUND', /** * No audio input device found with the provided deviceId (property [[PublisherProperties.audioSource]]) - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] */ INPUT_AUDIO_DEVICE_NOT_FOUND = 'INPUT_AUDIO_DEVICE_NOT_FOUND', @@ -77,7 +77,7 @@ export enum OpenViduErrorName { /** * Some media property of [[PublisherProperties]] such as `frameRate` or `resolution` is not supported * by the input devices (whenever it is possible they are automatically adjusted to the most similar value). - * Returned uppon unsuccessful [[OpenVidu.initPublisher]] + * Returned upon unsuccessful [[OpenVidu.initPublisher]] */ PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR', diff --git a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts index 45722696..bc0040c6 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts @@ -54,6 +54,6 @@ export class ConnectionEvent extends Event { * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/Event.ts b/openvidu-browser/src/OpenViduInternal/Events/Event.ts index a69228ea..0645278f 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/Event.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/Event.ts @@ -21,7 +21,7 @@ import { Session } from '../../OpenVidu/Session'; export abstract class Event { /** - * Whether the event has a default behaviour that may be prevented by calling [[Event.preventDefault]] + * Whether the event has a default behavior that may be prevented by calling [[Event.preventDefault]] */ cancelable: boolean; @@ -54,7 +54,7 @@ export abstract class Event { } /** - * Prevents the default behaviour of the event. The following events have a default behaviour: + * Prevents the default behavior of the event. The following events have a default behavior: * * - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to each Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or @@ -69,10 +69,10 @@ export abstract class Event { */ preventDefault() { // tslint:disable-next-line:no-empty - this.callDefaultBehaviour = () => { }; + this.callDefaultBehavior = () => { }; this.hasBeenPrevented = true; } - protected abstract callDefaultBehaviour(); + protected abstract callDefaultBehavior(); } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/PublisherSpeakingEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/PublisherSpeakingEvent.ts index a8f1e654..866df4d9 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/PublisherSpeakingEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/PublisherSpeakingEvent.ts @@ -57,6 +57,6 @@ export class PublisherSpeakingEvent extends Event { * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts index 9d42f4fe..6b1ff6d8 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts @@ -56,6 +56,6 @@ export class RecordingEvent extends Event { * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts index 75aedc65..49933af6 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts @@ -41,9 +41,9 @@ export class SessionDisconnectedEvent extends Event { /** * @hidden */ - callDefaultBehaviour() { + callDefaultBehavior() { - console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Session'"); + console.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'"); const session = this.target; diff --git a/openvidu-browser/src/OpenViduInternal/Events/SignalEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SignalEvent.ts index 9a44312d..7b3d3f87 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/SignalEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/SignalEvent.ts @@ -60,6 +60,6 @@ export class SignalEvent extends Event { * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts index 0f838dbe..90b6186b 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts @@ -55,17 +55,27 @@ export class StreamEvent extends Event { /** * @hidden */ - callDefaultBehaviour() { + callDefaultBehavior() { if (this.type === 'streamDestroyed') { if (this.target instanceof Session) { // Remote Stream - console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Session'"); + console.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'"); this.stream.disposeWebRtcPeer(); } else if (this.target instanceof Publisher) { // Local Stream - console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Publisher'"); + console.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Publisher'"); + clearInterval((this.target).screenShareResizeInterval); this.stream.isLocalStreamReadyToPublish = false; + + // Delete Publisher object from OpenVidu publishers array + const openviduPublishers = (this.target).openvidu.publishers; + for (let i = 0; i < openviduPublishers.length; i++) { + if (openviduPublishers[i] === (this.target)) { + openviduPublishers.splice(i, 1); + break; + } + } } // Dispose the MediaStream local object diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts index b5cf4275..4dd54a7c 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts @@ -35,6 +35,6 @@ export class StreamManagerEvent extends Event { * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts index 606a1fc6..a0cc59eb 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts @@ -18,47 +18,59 @@ import { Event } from './Event'; import { Session } from '../../OpenVidu/Session'; import { Stream } from '../../OpenVidu/Stream'; +import { StreamManager } from '../../OpenVidu/StreamManager'; /** - * Defines event `streamPropertyChangedEvent` dispatched by [[Session]] + * Defines event `streamPropertyChanged` dispatched by [[Session]] as well as by [[StreamManager]] ([[Publisher]] and [[Subscriber]]). + * This event is fired when any remote stream (owned by a Subscriber) or local stream (owned by a Publisher) undergoes + * any change in any of its mutable properties (see [[changedProperty]]). */ export class StreamPropertyChangedEvent extends Event { /** - * The Stream whose property has changed + * The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property [[Stream.connection]] */ stream: Stream; /** - * The property of the stream that changed. This value is either `"hasAudio"`, `"hasVideo"` or `"videoDimensions"` + * The property of the stream that changed. This value is either `"videoActive"`, `"audioActive"` or `"videoDimensions"` */ changedProperty: string; /** - * New value of the property (before change) + * Cause of the change on the stream's property: + * - For `videoActive`: `"publishVideo"` + * - For `audioActive`: `"publishAudio"` + * - For `videoDimensions`: `"deviceRotated"` or `"screenResized"` + */ + reason: string; + + /** + * New value of the property (after change, current value) */ newValue: Object; /** - * Previous value of the property (after change) + * Previous value of the property (before change) */ oldValue: Object; /** * @hidden */ - constructor(target: Session, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object) { - super(false, target, 'streamPropertyChangedEvent'); + constructor(target: Session | StreamManager, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object, reason: string) { + super(false, target, 'streamPropertyChanged'); this.stream = stream; this.changedProperty = changedProperty; this.newValue = newValue; this.oldValue = oldValue; + this.reason = reason; } /** * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/VideoElementEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/VideoElementEvent.ts index ca1077da..fe0f6f03 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/VideoElementEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/VideoElementEvent.ts @@ -44,6 +44,6 @@ export class VideoElementEvent extends Event { * @hidden */ // tslint:disable-next-line:no-empty - callDefaultBehaviour() { } + callDefaultBehavior() { } } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/InboundStreamOptions.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/InboundStreamOptions.ts index a80d2a8d..55da3a0b 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/InboundStreamOptions.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/InboundStreamOptions.ts @@ -20,8 +20,11 @@ import { Connection } from '../../../OpenVidu/Connection'; export interface InboundStreamOptions { id: string; connection: Connection; - frameRate: number; - recvAudio: boolean; - recvVideo: boolean; + hasAudio: boolean; + hasVideo: boolean; + audioActive: boolean; + videoActive: boolean; typeOfVideo: string; + frameRate: number; + videoDimensions: { width: number, height: number }; } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/StreamOptionsServer.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/StreamOptionsServer.ts index 1a29574a..8da080a1 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/StreamOptionsServer.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/StreamOptionsServer.ts @@ -17,8 +17,11 @@ export interface StreamOptionsServer { id: string; + hasAudio: boolean; + hasVideo: boolean; audioActive: boolean; - frameRate: number; videoActive: boolean; typeOfVideo: string; + frameRate: number; + videoDimensions: string; } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/PublisherProperties.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/PublisherProperties.ts index 1e38228f..2ee8cf52 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/PublisherProperties.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/PublisherProperties.ts @@ -32,7 +32,9 @@ export interface PublisherProperties { audioSource?: string | MediaStreamTrack | boolean; /** - * Desired framerate of the video in frames per second + * Desired framerate of the video in frames per second. + * Limiting the framerate has always effect on browsers Chrome and Opera. Firefox requires that the input device explicitly supports the desired framerate. + * @default undefined */ frameRate?: number; diff --git a/openvidu-browser/src/index.ts b/openvidu-browser/src/index.ts index 9e8c2591..a50c8dcf 100644 --- a/openvidu-browser/src/index.ts +++ b/openvidu-browser/src/index.ts @@ -20,6 +20,7 @@ export { SignalEvent } from './OpenViduInternal/Events/SignalEvent'; export { StreamEvent } from './OpenViduInternal/Events/StreamEvent'; export { StreamManagerEvent } from './OpenViduInternal/Events/StreamManagerEvent'; export { VideoElementEvent } from './OpenViduInternal/Events/VideoElementEvent'; +export { StreamPropertyChangedEvent } from './OpenViduInternal/Events/StreamPropertyChangedEvent'; export { Device } from './OpenViduInternal/Interfaces/Public/Device'; export { EventDispatcher } from './OpenViduInternal/Interfaces/Public/EventDispatcher';