mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser StreamManager API
parent
1beed6c0a4
commit
6684ff414c
|
@ -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`."
|
||||
|
|
|
@ -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)';
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)';
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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() { }
|
||||
|
||||
}
|
|
@ -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() { }
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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';
|
Loading…
Reference in New Issue