openvidu-browser: StreamPropertyChangedEvent

pull/88/merge
pabloFuente 2018-07-03 15:35:08 +02:00
parent a20b878d7a
commit 15d879992a
22 changed files with 436 additions and 112 deletions

View File

@ -13,8 +13,7 @@ module.exports = {
exclude: [ exclude: [
"**/OpenViduInternal/Interfaces/Private/**", "**/OpenViduInternal/Interfaces/Private/**",
"**/OpenViduInternal/WebRtcStats/WebRtcStats.ts", "**/OpenViduInternal/WebRtcStats/WebRtcStats.ts",
"**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts", "**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts"
"**/OpenViduInternal/Events/StreamPropertyChangedEvent.ts"
], ],
excludeExternals: true, excludeExternals: true,
excludePrivate: true, excludePrivate: true,

View File

@ -123,10 +123,13 @@ export class Connection {
const streamOptions: InboundStreamOptions = { const streamOptions: InboundStreamOptions = {
id: opts.id, id: opts.id,
connection: this, connection: this,
hasAudio: opts.hasAudio,
hasVideo: opts.hasVideo,
audioActive: opts.audioActive,
videoActive: opts.videoActive,
typeOfVideo: opts.typeOfVideo,
frameRate: opts.frameRate, frameRate: opts.frameRate,
recvAudio: opts.audioActive, videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined
recvVideo: opts.videoActive,
typeOfVideo: opts.typeOfVideo
}; };
const stream = new Stream(this.session, streamOptions); const stream = new Stream(this.session, streamOptions);

View File

@ -19,6 +19,7 @@ import { LocalRecorder } from './LocalRecorder';
import { Publisher } from './Publisher'; import { Publisher } from './Publisher';
import { Session } from './Session'; import { Session } from './Session';
import { Stream } from './Stream'; import { Stream } from './Stream';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { Device } from '../OpenViduInternal/Interfaces/Public/Device'; import { Device } from '../OpenViduInternal/Interfaces/Public/Device';
import { OpenViduAdvancedConfiguration } from '../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration'; import { OpenViduAdvancedConfiguration } from '../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties'; import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
@ -44,6 +45,10 @@ export class OpenVidu {
* @hidden * @hidden
*/ */
session: Session; session: Session;
/**
* @hidden
*/
publishers: Publisher[] = [];
/** /**
* @hidden * @hidden
*/ */
@ -71,6 +76,64 @@ export class OpenVidu {
constructor() { constructor() {
console.info("'OpenVidu' initialized"); console.info("'OpenVidu' initialized");
if (platform.name!!.toLowerCase().indexOf('mobile') !== -1) {
// Listen to orientationchange only on mobile browsers
(<any>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', []); publisher.emitEvent('accessDenied', []);
}); });
this.publishers.push(publisher);
return publisher; return publisher;
} }
@ -510,6 +574,7 @@ export class OpenVidu {
recordingStarted: this.session.onRecordingStarted.bind(this.session), recordingStarted: this.session.onRecordingStarted.bind(this.session),
recordingStopped: this.session.onRecordingStopped.bind(this.session), recordingStopped: this.session.onRecordingStopped.bind(this.session),
sendMessage: this.session.onNewMessage.bind(this.session), sendMessage: this.session.onNewMessage.bind(this.session),
streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session),
iceCandidate: this.session.recvIceCandidate.bind(this.session), iceCandidate: this.session.recvIceCandidate.bind(this.session),
mediaError: this.session.onMediaError.bind(this.session) mediaError: this.session.onMediaError.bind(this.session)
} }

View File

