openvidu-browser StreamManager API

pull/73/head
pabloFuente 2018-05-29 18:28:58 +02:00
parent 1beed6c0a4
commit 6684ff414c
16 changed files with 662 additions and 453 deletions

View File

@ -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`."

View File

@ -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)';
}
}

View File

@ -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)

View File

@ -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, <VideoInsertMode>this.properties.insertMode);
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
if (!!this.firstVideoElement) {
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
}
delete this.firstVideoElement;
resolve();
};

View File

@ -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, <VideoInsertMode>properties.insertMode);
});
if (!!subscriber.targetElement) {
stream.streamManager.createVideoElement(subscriber.targetElement, <VideoInsertMode>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) {

View File

@ -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();

View File

@ -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)';
}
}

View File

@ -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;
/**

View File

@ -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;

View File

@ -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();
}

View File

@ -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];

View File

@ -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() { }
}

View File

@ -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() { }
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';