diff --git a/openvidu-browser/config/tslint.json b/openvidu-browser/config/tslint.json index 2f1e9bc7..ec83fc2d 100644 --- a/openvidu-browser/config/tslint.json +++ b/openvidu-browser/config/tslint.json @@ -7,10 +7,6 @@ ], "ban-types": { "options": [ - [ - "Object", - "Avoid using the `Object` type. Did you mean `object`?" - ], [ "Function", "Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`." diff --git a/openvidu-browser/src/OpenVidu/MediaManager.ts b/openvidu-browser/src/OpenVidu/MediaManager.ts deleted file mode 100644 index 22c210d1..00000000 --- a/openvidu-browser/src/OpenVidu/MediaManager.ts +++ /dev/null @@ -1,302 +0,0 @@ -/* - * (C) Copyright 2017-2018 OpenVidu (https://openvidu.io/) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import { Stream } from './Stream'; -import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher'; -import { Event } from '../OpenViduInternal/Events/Event'; -import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'; -import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; - -import EventEmitter = require('wolfy87-eventemitter'); - - -/** - * Interface in charge of displaying the media streams in the HTML DOM. This wraps any Publisher and Subscriber object, as well as - * any extra representation in the DOM you assign to some Stream by calling [[Stream.addVideoElement]]. - * - * The use of this interface is useful when you don't need to differentiate between streams and just want to directly manage videos - */ -export class MediaManager implements EventDispatcher { - - /** - * The Stream represented in the DOM by the MediaManager - */ - stream: Stream; - - /** - * Whether the MediaManager is representing in the DOM a local Stream ([[Publisher]]) or a remote Stream ([[Subscriber]]) - */ - remote: boolean; - - /** - * The DOM HTMLElement assigned as target element when initializing the MediaManager. This property is defined when [[OpenVidu.initPublisher]] - * or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter. It is undefined when [[OpenVidu.initPublisher]] - * or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter or when the MediaManager hass been - * created by calling [[Stream.addVideoElement]] - */ - targetElement?: HTMLElement; - - /** - * The DOM HTMLVideoElement displaying the MediaManager's stream - */ - video: HTMLVideoElement; - - /** - * `id` attribute of the DOM HTMLVideoElement displaying the MediaManager's stream - */ - id: string; - - /** - * @hidden - */ - isVideoElementCreated = false; - - protected ee = new EventEmitter(); - protected customEe = new EventEmitter(); - - - /** - * @hidden - */ - constructor(stream: Stream, targetElement?: HTMLElement | string) { - this.stream = stream; - this.stream.mediaManagers.push(this); - if (typeof targetElement === 'string') { - const e = document.getElementById(targetElement); - if (!!e) { - this.targetElement = e; - } - } else if (targetElement instanceof HTMLElement) { - this.targetElement = targetElement; - } else if (!!this.targetElement) { - console.warn("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement); - } - - this.customEe.on('video-removed', (element: HTMLVideoElement) => { - this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(element, this, 'videoElementDestroyed')]); - }); - } - - /** - * See [[EventDispatcher.on]] - */ - on(type: string, handler: (event: Event) => void): EventDispatcher { - this.ee.on(type, event => { - if (event) { - console.info("Event '" + type + "' triggered", event); - } else { - console.info("Event '" + type + "' triggered"); - } - handler(event); - }); - if (type === 'videoElementCreated') { - if (!!this.stream && this.isVideoElementCreated) { - this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.video, this, 'videoElementCreated')]); - } else { - this.customEe.on('video-element-created', element => { - this.id = element.id; - this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(element.element, this, 'videoElementCreated')]); - }); - } - } - if (type === 'videoPlaying') { - if (!this.stream.displayMyRemote() && !!this.video && - this.video.currentTime > 0 && - this.video.paused === false && - this.video.ended === false && - this.video.readyState === 4) { - this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.video, this, 'videoPlaying')]); - } else { - this.customEe.once('video-is-playing', (element) => { - this.ee.emitEvent('videoPlaying', [new VideoElementEvent(element.element, this, 'videoPlaying')]); - }); - } - } - return this; - } - - /** - * See [[EventDispatcher.once]] - */ - once(type: string, handler: (event: Event) => void): MediaManager { - this.ee.once(type, event => { - if (event) { - console.info("Event '" + type + "' triggered once", event); - } else { - console.info("Event '" + type + "' triggered once"); - } - handler(event); - }); - if (type === 'videoElementCreated') { - if (!!this.stream && this.isVideoElementCreated) { - this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.video, this, 'videoElementCreated')]); - } else { - this.customEe.once('video-element-created', element => { - this.id = element.id; - this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(element.element, this, 'videoElementCreated')]); - }); - } - } - if (type === 'videoPlaying') { - if (!this.stream.displayMyRemote() && this.video && - this.video.currentTime > 0 && - this.video.paused === false && - this.video.ended === false && - this.video.readyState === 4) { - this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.video, this, 'videoPlaying')]); - } else { - this.customEe.once('video-is-playing', (element) => { - this.ee.emitEvent('videoPlaying', [new VideoElementEvent(element.element, this, 'videoPlaying')]); - }); - } - } - return this; - } - - /** - * See [[EventDispatcher.off]] - */ - off(type: string, handler?: (event: Event) => void): MediaManager { - if (!handler) { - this.ee.removeAllListeners(type); - } else { - this.ee.off(type, handler); - } - return this; - } - - - /** - * @hidden - */ - insertVideo(targetElement?: HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement { - if (!!targetElement) { - - this.video = document.createElement('video'); - - this.video.id = (this.stream.isLocal() ? 'local-' : 'remote-') + 'video-' + this.stream.streamId; - this.video.autoplay = true; - this.video.controls = false; - this.video.srcObject = this.stream.getMediaStream(); - - if (this.stream.isLocal() && !this.stream.displayMyRemote()) { - this.video.muted = true; - - if (this.stream.outboundStreamOpts.publisherProperties.mirror) { - this.mirrorVideo(); - } - - this.video.oncanplay = () => { - console.info("Local 'Stream' with id [" + this.stream.streamId + '] video is now playing'); - this.customEe.emitEvent('video-is-playing', [{ - element: this.video - }]); - }; - } else { - this.video.title = this.stream.streamId; - } - - this.targetElement = targetElement; - - const insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND; - - this.insertVideoWithMode(insMode); - - this.customEe.emitEvent('video-element-created', [{ - element: this.video - }]); - - this.isVideoElementCreated = true; - } - - if (this.stream.isLocal()) { - this.stream.isLocalStreamReadyToPublish = true; - this.stream.emitEvent('stream-ready-to-publish', []); - } - - return this.video; - } - - /** - * @hidden - */ - insertVideoWithMode(insertMode: VideoInsertMode): void { - if (!!this.targetElement) { - switch (insertMode) { - case VideoInsertMode.AFTER: - this.targetElement.parentNode!!.insertBefore(this.video, this.targetElement.nextSibling); - break; - case VideoInsertMode.APPEND: - this.targetElement.appendChild(this.video); - break; - case VideoInsertMode.BEFORE: - this.targetElement.parentNode!!.insertBefore(this.video, this.targetElement); - break; - case VideoInsertMode.PREPEND: - this.targetElement.insertBefore(this.video, this.targetElement.childNodes[0]); - break; - case VideoInsertMode.REPLACE: - this.targetElement.parentNode!!.replaceChild(this.video, this.targetElement); - break; - default: - this.insertVideoWithMode(VideoInsertMode.APPEND); - } - } - } - - /** - * @hidden - */ - removeVideo(): void { - if (!!this.video) { - this.video.parentNode!.removeChild(this.video); - this.customEe.emitEvent('video-removed', [this.video]); - delete this.video; - } - } - - /** - * @hidden - */ - addOnCanPlayEvent() { - if (!!this.video) { - // let thumbnailId = this.video.thumb; - this.video.oncanplay = () => { - if (this.stream.isLocal() && this.stream.displayMyRemote()) { - console.info("Your own remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); - this.customEe.emitEvent('remote-video-is-playing', [{ - element: this.video - }]); - } else if (!this.stream.isLocal() && !this.stream.displayMyRemote()) { - console.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); - this.customEe.emitEvent('video-is-playing', [{ - element: this.video - }]); - } - // show(thumbnailId); - // this.hideSpinner(this.streamId); - }; - } - } - - - private mirrorVideo(): void { - this.video.style.transform = 'rotateY(180deg)'; - this.video.style.webkitTransform = 'rotateY(180deg)'; - } - -} \ No newline at end of file diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index e87b62ae..dc2fe6ab 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -38,9 +38,12 @@ import platform = require('platform'); */ export class OpenVidu { - private session: Session; private jsonRpcClient: any; + /** + * @hidden + */ + session: Session; /** * @hidden */ @@ -87,9 +90,9 @@ export class OpenVidu { * * The [[Publisher]] object will dispatch an `accessAllowed` or `accessDenied` event once it has been granted access to the requested input devices or not. * - * The [[Publisher]] object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM (if _targetElement_ not null or undefined) + * The [[Publisher]] object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM (if _targetElement_ not null or undefined, or if you call [[Publisher.createVideoElement]]) * - * The [[Publisher]] object will dispatch a `videoPlaying` event once the local video starts playing (only if `videoElementCreated` event has been previously dispatched) + * The [[Publisher]] object will dispatch a `streamPlaying` event once the local streams starts playing * * @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher will be inserted (see [[PublisherProperties.insertMode]]). If null or undefined no default video will be created for this Publisher * (you can always call method [[Stream.addVideoElement]] for the object [[Publisher.stream]] to manage the video elements on your own) diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index 40cb5825..6195b990 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -15,10 +15,10 @@ * */ -import { MediaManager } from './MediaManager'; import { OpenVidu } from './OpenVidu'; import { Session } from './Session'; import { Stream } from './Stream'; +import { StreamManager } from './StreamManager'; import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher'; import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties'; import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions'; @@ -33,7 +33,7 @@ import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; /** * Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method */ -export class Publisher extends MediaManager { +export class Publisher extends StreamManager { /** * Whether the Publisher has been granted access to the requested input devices or not @@ -45,12 +45,7 @@ export class Publisher extends MediaManager { */ session: Session; // Initialized by Session.publish(Publisher) - /** - * @hidden - */ - accessDenied = false; - - private element?: HTMLElement; + private accessDenied = false; private properties: PublisherProperties; private permissionDialogTimeout: NodeJS.Timer; @@ -58,10 +53,10 @@ export class Publisher extends MediaManager { * @hidden */ constructor(targEl: string | HTMLElement, properties: PublisherProperties, private openvidu: OpenVidu) { - super(new Stream(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.stream.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); this.ee.emitEvent('streamDestroyed', [streamEvent]); streamEvent.callDefaultBehaviour(); @@ -103,22 +98,18 @@ export class Publisher extends MediaManager { if (!!this.stream && this.stream.isLocalStreamPublished) { this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); } else { - this.stream.on('stream-created-by-publisher', () => { + this.stream.ee.on('stream-created-by-publisher', () => { this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); }); } } if (type === 'remoteVideoPlaying') { - if (this.stream.displayMyRemote() && this.video && - this.video.currentTime > 0 && - this.video.paused === false && - this.video.ended === false && - this.video.readyState === 4) { - this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.video, this, 'remoteVideoPlaying')]); - } else { - this.stream.on('remote-video-is-playing', (element) => { - this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(element.element, this, 'remoteVideoPlaying')]); - }); + if (this.stream.displayMyRemote() && this.videos[0] && this.videos[0].video && + this.videos[0].video.currentTime > 0 && + this.videos[0].video.paused === false && + this.videos[0].video.ended === false && + this.videos[0].video.readyState === 4) { + this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); } } if (type === 'accessAllowed') { @@ -144,22 +135,18 @@ export class Publisher extends MediaManager { if (!!this.stream && this.stream.isLocalStreamPublished) { this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); } else { - this.stream.once('stream-created-by-publisher', () => { + this.stream.ee.once('stream-created-by-publisher', () => { this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]); }); } } if (type === 'remoteVideoPlaying') { - if (this.stream.displayMyRemote() && this.video && - this.video.currentTime > 0 && - this.video.paused === false && - this.video.ended === false && - this.video.readyState === 4) { - this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.video, this, 'remoteVideoPlaying')]); - } else { - this.stream.once('remote-video-is-playing', (element) => { - this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(element.element, this, 'remoteVideoPlaying')]); - }); + if (this.stream.displayMyRemote() && this.videos[0] && this.videos[0].video && + this.videos[0].video.currentTime > 0 && + this.videos[0].video.paused === false && + this.videos[0].video.ended === false && + this.videos[0].video.readyState === 4) { + this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); } } if (type === 'accessAllowed') { @@ -213,7 +200,13 @@ export class Publisher extends MediaManager { } this.stream.setMediaStream(mediaStream); - this.insertVideo(this.targetElement, this.properties.insertMode); + 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; resolve(); }; diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index 1304cedd..cb744e5b 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -19,6 +19,7 @@ import { Connection } from './Connection'; import { OpenVidu } from './OpenVidu'; import { Publisher } from './Publisher'; import { Stream } from './Stream'; +import { StreamManager } from './StreamManager'; import { Subscriber } from './Subscriber'; import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher'; import { SignalOptions } from '../OpenViduInternal/Interfaces/Public/SignalOptions'; @@ -56,6 +57,11 @@ export class Session implements EventDispatcher { */ sessionId: string; + /** + * Collection of all StreamManagers of this Session ([[Publishers]] and [[Subscribers]]) + */ + streamManagers: StreamManager[] = []; + // This map is only used to avoid race condition between 'joinRoom' response and 'onParticipantPublished' notification /** * @hidden @@ -177,9 +183,9 @@ export class Session implements EventDispatcher { * * #### Events dispatched * - * The [[Subscriber]] object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM (if _targetElement_ not null or undefined) + * The [[Subscriber]] object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM (if _targetElement_ not null or undefined, or if you call [[Subscriber.createVideoElement]]) * - * The [[Subscriber]] object will dispatch a `videoPlaying` event once the remote video starts playing (only if `videoElementCreated` event has been previously dispatched) + * The [[Subscriber]] object will dispatch a `streamPlaying` event once the remote stream starts playing * * See [[VideoElementEvent]] to learn more. * @@ -226,9 +232,9 @@ export class Session implements EventDispatcher { } }); const subscriber = new Subscriber(stream, targetElement, properties); - stream.mediaManagers.forEach(mediaManager => { - mediaManager.insertVideo(subscriber.targetElement, properties.insertMode); - }); + if (!!subscriber.targetElement) { + stream.streamManager.createVideoElement(subscriber.targetElement, properties.insertMode); + } return subscriber; } @@ -287,7 +293,7 @@ export class Session implements EventDispatcher { subscriber.stream.disposeMediaStream(); } ); - subscriber.stream.removeVideos(); + subscriber.stream.streamManager.removeAllVideos(); } @@ -298,8 +304,7 @@ export class Session implements EventDispatcher { * * The local [[Publisher]] object will dispatch a `streamCreated` event upon successful termination of this method. See [[StreamEvent]] to learn more. * - * The local [[Publisher]] object will dispatch a `remoteVideoPlaying` event only if [[Publisher.subscribeToRemote]] was called before this method, once the remote video starts playing. - * See [[VideoElementEvent]] to learn more. + * The local [[Publisher]] object will dispatch a `streamPlaying` once the media stream starts playing. See [[StreamManagerEvent]] to learn more. * * The [[Session]] object of every other participant connected to the session will dispatch a `streamCreated` event so they can subscribe to it. See [[StreamEvent]] to learn more. * @@ -791,7 +796,7 @@ export class Session implements EventDispatcher { if (!!this.connection.stream) { // Make Publisher object dispatch 'streamDestroyed' event (if there's a local stream) this.connection.stream.disposeWebRtcPeer(); - this.connection.stream.emitEvent('local-stream-destroyed-by-disconnect', [reason]); + this.connection.stream.ee.emitEvent('local-stream-destroyed-by-disconnect', [reason]); } if (!this.connection.disposed) { diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index fef4b262..cdfba934 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -16,8 +16,8 @@ */ import { Connection } from './Connection'; -import { MediaManager } from './MediaManager'; import { Session } from './Session'; +import { StreamManager } from './StreamManager'; import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions'; import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions'; import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats'; @@ -30,8 +30,9 @@ import * as kurentoUtils from '../OpenViduInternal/KurentoUtils/kurento-utils-js /** - * Represents each one of the videos send and receive by a user in a session. - * Therefore each [[Publisher]] and [[Subscriber]] has an attribute of type Stream + * Represents each one of the media streams available in OpenVidu Server for certain session. + * Each [[Publisher]] and [[Subscriber]] has an attribute of type Stream, as they give access + * to at least one of them (sending and receiving it, respectively) */ export class Stream { @@ -41,17 +42,20 @@ export class Stream { connection: Connection; /** - * Frame rate of the video in frames per second. This property is only defined if the [[Publisher]] of the stream was initialized passing a _frameRate_ property on [[OpenVidu.initPublisher]] method + * Frame rate of the video in frames per second. This property is only defined if the [[Publisher]] of + * the stream was initialized passing a _frameRate_ property on [[OpenVidu.initPublisher]] method */ frameRate?: number; /** - * Whether the stream has a video track or not + * Whether the stream has a video track or not. This attribute may change if the Publisher publishing the Stream + * calls [[Publisher.publishVideo]]. You can listen to event [[StreamPropertyChangedEvent]] to know when this happens */ hasVideo: boolean; /** - * Whether the stream has an audio track or not + * Whether the stream has an audio track or not. This attribute may change if the Publisher publishing the Stream + * calls [[Publisher.publishAudio]]. You can listen to event [[StreamPropertyChangedEvent]] to know when this happens */ hasAudio: boolean; @@ -61,16 +65,19 @@ export class Stream { streamId: string; /** - * `"CAMERA"` or `"SCREEN"`. undefined if stream is audio-only + * `"CAMERA"` or `"SCREEN"`. *undefined* if stream is audio-only */ typeOfVideo?: string; /** - * Array of [[MediaManager]] objects displaying this stream in the DOM + * StreamManager object ([[Publisher]] or [[Subscriber]]) in charge of displaying this stream in the DOM */ - mediaManagers: MediaManager[] = []; + streamManager: StreamManager; - private ee = new EventEmitter(); + /** + * @hidden + */ + ee = new EventEmitter(); private webRtcPeer: any; private mediaStream: MediaStream; @@ -140,31 +147,12 @@ export class Stream { this.hasVideo = this.isSendVideo(); } - this.on('mediastream-updated', () => { - this.mediaManagers.forEach(mediaManager => { - if (!!mediaManager.video) { - mediaManager.video.srcObject = this.mediaStream; - } - }); + this.ee.on('mediastream-updated', () => { + this.streamManager.updateMediaStream(this.mediaStream); console.debug('Video srcObject [' + this.mediaStream + '] updated in stream [' + this.streamId + ']'); }); } - /** - * Makes `video` element parameter display this Stream. This is useful when you are managing the video elements on your own - * (parameter `targetElement` of methods [[OpenVidu.initPublisher]] or [[Session.subscribe]] is set to *null* or *undefined*) - * or if you want to have multiple video elements display the same media stream - */ - addVideoElement(video: HTMLVideoElement): MediaManager { - video.srcObject = this.mediaStream; - const mediaManager = new MediaManager(this); - mediaManager.video = video; - mediaManager.id = video.id; - mediaManager.isVideoElementCreated = true; - mediaManager.remote = !this.isLocal(); - return mediaManager; - } - /* Hidden methods */ @@ -240,7 +228,7 @@ export class Stream { reject(error); }); } else { - this.ee.once('stream-ready-to-publish', streamEvent => { + this.ee.once('stream-ready-to-publish', () => { this.publish() .then(() => { resolve(); @@ -280,6 +268,7 @@ export class Stream { this.mediaStream.getVideoTracks().forEach((track) => { track.stop(); }); + delete this.mediaStream; } console.info((!!this.outboundStreamOpts ? 'Local ' : 'Remote ') + "MediaStream from 'Stream' with id [" + this.streamId + '] is now disposed'); } @@ -291,20 +280,6 @@ export class Stream { return this.isSubscribeToRemote; } - /** - * @hidden - */ - on(eventName: string, listener: any): void { - this.ee.on(eventName, listener); - } - - /** - * @hidden - */ - once(eventName: string, listener: any): void { - this.ee.once(eventName, listener); - } - /** * @hidden */ @@ -331,13 +306,6 @@ export class Stream { this.outboundStreamOpts.publisherProperties.videoSource === 'screen'); } - /** - * @hidden - */ - emitEvent(type: string, eventArray: any[]): void { - this.ee.emitEvent(type, eventArray); - } - /** * @hidden */ @@ -387,15 +355,6 @@ export class Stream { this.speechEvent = undefined; } - /** - * @hidden - */ - removeVideos(): void { - this.mediaManagers.forEach(mediaManager => { - mediaManager.removeVideo(); - }); - } - /* Private methods */ @@ -435,6 +394,7 @@ export class Stream { } else { this.processSdpAnswer(response.sdpAnswer) .then(() => { + this.isLocalStreamPublished = true; this.ee.emitEvent('stream-created-by-publisher'); resolve(); }) @@ -461,7 +421,6 @@ export class Stream { this.webRtcPeer.generateOffer(successCallback); }); } - this.isLocalStreamPublished = true; }); } @@ -524,27 +483,17 @@ export class Stream { const peerConnection = this.webRtcPeer.peerConnection; peerConnection.setRemoteDescription(answer, () => { - // Avoids to subscribe to your own stream remotely - // except when showMyRemote is true + // Update remote MediaStream object except when local stream if (!this.isLocal() || this.displayMyRemote()) { this.mediaStream = peerConnection.getRemoteStreams()[0]; console.debug('Peer remote stream', this.mediaStream); if (!!this.mediaStream) { - this.ee.emitEvent('mediastream-updated'); - if (!!this.mediaStream.getAudioTracks()[0] && this.session.speakingEventsEnabled) { this.enableSpeakingEvents(); } } - - this.mediaManagers.forEach(mediaManager => { - mediaManager.addOnCanPlayEvent(); - }); - this.session.emitEvent('stream-subscribed', [{ - stream: this - }]); } this.initWebRtcStats(); diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts new file mode 100644 index 00000000..3135599a --- /dev/null +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -0,0 +1,407 @@ +/* + * (C) Copyright 2017-2018 OpenVidu (https://openvidu.io/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Stream } from './Stream'; +import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher'; +import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo'; +import { Event } from '../OpenViduInternal/Events/Event'; +import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent'; +import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'; +import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; + +import EventEmitter = require('wolfy87-eventemitter'); + + +/** + * Interface in charge of displaying the media streams in the HTML DOM. This wraps any [[Publisher]] and [[Subscriber]] object. + * You can insert as many video players fo the same Stream as you want by calling [[StreamManager.addVideoElement]] or + * [[StreamManager.createVideoElement]]. + * + * The use of StreamManager wrapper is particularly useful when you don't need to differentiate between Publisher or Subscriber streams or just + * want to directly manage your own video elements (even more than one video element per Stream). This scenario is pretty common in + * declarative, MVC frontend frameworks such as Angular, React or Vue.js + */ +export class StreamManager implements EventDispatcher { + + CURRENTVIDEO: HTMLVideoElement; + + /** + * The Stream represented in the DOM by the Publisher/Subscriber + */ + stream: Stream; + + /** + * All the videos displaying the Stream of this Publisher/Subscriber + */ + videos: StreamManagerVideo[] = []; + + /** + * Whether the Stream represented in the DOM is local or remote + * - `false` for [[Publisher]] + * - `true` for [[Subscriber]] + */ + remote: boolean; + + /** + * The DOM HTMLElement assigned as target element when creating the first video for the Publisher/Subscriber. This property is only defined if: + * - [[Publisher]] has been initialized by calling method [[OpenVidu.initPublisher]] with a valid `targetElement` parameter + * - [[Subscriber]] has been initialized by calling method [[Session.subscribe]] with a valid `targetElement` parameter + */ + targetElement: HTMLElement; + + /** + * `id` attribute of the DOM video element displaying the Publisher/Subscriber's stream. This property is only defined if: + * - [[Publisher]] has been initialized by calling method [[OpenVidu.initPublisher]] with a valid `targetElement` parameter + * - [[Subscriber]] has been initialized by calling method [[Session.subscribe]] with a valid `targetElement` parameter + */ + id: string; + + /** + * @hidden + */ + firstVideoElement: StreamManagerVideo; + /** + * @hidden + */ + lazyLaunchVideoElementCreatedEvent = false; + /** + * @hidden + */ + element: HTMLElement; + /** + * @hidden + */ + protected ee = new EventEmitter(); + /** + * @hidden + */ + protected customEe = new EventEmitter(); + + + /** + * @hidden + */ + constructor(stream: Stream, targetElement?: HTMLElement | string) { + this.stream = stream; + this.stream.streamManager = this; + this.remote = !this.stream.isLocal(); + + if (!!targetElement) { + let targEl; + if (typeof targetElement === 'string') { + targEl = document.getElementById(targetElement); + } else if (targetElement instanceof HTMLElement) { + targEl = targetElement; + } + + if (!!targEl) { + this.firstVideoElement = { + targetElement: targEl, + video: document.createElement('video'), + id: '' + }; + this.targetElement = targEl; + this.element = targEl; + } + } + } + + /** + * See [[EventDispatcher.on]] + */ + on(type: string, handler: (event: Event) => void): EventDispatcher { + this.ee.on(type, event => { + if (event) { + console.info("Event '" + type + "' triggered", event); + } else { + console.info("Event '" + type + "' triggered"); + } + handler(event); + }); + if (type === 'videoElementCreated') { + if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) { + this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]); + this.lazyLaunchVideoElementCreatedEvent = false; + } + } + if (type === 'streamPlaying' || type === 'videoPlaying') { + if (this.videos[0] && this.videos[0].video && + this.videos[0].video.currentTime > 0 && + this.videos[0].video.paused === false && + this.videos[0].video.ended === false && + this.videos[0].video.readyState === 4) { + this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this)]); + this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); + } + } + return this; + } + + /** + * See [[EventDispatcher.once]] + */ + once(type: string, handler: (event: Event) => void): StreamManager { + this.ee.once(type, event => { + if (event) { + console.info("Event '" + type + "' triggered once", event); + } else { + console.info("Event '" + type + "' triggered once"); + } + handler(event); + }); + if (type === 'videoElementCreated') { + if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) { + this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]); + } + } + if (type === 'streamPlaying' || type === 'videoPlaying') { + if (this.videos[0] && this.videos[0].video && + this.videos[0].video.currentTime > 0 && + this.videos[0].video.paused === false && + this.videos[0].video.ended === false && + this.videos[0].video.readyState === 4) { + this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this)]); + this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); + } + } + return this; + } + + /** + * See [[EventDispatcher.off]] + */ + off(type: string, handler?: (event: Event) => void): StreamManager { + if (!handler) { + this.ee.removeAllListeners(type); + } else { + this.ee.off(type, handler); + } + return this; + } + + /** + * Makes `video` element parameter display this Stream. This is useful when you are managing the video elements on your own + * (parameter `targetElement` of methods [[OpenVidu.initPublisher]] or [[Session.subscribe]] is set to *null* or *undefined*) + * or if you want to have multiple video elements displaying the same media stream. + * + * Calling this method with a video already added to other Publisher/Subscriber will cause the video element to be + * disassociated from that previous Publisher/Subscriber and to be associated to this one. + * + * @returns 1 if the video wasn't associated to any other Publisher/Subscriber and has been successfully added to this one. + * 0 if the video was already added to this Publisher/Subscriber. -1 if the video was previously associated to any other + * Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one. + */ + addVideoElement(video: HTMLVideoElement): number { + + this.initializeVideoProperties(video); + + // If the video element is already part of this StreamManager do nothing + for (const v of this.videos) { + if (v.video === video) { + return 0; + } + } + + let returnNumber = 1; + + this.initializeVideoProperties(video); + + for (const streamManager of this.stream.session.streamManagers) { + if (streamManager.disassociateVideo(video)) { + returnNumber = -1; + break; + } + } + + this.stream.session.streamManagers.forEach(streamManager => { + streamManager.disassociateVideo(video); + }); + + this.pushNewStreamManagerVideo({ + video, + id: video.id + }); + + console.info('New video element associated to ', this); + + return returnNumber; + } + + /** + * Creates a new video element displaying this Stream. This allows you to have multiple video elements displaying the same media stream. + * + * @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher/Subscriber will be inserted. + * If *null* or *undefined* no default video will be created. You can always call later method [[StreamManager.addVideoElement]] or [[StreamManager.createVideoElement]] + */ + createVideoElement(targetElement?: string | HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement { + let targEl; + if (typeof targetElement === 'string') { + targEl = document.getElementById(targEl); + if (!targEl) { + throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement); + } + } else if (targetElement instanceof HTMLElement) { + targEl = targetElement; + } else { + throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement); + } + + const video = document.createElement('video'); + this.initializeVideoProperties(video); + + let insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND; + switch (insMode) { + case VideoInsertMode.AFTER: + targEl.parentNode!!.insertBefore(video, targEl.nextSibling); + break; + case VideoInsertMode.APPEND: + targEl.appendChild(video); + break; + case VideoInsertMode.BEFORE: + targEl.parentNode!!.insertBefore(video, targEl); + break; + case VideoInsertMode.PREPEND: + targEl.insertBefore(video, targEl.childNodes[0]); + break; + case VideoInsertMode.REPLACE: + targEl.parentNode!!.replaceChild(video, targEl); + break; + default: + insMode = VideoInsertMode.APPEND; + targEl.appendChild(video); + break; + } + + const v: StreamManagerVideo = { + targetElement: targEl, + video, + insertMode: insMode, + id: video.id + }; + this.pushNewStreamManagerVideo(v); + + this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]); + + this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement; + + return video; + } + + /** + * @hidden + */ + initializeVideoProperties(video: HTMLVideoElement): void { + video.srcObject = this.stream.getMediaStream(); + video.autoplay = true; + video.controls = false; + if (!video.id) { + video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId; + } + if (!this.remote && !this.stream.displayMyRemote()) { + video.muted = true; + if (this.stream.outboundStreamOpts.publisherProperties.mirror) { + this.mirrorVideo(video); + } + } + } + + /** + * @hidden + */ + removeAllVideos(): void { + for (let i = this.stream.session.streamManagers.length - 1; i >= 0; --i) { + if (this.stream.session.streamManagers[i] === this) { + this.stream.session.streamManagers.splice(i, 1); + } + } + + this.videos.slice().reverse().forEach((streamManagerVideo, index, videos) => { + if (!!streamManagerVideo.targetElement) { + // Only remove videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher and Session.subscribe + // or those created by StreamManager.createVideoElement). These are also the videos that triggered a videoElementCreated event + streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video); + this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')]); + this.videos.splice(videos.length - 1 - index, 1); + } else { + // Remove srcObject in all videos managed by the user + streamManagerVideo.video.srcObject = null; + } + }); + } + + /** + * @hidden + */ + disassociateVideo(video: HTMLVideoElement): boolean { + let disassociated = false; + for (let i = 0; i < this.videos.length; i++) { + if (this.videos[i].video === video) { + this.videos.splice(i, 1); + disassociated = true; + console.info('Video element disassociated from ', this); + break; + } + } + return disassociated; + } + + /** + * @hidden + */ + addPlayEventToFirstVideo() { + if ((!!this.videos[0]) && (!!this.videos[0].video) && (this.videos[0].video.oncanplay === null)) { + this.videos[0].video.oncanplay = () => { + if (this.stream.isLocal()) { + if (!this.stream.displayMyRemote()) { + console.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing'); + this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); + } else { + console.info("Your own remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); + this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); + } + } else { + console.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); + this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); + } + this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this)]); + }; + } + } + + /** + * @hidden + */ + updateMediaStream(mediaStream: MediaStream) { + this.videos.forEach(streamManagerVideo => { + streamManagerVideo.video.srcObject = mediaStream; + }); + } + + private pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) { + this.videos.push(streamManagerVideo); + this.addPlayEventToFirstVideo(); + if (this.stream.session.streamManagers.indexOf(this) === -1) { + this.stream.session.streamManagers.push(this); + } + } + + private mirrorVideo(video): void { + video.style.transform = 'rotateY(180deg)'; + video.style.webkitTransform = 'rotateY(180deg)'; + } + +} \ No newline at end of file diff --git a/openvidu-browser/src/OpenVidu/Subscriber.ts b/openvidu-browser/src/OpenVidu/Subscriber.ts index 63c9e4f5..6fea3ded 100644 --- a/openvidu-browser/src/OpenVidu/Subscriber.ts +++ b/openvidu-browser/src/OpenVidu/Subscriber.ts @@ -15,17 +15,16 @@ * */ -import { MediaManager } from './MediaManager'; import { Stream } from './Stream'; +import { StreamManager } from './StreamManager'; import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties'; /** * Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method */ -export class Subscriber extends MediaManager { +export class Subscriber extends StreamManager { - private element?: HTMLElement; private properties: SubscriberProperties; /** diff --git a/openvidu-browser/src/OpenViduInternal/Events/Event.ts b/openvidu-browser/src/OpenViduInternal/Events/Event.ts index 2eacc343..3c1c9c32 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/Event.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/Event.ts @@ -15,7 +15,7 @@ * */ -import { MediaManager } from '../../OpenVidu/MediaManager'; +import { StreamManager } from '../../OpenVidu/StreamManager'; import { Session } from '../../OpenVidu/Session'; export abstract class Event { @@ -28,7 +28,7 @@ export abstract class Event { /** * The object that dispatched the event */ - target: Session | MediaManager; + target: Session | StreamManager; /** * The type of event. This is the same string you pass as first parameter when calling method `on()` of any object implementing [[EventDispatcher]] interface @@ -40,7 +40,7 @@ export abstract class Event { /** * @hidden */ - constructor(cancelable: boolean, target: Session | MediaManager, type: string) { + constructor(cancelable: boolean, target: Session | StreamManager, type: string) { this.cancelable = cancelable; this.target = target; this.type = type; diff --git a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts index 0a9e532c..75aedc65 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts @@ -52,7 +52,9 @@ export class SessionDisconnectedEvent extends Event { if (!!session.remoteConnections[connectionId].stream) { session.remoteConnections[connectionId].stream.disposeWebRtcPeer(); session.remoteConnections[connectionId].stream.disposeMediaStream(); - session.remoteConnections[connectionId].stream.removeVideos(); + if (session.remoteConnections[connectionId].stream.streamManager) { + session.remoteConnections[connectionId].stream.streamManager.removeAllVideos(); + } delete session.remoteStreamsCreated[session.remoteConnections[connectionId].stream.streamId]; session.remoteConnections[connectionId].dispose(); } diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts index c2873478..0f838dbe 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts @@ -59,24 +59,22 @@ export class StreamEvent extends Event { if (this.type === 'streamDestroyed') { if (this.target instanceof Session) { - - console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Session'"); - // Remote Stream + console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Session'"); this.stream.disposeWebRtcPeer(); - this.stream.disposeMediaStream(); - this.stream.removeVideos(); - } else if (this.target instanceof Publisher) { - - console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Publisher'"); - // Local Stream - this.stream.disposeMediaStream(); - this.stream.removeVideos(); + console.info("Calling default behaviour upon '" + this.type + "' event dispatched by 'Publisher'"); this.stream.isLocalStreamReadyToPublish = false; } + // Dispose the MediaStream local object + this.stream.disposeMediaStream(); + + // Remove from DOM all video elements associated to this Stream, if there's a StreamManager defined + // (method Session.subscribe must have been called) + if (this.stream.streamManager) this.stream.streamManager.removeAllVideos(); + // Delete stream from Session.remoteStreamsCreated map delete this.stream.session.remoteStreamsCreated[this.stream.streamId]; diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts new file mode 100644 index 00000000..b5cf4275 --- /dev/null +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2017-2018 OpenVidu (https://openvidu.io/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Event } from './Event'; +import { StreamManager } from '../../OpenVidu/StreamManager'; + +/** + * Defines the following events: + * - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) + */ +export class StreamManagerEvent extends Event { + + /** + * @hidden + */ + constructor(target: StreamManager) { + super(false, target, 'streamPlaying'); + } + + /** + * @hidden + */ + // tslint:disable-next-line:no-empty + callDefaultBehaviour() { } + +} \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts new file mode 100644 index 00000000..606a1fc6 --- /dev/null +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamPropertyChangedEvent.ts @@ -0,0 +1,64 @@ +/* + * (C) Copyright 2017-2018 OpenVidu (https://openvidu.io/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Event } from './Event'; +import { Session } from '../../OpenVidu/Session'; +import { Stream } from '../../OpenVidu/Stream'; + +/** + * Defines event `streamPropertyChangedEvent` dispatched by [[Session]] + */ +export class StreamPropertyChangedEvent extends Event { + + /** + * The Stream whose property has changed + */ + stream: Stream; + + /** + * The property of the stream that changed. This value is either `"hasAudio"`, `"hasVideo"` or `"videoDimensions"` + */ + changedProperty: string; + + /** + * New value of the property (before change) + */ + newValue: Object; + + /** + * Previous value of the property (after change) + */ + oldValue: Object; + + /** + * @hidden + */ + constructor(target: Session, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object) { + super(false, target, 'streamPropertyChangedEvent'); + this.stream = stream; + this.changedProperty = changedProperty; + this.newValue = newValue; + this.oldValue = oldValue; + } + + /** + * @hidden + */ + // tslint:disable-next-line:no-empty + callDefaultBehaviour() { } + +} \ 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 5ddc84a6..a3c31bc1 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/VideoElementEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/VideoElementEvent.ts @@ -16,29 +16,25 @@ */ import { Event } from './Event'; -import { MediaManager } from '../../OpenVidu/MediaManager'; -import { Publisher } from '../../OpenVidu/Publisher'; -import { Subscriber } from '../../OpenVidu/Subscriber'; +import { StreamManager } from '../../OpenVidu/StreamManager'; /** * Defines the following events: - * - `videoElementCreated`: dispatched by [[Publisher]] and [[Subscriber]] - * - `videoElementDestroyed`: dispatched by [[Publisher]] and [[Subscriber]] - * - `videoPlaying`: dispatched by [[Publisher]] and [[Subscriber]] - * - `remoteVideoPlaying`: dispatched by [[Publisher]] if `Publisher.subscribeToRemote()` was called before `Session.publish(Publisher)` + * - `videoElementCreated`: dispatched by [[Publisher]] and [[Subscriber]] whenever a new HTML video element has been inserted into DOM + * - `videoElementDestroyed`: dispatched by [[Publisher]] and [[Subscriber]] whenever an HTML video element has been removed from DOM */ export class VideoElementEvent extends Event { /** - * Video element that was created, destroyed or started playing + * Video element that was created or destroyed */ element: HTMLVideoElement; /** * @hidden */ - constructor(element: HTMLVideoElement, target: MediaManager, type: string) { + constructor(element: HTMLVideoElement, target: StreamManager, type: string) { super(false, target, type); this.element = element; } diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts new file mode 100644 index 00000000..adbf93e8 --- /dev/null +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts @@ -0,0 +1,57 @@ +/* + * (C) Copyright 2017-2018 OpenVidu (https://openvidu.io/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Connection } from '../../../OpenVidu/Connection'; +import { StreamManager } from '../../../OpenVidu/StreamManager'; +import { VideoInsertMode } from '../../Enums/VideoInsertMode'; + + +export interface StreamManagerVideo { + + /** + * DOM video element displaying the StreamManager's stream + */ + video: HTMLVideoElement; + + /** + * `id` attribute of the DOM video element displaying the StreamManager's stream + */ + id: string; + + /** + * The DOM HTMLElement assigned as target element when creating a video for the StreamManager. This property is defined when: + * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter. + * - [[SessionManager.createVideoElement]] has been called. + * + * This property is undefined when: + * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter. + * - [[SessionManager.addVideoElement]] has been called. + */ + targetElement?: HTMLElement; + + /** + * How the DOM video element should be inserted with respect to `targetElement`. This property is defined when: + * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter. + * - [[SessionManager.createVideoElement]] has been called. + * + * This property is undefined when: + * - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter. + * - [[SessionManager.addVideoElement]] has been called. + */ + insertMode?: VideoInsertMode; + +} \ No newline at end of file diff --git a/openvidu-browser/src/index.ts b/openvidu-browser/src/index.ts index 5b654f7a..9e8c2591 100644 --- a/openvidu-browser/src/index.ts +++ b/openvidu-browser/src/index.ts @@ -2,7 +2,7 @@ export { OpenVidu } from './OpenVidu/OpenVidu'; export { Session } from './OpenVidu/Session'; export { Publisher } from './OpenVidu/Publisher'; export { Subscriber } from './OpenVidu/Subscriber'; -export { MediaManager } from './OpenVidu/MediaManager'; +export { StreamManager } from './OpenVidu/StreamManager'; export { Stream } from './OpenVidu/Stream'; export { Connection } from './OpenVidu/Connection'; export { LocalRecorder } from './OpenVidu/LocalRecorder'; @@ -18,6 +18,7 @@ export { RecordingEvent } from './OpenViduInternal/Events/RecordingEvent'; export { SessionDisconnectedEvent } from './OpenViduInternal/Events/SessionDisconnectedEvent'; 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 { Device } from './OpenViduInternal/Interfaces/Public/Device'; @@ -25,4 +26,5 @@ export { EventDispatcher } from './OpenViduInternal/Interfaces/Public/EventDispa export { OpenViduAdvancedConfiguration } from './OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration'; export { PublisherProperties } from './OpenViduInternal/Interfaces/Public/PublisherProperties'; export { SignalOptions } from './OpenViduInternal/Interfaces/Public/SignalOptions'; +export { StreamManagerVideo } from './OpenViduInternal/Interfaces/Public/StreamManagerVideo'; export { SubscriberProperties } from './OpenViduInternal/Interfaces/Public/SubscriberProperties'; \ No newline at end of file