@ -23,10 +23,13 @@ import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDisp
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties'; import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
import { Event } from '../OpenViduInternal/Events/Event'; import { Event } from '../OpenViduInternal/Events/Event';
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent'; import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'; import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; 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 * 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 properties: PublisherProperties;
private permissionDialogTimeout: NodeJS.Timer; private permissionDialogTimeout: NodeJS.Timer;
/**
* hidden
*/
openvidu: OpenVidu;
/** /**
* @hidden * @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); super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
this.properties = properties; this.properties = properties;
this.openvidu = openvidu;
this.stream.ee.on('local-stream-destroyed-by-disconnect', (reason: string) => { this.stream.ee.on('local-stream-destroyed-by-disconnect', (reason: string) => {
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason); const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
this.ee.emitEvent('streamDestroyed', [streamEvent]); this.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehaviour(); streamEvent.callDefaultBehavior();
}); });
} }
/** /**
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same value will have no effect * 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 * @param value `true` to publish the audio stream, `false` to unpublish it
*/ */
publishAudio(value: boolean): void { publishAudio(value: boolean): void {
this.stream.getMediaStream().getAudioTracks().forEach((track) => { if (this.stream.audioActive !== value) {
track.enabled = value; this.stream.getMediaStream().getAudioTracks().forEach((track) => {
}); track.enabled = value;
console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its audio stream'); });
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 * 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 * @param value `true` to publish the video stream, `false` to unpublish it
*/ */
publishVideo(value: boolean): void { publishVideo(value: boolean): void {
this.stream.getMediaStream().getVideoTracks().forEach((track) => { if (this.stream.videoActive !== value) {
track.enabled = value; this.stream.getMediaStream().getVideoTracks().forEach((track) => {
}); track.enabled = value;
console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its video stream'); });
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 { subscribeToRemote(value?: boolean): void {
value = (value !== undefined) ? value : true; value = (value !== undefined) ? value : true;
@ -108,10 +185,10 @@ export class Publisher extends StreamManager {
super.on(type, handler); super.on(type, handler);
if (type === 'streamCreated') { if (type === 'streamCreated') {
if (!!this.stream && this.stream.isLocalStreamPublished) { 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 { } else {
this.stream.ee.on('stream-created-by-publisher', () => { 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.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('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); this.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
} }
} }
if (type === 'accessAllowed') { if (type === 'accessAllowed') {
if (this.accessAllowed) { if (this.accessAllowed) {
this.ee.emitEvent('accessAllowed'); this.emitEvent('accessAllowed', []);
} }
} }
if (type === 'accessDenied') { if (type === 'accessDenied') {
if (this.accessDenied) { if (this.accessDenied) {
this.ee.emitEvent('accessDenied'); this.emitEvent('accessDenied', []);
} }
} }
return this; return this;
@ -145,10 +222,10 @@ export class Publisher extends StreamManager {
super.once(type, handler); super.once(type, handler);
if (type === 'streamCreated') { if (type === 'streamCreated') {
if (!!this.stream && this.stream.isLocalStreamPublished) { 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 { } else {
this.stream.ee.once('stream-created-by-publisher', () => { 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.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('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); this.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
} }
} }
if (type === 'accessAllowed') { if (type === 'accessAllowed') {
if (this.accessAllowed) { if (this.accessAllowed) {
this.ee.emitEvent('accessAllowed'); this.emitEvent('accessAllowed', []);
} }
} }
if (type === 'accessDenied') { if (type === 'accessDenied') {
if (this.accessDenied) { if (this.accessDenied) {
this.ee.emitEvent('accessDenied'); this.emitEvent('accessDenied', []);
} }
} }
return this; return this;
@ -217,14 +294,76 @@ export class Publisher extends StreamManager {
// avoid early 'streamPlaying' event // avoid early 'streamPlaying' event
this.stream.updateMediaStreamInVideos(); this.stream.updateMediaStreamInVideos();
} }
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
if (!!this.firstVideoElement) { if (!!this.firstVideoElement) {
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode); this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
} }
delete this.firstVideoElement; 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(); resolve();
}; };
@ -371,13 +510,6 @@ export class Publisher extends StreamManager {
this.stream.session = session; this.stream.session = session;
} }
/**
* @hidden
*/
emitEvent(type: string, eventArray: any[]): void {
this.ee.emitEvent(type, eventArray);
}
/** /**
* @hidden * @hidden
*/ */
@ -392,7 +524,7 @@ export class Publisher extends StreamManager {
private setPermissionDialogTimer(waitTime: number): void { private setPermissionDialogTimer(waitTime: number): void {
this.permissionDialogTimeout = setTimeout(() => { this.permissionDialogTimeout = setTimeout(() => {
this.ee.emitEvent('accessDialogOpened', []); this.emitEvent('accessDialogOpened', []);
}, waitTime); }, waitTime);
} }
@ -400,7 +532,7 @@ export class Publisher extends StreamManager {
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.ee.emitEvent('accessDialogClosed', []); this.emitEvent('accessDialogClosed', []);
} }
} }

View File

@ -33,6 +33,7 @@ import { RecordingEvent } from '../OpenViduInternal/Events/RecordingEvent';
import { SessionDisconnectedEvent } from '../OpenViduInternal/Events/SessionDisconnectedEvent'; import { SessionDisconnectedEvent } from '../OpenViduInternal/Events/SessionDisconnectedEvent';
import { SignalEvent } from '../OpenViduInternal/Events/SignalEvent'; import { SignalEvent } from '../OpenViduInternal/Events/SignalEvent';
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent'; import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; 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) * 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)). * 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. * 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. * 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. * 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)). * 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. * 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. * 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. * 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) * 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)). * 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. * 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. * 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. * 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 * 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)). * (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. * 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. * 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 * 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)). * 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. * 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. * 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'); const streamEvent = new StreamEvent(true, publisher, 'streamDestroyed', publisher.stream, 'unpublish');
publisher.emitEvent('streamDestroyed', [streamEvent]); 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); const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, msg.reason);
this.ee.emitEvent('streamDestroyed', [streamEvent]); this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehaviour(); streamEvent.callDefaultBehavior();
delete this.remoteStreamsCreated[stream.streamId]; 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); const streamEvent = new StreamEvent(true, this, 'streamDestroyed', connection.stream, msg.reason);
this.ee.emitEvent('streamDestroyed', [streamEvent]); this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehaviour(); streamEvent.callDefaultBehavior();
// Deleting the remote stream // Deleting the remote stream
const streamId: string = connection.stream.streamId; const streamId: string = connection.stream.streamId;
@ -654,7 +655,7 @@ export class Session implements EventDispatcher {
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, 'forceDisconnect'); const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, 'forceDisconnect');
this.ee.emitEvent('streamDestroyed', [streamEvent]); this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehaviour(); streamEvent.callDefaultBehavior();
delete this.remoteStreamsCreated[stream.streamId]; 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 * @hidden
*/ */
@ -818,7 +859,7 @@ export class Session implements EventDispatcher {
// Make Session object dispatch 'sessionDisconnected' event (if it is not already disposed) // Make Session object dispatch 'sessionDisconnected' event (if it is not already disposed)
const sessionDisconnectEvent = new SessionDisconnectedEvent(this, reason); const sessionDisconnectEvent = new SessionDisconnectedEvent(this, reason);
this.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]); this.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]);
sessionDisconnectEvent.callDefaultBehaviour(); sessionDisconnectEvent.callDefaultBehavior();
} }
} else { } else {
console.warn('You were not connected to the session ' + this.sessionId); console.warn('You were not connected to the session ' + this.sessionId);
@ -952,7 +993,7 @@ export class Session implements EventDispatcher {
{ urls: [stunUrl] }, { urls: [stunUrl] },
{ urls: [turnUrl1, turnUrl2], username: turnUsername, credential: turnCredential } { urls: [turnUrl1, turnUrl2], username: turnUsername, credential: turnCredential }
]; ];
console.log('TURN temp credentials [' + turnUsername + ':' + turnCredential + ']') console.log('TURN temp credentials [' + turnUsername + ':' + turnCredential + ']');
} }
if (!!role) { if (!!role) {
this.openvidu.role = role; this.openvidu.role = role;

View File

@ -56,13 +56,30 @@ export class Stream {
*/ */
hasAudio: boolean; 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 * Unique identifier of the stream
*/ */
streamId: string; 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; typeOfVideo?: string;
@ -71,6 +88,17 @@ export class Stream {
*/ */
streamManager: StreamManager; 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 * @hidden
*/ */
@ -119,26 +147,36 @@ export class Stream {
// InboundStreamOptions: stream belongs to a Subscriber // InboundStreamOptions: stream belongs to a Subscriber
this.inboundStreamOpts = <InboundStreamOptions>options; this.inboundStreamOpts = <InboundStreamOptions>options;
this.streamId = this.inboundStreamOpts.id; this.streamId = this.inboundStreamOpts.id;
this.hasAudio = this.inboundStreamOpts.recvAudio; this.hasAudio = this.inboundStreamOpts.hasAudio;
this.hasVideo = this.inboundStreamOpts.recvVideo; this.hasVideo = this.inboundStreamOpts.hasVideo;
this.typeOfVideo = (!this.inboundStreamOpts.typeOfVideo) ? undefined : this.inboundStreamOpts.typeOfVideo; if (this.hasAudio) {
this.frameRate = (this.inboundStreamOpts.frameRate === -1) ? undefined : this.inboundStreamOpts.frameRate; 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 { } else {
// OutboundStreamOptions: stream belongs to a Publisher // OutboundStreamOptions: stream belongs to a Publisher
this.outboundStreamOpts = <OutboundStreamOptions>options; this.outboundStreamOpts = <OutboundStreamOptions>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.hasAudio = this.isSendAudio();
this.hasVideo = this.isSendVideo(); 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', () => { this.ee.on('mediastream-updated', () => {
@ -410,13 +448,21 @@ export class Stream {
console.debug('Sending SDP offer to publish as ' console.debug('Sending SDP offer to publish as '
+ this.streamId, sdpOfferParam); + 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', { this.session.openvidu.sendRequest('publishVideo', {
sdpOffer: sdpOfferParam, sdpOffer: sdpOfferParam,
doLoopback: this.displayMyRemote() || false, doLoopback: this.displayMyRemote() || false,
audioActive: this.isSendAudio(), hasAudio: this.isSendAudio(),
videoActive: this.isSendVideo(), hasVideo: this.isSendVideo(),
typeOfVideo: ((this.isSendVideo()) ? (this.isSendScreen() ? 'SCREEN' : 'CAMERA') : ''), audioActive: this.audioActive,
frameRate: !!this.frameRate ? this.frameRate : -1 videoActive: this.videoActive,
typeOfVideo,
frameRate: !!this.frameRate ? this.frameRate : -1,
videoDimensions: JSON.stringify(this.videoDimensions)
}, (error, response) => { }, (error, response) => {
if (error) { if (error) {
reject('Error on publishVideo: ' + JSON.stringify(error)); reject('Error on publishVideo: ' + JSON.stringify(error));
@ -426,7 +472,7 @@ export class Stream {
this.streamId = response.id; this.streamId = response.id;
this.isLocalStreamPublished = true; this.isLocalStreamPublished = true;
if (this.displayMyRemote()) { if (this.displayMyRemote()) {
this.remotePeerSuccesfullyEstablished(); this.remotePeerSuccessfullyEstablished();
} }
this.ee.emitEvent('stream-created-by-publisher'); this.ee.emitEvent('stream-created-by-publisher');
this.initWebRtcStats(); this.initWebRtcStats();
@ -457,8 +503,8 @@ export class Stream {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const offerConstraints = { const offerConstraints = {
audio: this.inboundStreamOpts.recvAudio, audio: this.inboundStreamOpts.hasAudio,
video: this.inboundStreamOpts.recvVideo video: this.inboundStreamOpts.hasVideo
}; };
console.debug("'Session.subscribe(Stream)' called. Constraints of generate SDP offer", console.debug("'Session.subscribe(Stream)' called. Constraints of generate SDP offer",
offerConstraints); offerConstraints);
@ -480,7 +526,7 @@ export class Stream {
reject(new Error('Error on recvVideoFrom: ' + JSON.stringify(error))); reject(new Error('Error on recvVideoFrom: ' + JSON.stringify(error)));
} else { } else {
this.webRtcPeer.processAnswer(response.sdpAnswer).then(() => { this.webRtcPeer.processAnswer(response.sdpAnswer).then(() => {
this.remotePeerSuccesfullyEstablished(); this.remotePeerSuccessfullyEstablished();
this.initWebRtcStats(); this.initWebRtcStats();
resolve(); resolve();
}).catch(error => { }).catch(error => {
@ -501,7 +547,7 @@ export class Stream {
}); });
} }
private remotePeerSuccesfullyEstablished(): void { private remotePeerSuccessfullyEstablished(): void {
this.mediaStream = this.webRtcPeer.pc.getRemoteStreams()[0]; this.mediaStream = this.webRtcPeer.pc.getRemoteStreams()[0];
console.debug('Peer remote stream', this.mediaStream); console.debug('Peer remote stream', this.mediaStream);

View File

@ -139,9 +139,9 @@ export class StreamManager implements EventDispatcher {
on(type: string, handler: (event: Event) => void): EventDispatcher { on(type: string, handler: (event: Event) => void): EventDispatcher {
this.ee.on(type, event => { this.ee.on(type, event => {
if (event) { if (event) {
console.info("Event '" + type + "' triggered", event); console.info("Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", event);
} else { } else {
console.info("Event '" + type + "' triggered"); console.info("Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'");
} }
handler(event); 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) { private pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) {
this.videos.push(streamManagerVideo); this.videos.push(streamManagerVideo);
this.addPlayEventToFirstVideo(); this.addPlayEventToFirstVideo();

View File

@ -22,49 +22,49 @@ export enum OpenViduErrorName {
/** /**
* Browser is not supported by OpenVidu. * Browser is not supported by OpenVidu.
* Returned uppon unsuccessful [[Session.connect]] * Returned upon unsuccessful [[Session.connect]]
*/ */
BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED', BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',
/** /**
* The user hasn't granted permissions to the required input device when the browser asked for them. * 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', DEVICE_ACCESS_DENIED = 'DEVICE_ACCESS_DENIED',
/** /**
* 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.
* Returned uppon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] * Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
*/ */
SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED', SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED',
/** /**
* Browser does not support screen sharing. * Browser does not support screen sharing.
* Returned uppon unsuccessful [[OpenVidu.initPublisher]] * Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/ */
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED', SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
/** /**
* Only for Chrome, there's no screen sharing extension installed * 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', SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED',
/** /**
* Only for Chrome, the screen sharing extension is installed but is disabled * 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', SCREEN_EXTENSION_DISABLED = 'SCREEN_EXTENSION_DISABLED',
/** /**
* No video input device found with the provided deviceId (property [[PublisherProperties.videoSource]]) * 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', INPUT_VIDEO_DEVICE_NOT_FOUND = 'INPUT_VIDEO_DEVICE_NOT_FOUND',
/** /**
* No audio input device found with the provided deviceId (property [[PublisherProperties.audioSource]]) * 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', 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 * 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). * 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', PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR',

View File

@ -54,6 +54,6 @@ export class ConnectionEvent extends Event {
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -21,7 +21,7 @@ import { Session } from '../../OpenVidu/Session';
export abstract class Event { 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; 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) * - `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 * 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() { preventDefault() {
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
this.callDefaultBehaviour = () => { }; this.callDefaultBehavior = () => { };
this.hasBeenPrevented = true; this.hasBeenPrevented = true;
} }
protected abstract callDefaultBehaviour(); protected abstract callDefaultBehavior();
} }

View File

@ -57,6 +57,6 @@ export class PublisherSpeakingEvent extends Event {
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -56,6 +56,6 @@ export class RecordingEvent extends Event {
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -41,9 +41,9 @@ export class SessionDisconnectedEvent extends Event {
/** /**
* @hidden * @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 = <Session>this.target; const session = <Session>this.target;

View File

@ -60,6 +60,6 @@ export class SignalEvent extends Event {
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -55,17 +55,27 @@ export class StreamEvent extends Event {
/** /**
* @hidden * @hidden
*/ */
callDefaultBehaviour() { callDefaultBehavior() {
if (this.type === 'streamDestroyed') { if (this.type === 'streamDestroyed') {
if (this.target instanceof Session) { if (this.target instanceof Session) {
// Remote Stream // 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(); this.stream.disposeWebRtcPeer();
} else if (this.target instanceof Publisher) { } else if (this.target instanceof Publisher) {
// Local Stream // 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((<Publisher>this.target).screenShareResizeInterval);
this.stream.isLocalStreamReadyToPublish = false; this.stream.isLocalStreamReadyToPublish = false;
// Delete Publisher object from OpenVidu publishers array
const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
for (let i = 0; i < openviduPublishers.length; i++) {
if (openviduPublishers[i] === (<Publisher>this.target)) {
openviduPublishers.splice(i, 1);
break;
}
}
} }
// Dispose the MediaStream local object // Dispose the MediaStream local object

View File

@ -35,6 +35,6 @@ export class StreamManagerEvent extends Event {
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -18,47 +18,59 @@
import { Event } from './Event'; import { Event } from './Event';
import { Session } from '../../OpenVidu/Session'; import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream'; 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 { 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; 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; 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; newValue: Object;
/** /**
* Previous value of the property (after change) * Previous value of the property (before change)
*/ */
oldValue: Object; oldValue: Object;
/** /**
* @hidden * @hidden
*/ */
constructor(target: Session, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object) { constructor(target: Session | StreamManager, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object, reason: string) {
super(false, target, 'streamPropertyChangedEvent'); super(false, target, 'streamPropertyChanged');
this.stream = stream; this.stream = stream;
this.changedProperty = changedProperty; this.changedProperty = changedProperty;
this.newValue = newValue; this.newValue = newValue;
this.oldValue = oldValue; this.oldValue = oldValue;
this.reason = reason;
} }
/** /**
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -44,6 +44,6 @@ export class VideoElementEvent extends Event {
* @hidden * @hidden
*/ */
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty
callDefaultBehaviour() { } callDefaultBehavior() { }
} }

View File

@ -20,8 +20,11 @@ import { Connection } from '../../../OpenVidu/Connection';
export interface InboundStreamOptions { export interface InboundStreamOptions {
id: string; id: string;
connection: Connection; connection: Connection;
frameRate: number; hasAudio: boolean;
recvAudio: boolean; hasVideo: boolean;
recvVideo: boolean; audioActive: boolean;
videoActive: boolean;
typeOfVideo: string; typeOfVideo: string;
frameRate: number;
videoDimensions: { width: number, height: number };
} }

View File

@ -17,8 +17,11 @@
export interface StreamOptionsServer { export interface StreamOptionsServer {
id: string; id: string;
hasAudio: boolean;
hasVideo: boolean;
audioActive: boolean; audioActive: boolean;
frameRate: number;
videoActive: boolean; videoActive: boolean;
typeOfVideo: string; typeOfVideo: string;
frameRate: number;
videoDimensions: string;
} }

View File

@ -32,7 +32,9 @@ export interface PublisherProperties {
audioSource?: string | MediaStreamTrack | boolean; 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; frameRate?: number;

View File

@ -20,6 +20,7 @@ export { SignalEvent } from './OpenViduInternal/Events/SignalEvent';
export { StreamEvent } from './OpenViduInternal/Events/StreamEvent'; export { StreamEvent } from './OpenViduInternal/Events/StreamEvent';
export { StreamManagerEvent } from './OpenViduInternal/Events/StreamManagerEvent'; export { StreamManagerEvent } from './OpenViduInternal/Events/StreamManagerEvent';
export { VideoElementEvent } from './OpenViduInternal/Events/VideoElementEvent'; export { VideoElementEvent } from './OpenViduInternal/Events/VideoElementEvent';
export { StreamPropertyChangedEvent } from './OpenViduInternal/Events/StreamPropertyChangedEvent';
export { Device } from './OpenViduInternal/Interfaces/Public/Device'; export { Device } from './OpenViduInternal/Interfaces/Public/Device';
export { EventDispatcher } from './OpenViduInternal/Interfaces/Public/EventDispatcher'; export { EventDispatcher } from './OpenViduInternal/Interfaces/Public/EventDispatcher';