mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: MediaManager class parent of Publisher and Subscriber
parent
0dcc77b0c5
commit
d5e4482e37
|
@ -0,0 +1,302 @@
|
||||||
|
/*
|
||||||
|
* (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)';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -92,7 +92,7 @@ export class OpenVidu {
|
||||||
* 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 `videoPlaying` event once the local video starts playing (only if `videoElementCreated` event has been previously dispatched)
|
||||||
*
|
*
|
||||||
* @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
|
* @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 access the native MediaStream object by calling _Publisher.stream.getMediaStream()_ and use it as _srcObject_ of any HTML video element)
|
* (you can always call method [[Stream.addVideoElement]] for the object [[Publisher.stream]] to manage the video elements on your own)
|
||||||
* @param completionHandler `error` parameter is null if `initPublisher` succeeds, and is defined if it fails.
|
* @param completionHandler `error` parameter is null if `initPublisher` succeeds, and is defined if it fails.
|
||||||
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
|
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
|
||||||
*/
|
*/
|
||||||
|
@ -145,7 +145,7 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
publisher.emitEvent('accessAllowed', []);
|
publisher.emitEvent('accessAllowed', []);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (!!completionHandler !== undefined) {
|
if (completionHandler !== undefined) {
|
||||||
completionHandler(error);
|
completionHandler(error);
|
||||||
}
|
}
|
||||||
publisher.emitEvent('accessDenied', []);
|
publisher.emitEvent('accessDenied', []);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MediaManager } from './MediaManager';
|
||||||
import { OpenVidu } from './OpenVidu';
|
import { OpenVidu } from './OpenVidu';
|
||||||
import { Session } from './Session';
|
import { Session } from './Session';
|
||||||
import { Stream } from './Stream';
|
import { Stream } from './Stream';
|
||||||
|
@ -22,78 +23,49 @@ import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDisp
|
||||||
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
|
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
|
||||||
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
|
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
|
||||||
import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
|
import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
|
||||||
|
import { Event } from '../OpenViduInternal/Events/Event';
|
||||||
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
|
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
|
||||||
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
|
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
|
||||||
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
|
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
|
||||||
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
||||||
|
|
||||||
import EventEmitter = require('wolfy87-eventemitter');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method
|
* Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method
|
||||||
*/
|
*/
|
||||||
export class Publisher implements EventDispatcher {
|
export class Publisher extends MediaManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the Publisher has been granted access to the requested input devices or not
|
* Whether the Publisher has been granted access to the requested input devices or not
|
||||||
*/
|
*/
|
||||||
accessAllowed = false;
|
accessAllowed = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* HTML DOM element in which the Publisher's video has been inserted
|
|
||||||
*/
|
|
||||||
element: HTMLElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DOM id of the Publisher's video element
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [[Session]] to which the Publisher belongs
|
* The [[Session]] to which the Publisher belongs
|
||||||
*/
|
*/
|
||||||
session: Session; // Initialized by Session.publish(Publisher)
|
session: Session; // Initialized by Session.publish(Publisher)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [[Stream]] that you are publishing
|
* @hidden
|
||||||
*/
|
*/
|
||||||
stream: Stream;
|
accessDenied = false;
|
||||||
|
|
||||||
private ee = new EventEmitter();
|
|
||||||
|
|
||||||
|
private element?: HTMLElement;
|
||||||
private properties: PublisherProperties;
|
private properties: PublisherProperties;
|
||||||
private permissionDialogTimeout: NodeJS.Timer;
|
private permissionDialogTimeout: NodeJS.Timer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
constructor(targetElement: string | HTMLElement, properties: PublisherProperties, private openvidu: OpenVidu) {
|
constructor(targEl: string | HTMLElement, properties: PublisherProperties, private openvidu: OpenVidu) {
|
||||||
|
super(new Stream(new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.stream = new Stream(this.session, { publisherProperties: properties, mediaConstraints: {} });
|
|
||||||
|
|
||||||
this.stream.on('video-removed', (element: HTMLVideoElement) => {
|
this.stream.on('local-stream-destroyed-by-disconnect', (reason: string) => {
|
||||||
this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(element, this, 'videoElementDestroyed')]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.stream.on('stream-destroyed-by-disconnect', (reason: string) => {
|
|
||||||
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
|
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
|
||||||
this.ee.emitEvent('streamDestroyed', [streamEvent]);
|
this.ee.emitEvent('streamDestroyed', [streamEvent]);
|
||||||
streamEvent.callDefaultBehaviour();
|
streamEvent.callDefaultBehaviour();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (typeof targetElement === 'string') {
|
|
||||||
const e = document.getElementById(targetElement);
|
|
||||||
if (!!e) {
|
|
||||||
this.element = e;
|
|
||||||
}
|
|
||||||
} else if (targetElement instanceof HTMLElement) {
|
|
||||||
this.element = targetElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.element) {
|
|
||||||
console.warn("The provided 'targetElement' for the Publisher couldn't be resolved to any HTML element: " + targetElement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,18 +97,10 @@ export class Publisher implements EventDispatcher {
|
||||||
/**
|
/**
|
||||||
* See [[EventDispatcher.on]]
|
* See [[EventDispatcher.on]]
|
||||||
*/
|
*/
|
||||||
on(type: string, handler: (event: StreamEvent | VideoElementEvent) => void): EventDispatcher {
|
on(type: string, handler: (event: Event) => void): EventDispatcher {
|
||||||
this.ee.on(type, event => {
|
super.on(type, handler);
|
||||||
if (event) {
|
|
||||||
console.info("Event '" + type + "' triggered by 'Publisher'", event);
|
|
||||||
} else {
|
|
||||||
console.info("Event '" + type + "' triggered by 'Publisher'");
|
|
||||||
}
|
|
||||||
handler(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type === 'streamCreated') {
|
if (type === 'streamCreated') {
|
||||||
if (!!this.stream && this.stream.isPublisherPublished) {
|
if (!!this.stream && this.stream.isLocalStreamPublished) {
|
||||||
this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
|
this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
|
||||||
} else {
|
} else {
|
||||||
this.stream.on('stream-created-by-publisher', () => {
|
this.stream.on('stream-created-by-publisher', () => {
|
||||||
|
@ -144,38 +108,13 @@ export class Publisher implements EventDispatcher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'videoElementCreated') {
|
|
||||||
if (!!this.stream && this.stream.isVideoELementCreated) {
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoElementCreated')]);
|
|
||||||
} else {
|
|
||||||
this.stream.on('video-element-created-by-stream', (element) => {
|
|
||||||
this.id = element.id;
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(element.element, this, 'videoElementCreated')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'videoPlaying') {
|
|
||||||
const video = this.stream.getVideoElement();
|
|
||||||
if (!this.stream.displayMyRemote() && video &&
|
|
||||||
video.currentTime > 0 &&
|
|
||||||
video.paused === false &&
|
|
||||||
video.ended === false &&
|
|
||||||
video.readyState === 4) {
|
|
||||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoPlaying')]);
|
|
||||||
} else {
|
|
||||||
this.stream.on('video-is-playing', (element) => {
|
|
||||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(element.element, this, 'videoPlaying')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'remoteVideoPlaying') {
|
if (type === 'remoteVideoPlaying') {
|
||||||
const video = this.stream.getVideoElement();
|
if (this.stream.displayMyRemote() && this.video &&
|
||||||
if (this.stream.displayMyRemote() && video &&
|
this.video.currentTime > 0 &&
|
||||||
video.currentTime > 0 &&
|
this.video.paused === false &&
|
||||||
video.paused === false &&
|
this.video.ended === false &&
|
||||||
video.ended === false &&
|
this.video.readyState === 4) {
|
||||||
video.readyState === 4) {
|
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.video, this, 'remoteVideoPlaying')]);
|
||||||
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.stream.getVideoElement(), this, 'remoteVideoPlaying')]);
|
|
||||||
} else {
|
} else {
|
||||||
this.stream.on('remote-video-is-playing', (element) => {
|
this.stream.on('remote-video-is-playing', (element) => {
|
||||||
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(element.element, this, 'remoteVideoPlaying')]);
|
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(element.element, this, 'remoteVideoPlaying')]);
|
||||||
|
@ -183,16 +122,15 @@ export class Publisher implements EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'accessAllowed') {
|
if (type === 'accessAllowed') {
|
||||||
if (this.stream.accessIsAllowed) {
|
if (this.accessAllowed) {
|
||||||
this.ee.emitEvent('accessAllowed');
|
this.ee.emitEvent('accessAllowed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'accessDenied') {
|
if (type === 'accessDenied') {
|
||||||
if (this.stream.accessIsDenied) {
|
if (this.accessDenied) {
|
||||||
this.ee.emitEvent('accessDenied');
|
this.ee.emitEvent('accessDenied');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,18 +138,10 @@ export class Publisher implements EventDispatcher {
|
||||||
/**
|
/**
|
||||||
* See [[EventDispatcher.once]]
|
* See [[EventDispatcher.once]]
|
||||||
*/
|
*/
|
||||||
once(type: string, handler: (event: StreamEvent | VideoElementEvent) => void): Publisher {
|
once(type: string, handler: (event: Event) => void): Publisher {
|
||||||
this.ee.once(type, event => {
|
super.once(type, handler);
|
||||||
if (event) {
|
|
||||||
console.info("Event '" + type + "' triggered by 'Publisher'", event);
|
|
||||||
} else {
|
|
||||||
console.info("Event '" + type + "' triggered by 'Publisher'");
|
|
||||||
}
|
|
||||||
handler(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type === 'streamCreated') {
|
if (type === 'streamCreated') {
|
||||||
if (!!this.stream && this.stream.isPublisherPublished) {
|
if (!!this.stream && this.stream.isLocalStreamPublished) {
|
||||||
this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
|
this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
|
||||||
} else {
|
} else {
|
||||||
this.stream.once('stream-created-by-publisher', () => {
|
this.stream.once('stream-created-by-publisher', () => {
|
||||||
|
@ -219,38 +149,13 @@ export class Publisher implements EventDispatcher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'videoElementCreated') {
|
|
||||||
if (!!this.stream && this.stream.isVideoELementCreated) {
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoElementCreated')]);
|
|
||||||
} else {
|
|
||||||
this.stream.once('video-element-created-by-stream', (element) => {
|
|
||||||
this.id = element.id;
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(element.element, this, 'videoElementCreated')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'videoPlaying') {
|
|
||||||
const video = this.stream.getVideoElement();
|
|
||||||
if (!this.stream.displayMyRemote() && video &&
|
|
||||||
video.currentTime > 0 &&
|
|
||||||
video.paused === false &&
|
|
||||||
video.ended === false &&
|
|
||||||
video.readyState === 4) {
|
|
||||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoPlaying')]);
|
|
||||||
} else {
|
|
||||||
this.stream.once('video-is-playing', (element) => {
|
|
||||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(element.element, this, 'videoPlaying')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'remoteVideoPlaying') {
|
if (type === 'remoteVideoPlaying') {
|
||||||
const video = this.stream.getVideoElement();
|
if (this.stream.displayMyRemote() && this.video &&
|
||||||
if (this.stream.displayMyRemote() && video &&
|
this.video.currentTime > 0 &&
|
||||||
video.currentTime > 0 &&
|
this.video.paused === false &&
|
||||||
video.paused === false &&
|
this.video.ended === false &&
|
||||||
video.ended === false &&
|
this.video.readyState === 4) {
|
||||||
video.readyState === 4) {
|
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.video, this, 'remoteVideoPlaying')]);
|
||||||
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.stream.getVideoElement(), this, 'remoteVideoPlaying')]);
|
|
||||||
} else {
|
} else {
|
||||||
this.stream.once('remote-video-is-playing', (element) => {
|
this.stream.once('remote-video-is-playing', (element) => {
|
||||||
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(element.element, this, 'remoteVideoPlaying')]);
|
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(element.element, this, 'remoteVideoPlaying')]);
|
||||||
|
@ -258,29 +163,15 @@ export class Publisher implements EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'accessAllowed') {
|
if (type === 'accessAllowed') {
|
||||||
if (this.stream.accessIsAllowed) {
|
if (this.accessAllowed) {
|
||||||
this.ee.emitEvent('accessAllowed');
|
this.ee.emitEvent('accessAllowed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'accessDenied') {
|
if (type === 'accessDenied') {
|
||||||
if (this.stream.accessIsDenied) {
|
if (this.accessDenied) {
|
||||||
this.ee.emitEvent('accessDenied');
|
this.ee.emitEvent('accessDenied');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See [[EventDispatcher.off]]
|
|
||||||
*/
|
|
||||||
off(type: string, handler?: (event: StreamEvent | VideoElementEvent) => void): Publisher {
|
|
||||||
if (!handler) {
|
|
||||||
this.ee.removeAllListeners(type);
|
|
||||||
} else {
|
|
||||||
this.ee.off(type, handler);
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,14 +185,14 @@ export class Publisher implements EventDispatcher {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const errorCallback = (openViduError: OpenViduError) => {
|
const errorCallback = (openViduError: OpenViduError) => {
|
||||||
this.stream.accessIsDenied = true;
|
this.accessDenied = true;
|
||||||
this.stream.accessIsAllowed = false;
|
this.accessAllowed = false;
|
||||||
reject(openViduError);
|
reject(openViduError);
|
||||||
};
|
};
|
||||||
|
|
||||||
const successCallback = (mediaStream: MediaStream) => {
|
const successCallback = (mediaStream: MediaStream) => {
|
||||||
this.stream.accessIsAllowed = true;
|
this.accessAllowed = true;
|
||||||
this.stream.accessIsDenied = false;
|
this.accessDenied = false;
|
||||||
|
|
||||||
if (this.openvidu.isMediaStreamTrack(this.properties.audioSource)) {
|
if (this.openvidu.isMediaStreamTrack(this.properties.audioSource)) {
|
||||||
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
|
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
|
||||||
|
@ -322,7 +213,7 @@ export class Publisher implements EventDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stream.setMediaStream(mediaStream);
|
this.stream.setMediaStream(mediaStream);
|
||||||
this.stream.insertVideo(this.element, <VideoInsertMode>this.properties.insertMode);
|
this.insertVideo(this.targetElement, <VideoInsertMode>this.properties.insertMode);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
|
@ -185,7 +185,7 @@ export class Session implements EventDispatcher {
|
||||||
*
|
*
|
||||||
* @param stream Stream object to subscribe to
|
* @param stream Stream object to subscribe to
|
||||||
* @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Subscriber will be inserted (see [[SubscriberProperties.insertMode]]). If null or undefined no default video will be created for this Subscriber
|
* @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Subscriber will be inserted (see [[SubscriberProperties.insertMode]]). If null or undefined no default video will be created for this Subscriber
|
||||||
* (you can always access the native MediaStream object by calling _Subscriber.stream.getMediaStream()_ and use it as _srcObject_ of any HTML video element)
|
* (you can always call method [[Stream.addVideoElement]] for the object [[Subscriber.stream]] to manage the video elements on your own)
|
||||||
* @param completionHandler `error` parameter is null if `subscribe` succeeds, and is defined if it fails.
|
* @param completionHandler `error` parameter is null if `subscribe` succeeds, and is defined if it fails.
|
||||||
*/
|
*/
|
||||||
subscribe(stream: Stream, targetElement: string | HTMLElement, param3?: ((error: Error | undefined) => void) | SubscriberProperties, param4?: ((error: Error | undefined) => void)): Subscriber {
|
subscribe(stream: Stream, targetElement: string | HTMLElement, param3?: ((error: Error | undefined) => void) | SubscriberProperties, param4?: ((error: Error | undefined) => void)): Subscriber {
|
||||||
|
@ -226,7 +226,9 @@ export class Session implements EventDispatcher {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const subscriber = new Subscriber(stream, targetElement, properties);
|
const subscriber = new Subscriber(stream, targetElement, properties);
|
||||||
stream.insertVideo(subscriber.element, <VideoInsertMode>properties.insertMode);
|
stream.mediaManagers.forEach(mediaManager => {
|
||||||
|
mediaManager.insertVideo(subscriber.targetElement, <VideoInsertMode>properties.insertMode);
|
||||||
|
});
|
||||||
return subscriber;
|
return subscriber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,9 +274,9 @@ export class Session implements EventDispatcher {
|
||||||
|
|
||||||
console.info('Unsubscribing from ' + connectionId);
|
console.info('Unsubscribing from ' + connectionId);
|
||||||
|
|
||||||
this.openvidu.sendRequest('unsubscribeFromVideo', {
|
this.openvidu.sendRequest(
|
||||||
sender: subscriber.stream.connection.connectionId
|
'unsubscribeFromVideo',
|
||||||
},
|
{ sender: subscriber.stream.connection.connectionId },
|
||||||
(error, response) => {
|
(error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error unsubscribing from ' + connectionId, error);
|
console.error('Error unsubscribing from ' + connectionId, error);
|
||||||
|
@ -283,8 +285,9 @@ export class Session implements EventDispatcher {
|
||||||
}
|
}
|
||||||
subscriber.stream.disposeWebRtcPeer();
|
subscriber.stream.disposeWebRtcPeer();
|
||||||
subscriber.stream.disposeMediaStream();
|
subscriber.stream.disposeMediaStream();
|
||||||
});
|
}
|
||||||
subscriber.stream.removeVideo();
|
);
|
||||||
|
subscriber.stream.removeVideos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -307,7 +310,7 @@ export class Session implements EventDispatcher {
|
||||||
publisher.session = this;
|
publisher.session = this;
|
||||||
publisher.stream.session = this;
|
publisher.stream.session = this;
|
||||||
|
|
||||||
if (!publisher.stream.isPublisherPublished) {
|
if (!publisher.stream.isLocalStreamPublished) {
|
||||||
// 'Session.unpublish(Publisher)' has NOT been called
|
// 'Session.unpublish(Publisher)' has NOT been called
|
||||||
this.connection.addStream(publisher.stream);
|
this.connection.addStream(publisher.stream);
|
||||||
publisher.stream.publish()
|
publisher.stream.publish()
|
||||||
|
@ -788,7 +791,7 @@ export class Session implements EventDispatcher {
|
||||||
if (!!this.connection.stream) {
|
if (!!this.connection.stream) {
|
||||||
// Make Publisher object dispatch 'streamDestroyed' event (if there's a local stream)
|
// Make Publisher object dispatch 'streamDestroyed' event (if there's a local stream)
|
||||||
this.connection.stream.disposeWebRtcPeer();
|
this.connection.stream.disposeWebRtcPeer();
|
||||||
this.connection.stream.emitEvent('stream-destroyed-by-disconnect', [reason]);
|
this.connection.stream.emitEvent('local-stream-destroyed-by-disconnect', [reason]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.connection.disposed) {
|
if (!this.connection.disposed) {
|
||||||
|
|
|
@ -16,15 +16,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Connection } from './Connection';
|
import { Connection } from './Connection';
|
||||||
|
import { MediaManager } from './MediaManager';
|
||||||
import { Session } from './Session';
|
import { Session } from './Session';
|
||||||
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
|
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
|
||||||
import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
|
import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
|
||||||
import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats';
|
import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats';
|
||||||
import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent';
|
import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent';
|
||||||
|
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
||||||
|
|
||||||
import EventEmitter = require('wolfy87-eventemitter');
|
import EventEmitter = require('wolfy87-eventemitter');
|
||||||
|
|
||||||
import * as kurentoUtils from '../OpenViduInternal/KurentoUtils/kurento-utils-js';
|
import * as kurentoUtils from '../OpenViduInternal/KurentoUtils/kurento-utils-js';
|
||||||
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,13 +65,15 @@ export class Stream {
|
||||||
*/
|
*/
|
||||||
typeOfVideo?: string;
|
typeOfVideo?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of [[MediaManager]] objects displaying this stream in the DOM
|
||||||
|
*/
|
||||||
|
mediaManagers: MediaManager[] = [];
|
||||||
|
|
||||||
private ee = new EventEmitter();
|
private ee = new EventEmitter();
|
||||||
|
|
||||||
private webRtcPeer: any;
|
private webRtcPeer: any;
|
||||||
private mediaStream: MediaStream;
|
private mediaStream: MediaStream;
|
||||||
private video: HTMLVideoElement;
|
|
||||||
private targetElement: HTMLElement;
|
|
||||||
private parentId: string;
|
|
||||||
private webRtcStats: WebRtcStats;
|
private webRtcStats: WebRtcStats;
|
||||||
|
|
||||||
private isSubscribeToRemote = false;
|
private isSubscribeToRemote = false;
|
||||||
|
@ -77,23 +81,11 @@ export class Stream {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
isReadyToPublish = false;
|
isLocalStreamReadyToPublish = false;
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
isPublisherPublished = false;
|
isLocalStreamPublished = false;
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
isVideoELementCreated = false;
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
accessIsAllowed = false;
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
accessIsDenied = false;
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -149,11 +141,30 @@ export class Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on('mediastream-updated', () => {
|
this.on('mediastream-updated', () => {
|
||||||
if (this.video) this.video.srcObject = this.mediaStream;
|
this.mediaManagers.forEach(mediaManager => {
|
||||||
|
if (!!mediaManager.video) {
|
||||||
|
mediaManager.video.srcObject = this.mediaStream;
|
||||||
|
}
|
||||||
|
});
|
||||||
console.debug('Video srcObject [' + this.mediaStream + '] updated in stream [' + this.streamId + ']');
|
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 */
|
/* Hidden methods */
|
||||||
|
|
||||||
|
@ -186,13 +197,6 @@ export class Stream {
|
||||||
return this.webRtcPeer.peerConnection;
|
return this.webRtcPeer.peerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getVideoElement(): HTMLVideoElement {
|
|
||||||
return this.video;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -227,7 +231,7 @@ export class Stream {
|
||||||
*/
|
*/
|
||||||
publish(): Promise<any> {
|
publish(): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.isReadyToPublish) {
|
if (this.isLocalStreamReadyToPublish) {
|
||||||
this.initWebRtcPeerSend()
|
this.initWebRtcPeerSend()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -301,68 +305,6 @@ export class Stream {
|
||||||
this.ee.once(eventName, listener);
|
this.ee.once(eventName, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
insertVideo(targetElement?: HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement {
|
|
||||||
if (!!targetElement) {
|
|
||||||
|
|
||||||
this.video = document.createElement('video');
|
|
||||||
|
|
||||||
this.video.id = (this.isLocal() ? 'local-' : 'remote-') + 'video-' + this.streamId;
|
|
||||||
this.video.autoplay = true;
|
|
||||||
this.video.controls = false;
|
|
||||||
this.video.srcObject = this.mediaStream;
|
|
||||||
|
|
||||||
if (this.isLocal() && !this.displayMyRemote()) {
|
|
||||||
this.video.muted = true;
|
|
||||||
|
|
||||||
if (this.outboundStreamOpts.publisherProperties.mirror) {
|
|
||||||
this.mirrorVideo(this.video);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.video.oncanplay = () => {
|
|
||||||
console.info("Local 'Stream' with id [" + this.streamId + '] video is now playing');
|
|
||||||
this.ee.emitEvent('video-is-playing', [{
|
|
||||||
element: this.video
|
|
||||||
}]);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.video.title = this.streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.targetElement = targetElement;
|
|
||||||
this.parentId = targetElement.id;
|
|
||||||
|
|
||||||
const insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND;
|
|
||||||
this.insertElementWithMode(this.video, insMode);
|
|
||||||
|
|
||||||
this.ee.emitEvent('video-element-created-by-stream', [{
|
|
||||||
element: this.video
|
|
||||||
}]);
|
|
||||||
|
|
||||||
this.isVideoELementCreated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isReadyToPublish = true;
|
|
||||||
this.ee.emitEvent('stream-ready-to-publish');
|
|
||||||
|
|
||||||
return this.video;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
removeVideo(): void {
|
|
||||||
if (this.video) {
|
|
||||||
if (document.getElementById(this.parentId)) {
|
|
||||||
document.getElementById(this.parentId)!.removeChild(this.video);
|
|
||||||
this.ee.emitEvent('video-removed', [this.video]);
|
|
||||||
}
|
|
||||||
delete this.video;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -445,6 +387,15 @@ export class Stream {
|
||||||
this.speechEvent = undefined;
|
this.speechEvent = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
removeVideos(): void {
|
||||||
|
this.mediaManagers.forEach(mediaManager => {
|
||||||
|
mediaManager.removeVideo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Private methods */
|
/* Private methods */
|
||||||
|
|
||||||
|
@ -510,7 +461,7 @@ export class Stream {
|
||||||
this.webRtcPeer.generateOffer(successCallback);
|
this.webRtcPeer.generateOffer(successCallback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.isPublisherPublished = true;
|
this.isLocalStreamPublished = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,24 +539,9 @@ export class Stream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!this.video) {
|
this.mediaManagers.forEach(mediaManager => {
|
||||||
// let thumbnailId = this.video.thumb;
|
mediaManager.addOnCanPlayEvent();
|
||||||
this.video.oncanplay = () => {
|
});
|
||||||
if (this.isLocal() && this.displayMyRemote()) {
|
|
||||||
console.info("Your own remote 'Stream' with id [" + this.streamId + '] video is now playing');
|
|
||||||
this.ee.emitEvent('remote-video-is-playing', [{
|
|
||||||
element: this.video
|
|
||||||
}]);
|
|
||||||
} else if (!this.isLocal() && !this.displayMyRemote()) {
|
|
||||||
console.info("Remote 'Stream' with id [" + this.streamId + '] video is now playing');
|
|
||||||
this.ee.emitEvent('video-is-playing', [{
|
|
||||||
element: this.video
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
// show(thumbnailId);
|
|
||||||
// this.hideSpinner(this.streamId);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.session.emitEvent('stream-subscribed', [{
|
this.session.emitEvent('stream-subscribed', [{
|
||||||
stream: this
|
stream: this
|
||||||
}]);
|
}]);
|
||||||
|
@ -631,38 +567,12 @@ export class Stream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isLocal(): boolean {
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
isLocal(): boolean {
|
||||||
// inbound options undefined and outbound options defined
|
// inbound options undefined and outbound options defined
|
||||||
return (!this.inboundStreamOpts && !!this.outboundStreamOpts);
|
return (!this.inboundStreamOpts && !!this.outboundStreamOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private insertElementWithMode(element: HTMLElement, insertMode: VideoInsertMode): void {
|
|
||||||
if (!!this.targetElement) {
|
|
||||||
switch (insertMode) {
|
|
||||||
case VideoInsertMode.AFTER:
|
|
||||||
this.targetElement.parentNode!!.insertBefore(element, this.targetElement.nextSibling);
|
|
||||||
break;
|
|
||||||
case VideoInsertMode.APPEND:
|
|
||||||
this.targetElement.appendChild(element);
|
|
||||||
break;
|
|
||||||
case VideoInsertMode.BEFORE:
|
|
||||||
this.targetElement.parentNode!!.insertBefore(element, this.targetElement);
|
|
||||||
break;
|
|
||||||
case VideoInsertMode.PREPEND:
|
|
||||||
this.targetElement.insertBefore(element, this.targetElement.childNodes[0]);
|
|
||||||
break;
|
|
||||||
case VideoInsertMode.REPLACE:
|
|
||||||
this.targetElement.parentNode!!.replaceChild(element, this.targetElement);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.insertElementWithMode(element, VideoInsertMode.APPEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private mirrorVideo(video: HTMLVideoElement): void {
|
|
||||||
video.style.transform = 'rotateY(180deg)';
|
|
||||||
video.style.webkitTransform = 'rotateY(180deg)';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -15,56 +15,27 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MediaManager } from './MediaManager';
|
||||||
import { Stream } from './Stream';
|
import { Stream } from './Stream';
|
||||||
import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
|
import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
|
||||||
import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher';
|
|
||||||
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
|
|
||||||
import EventEmitter = require('wolfy87-eventemitter');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method
|
* Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method
|
||||||
*/
|
*/
|
||||||
export class Subscriber implements EventDispatcher {
|
export class Subscriber extends MediaManager {
|
||||||
|
|
||||||
/**
|
|
||||||
* HTML DOM element in which the Subscriber's video has been inserted
|
|
||||||
*/
|
|
||||||
element: HTMLElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DOM id of the Subscriber's video element
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The [[Stream]] to which you are subscribing
|
|
||||||
*/
|
|
||||||
stream: Stream;
|
|
||||||
|
|
||||||
private ee = new EventEmitter();
|
|
||||||
|
|
||||||
|
private element?: HTMLElement;
|
||||||
private properties: SubscriberProperties;
|
private properties: SubscriberProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
constructor(stream: Stream, targetElement: string | HTMLElement, properties: SubscriberProperties) {
|
constructor(stream: Stream, targEl: string | HTMLElement, properties: SubscriberProperties) {
|
||||||
|
super(stream, targEl);
|
||||||
|
this.element = this.targetElement;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
|
||||||
if (typeof targetElement === 'string') {
|
|
||||||
const e = document.getElementById(targetElement);
|
|
||||||
if (!!e) {
|
|
||||||
this.element = e;
|
|
||||||
}
|
|
||||||
} else if (targetElement instanceof HTMLElement) {
|
|
||||||
this.element = targetElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stream.once('video-removed', (element: HTMLVideoElement) => {
|
|
||||||
this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(element, this, 'videoElementDestroyed')]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,101 +62,4 @@ export class Subscriber implements EventDispatcher {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See [[EventDispatcher.on]]
|
|
||||||
*/
|
|
||||||
on(type: string, handler: (event: VideoElementEvent) => void): EventDispatcher {
|
|
||||||
this.ee.on(type, event => {
|
|
||||||
if (event) {
|
|
||||||
console.info("Event '" + type + "' triggered by 'Subscriber'", event);
|
|
||||||
} else {
|
|
||||||
console.info("Event '" + type + "' triggered by 'Subscriber'");
|
|
||||||
}
|
|
||||||
handler(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type === 'videoElementCreated') {
|
|
||||||
if (this.stream.isVideoELementCreated) {
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoElementCreated')]);
|
|
||||||
} else {
|
|
||||||
this.stream.once('video-element-created-by-stream', element => {
|
|
||||||
this.id = element.id;
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(element, this, 'videoElementCreated')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'videoPlaying') {
|
|
||||||
const video = this.stream.getVideoElement();
|
|
||||||
if (!this.stream.displayMyRemote() && video &&
|
|
||||||
video.currentTime > 0 &&
|
|
||||||
video.paused === false &&
|
|
||||||
video.ended === false &&
|
|
||||||
video.readyState === 4) {
|
|
||||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoPlaying')]);
|
|
||||||
} else {
|
|
||||||
this.stream.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: VideoElementEvent) => void): Subscriber {
|
|
||||||
this.ee.once(type, event => {
|
|
||||||
if (event) {
|
|
||||||
console.info("Event '" + type + "' triggered once by 'Subscriber'", event);
|
|
||||||
} else {
|
|
||||||
console.info("Event '" + type + "' triggered once by 'Subscriber'");
|
|
||||||
}
|
|
||||||
handler(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type === 'videoElementCreated') {
|
|
||||||
if (this.stream.isVideoELementCreated) {
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoElementCreated')]);
|
|
||||||
} else {
|
|
||||||
this.stream.once('video-element-created-by-stream', element => {
|
|
||||||
this.id = element.id;
|
|
||||||
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(element, this, 'videoElementCreated')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'videoPlaying') {
|
|
||||||
const video = this.stream.getVideoElement();
|
|
||||||
if (!this.stream.displayMyRemote() && video &&
|
|
||||||
video.currentTime > 0 &&
|
|
||||||
video.paused === false &&
|
|
||||||
video.ended === false &&
|
|
||||||
video.readyState === 4) {
|
|
||||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.stream.getVideoElement(), this, 'videoPlaying')]);
|
|
||||||
} else {
|
|
||||||
this.stream.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: VideoElementEvent) => void): Subscriber {
|
|
||||||
if (!handler) {
|
|
||||||
this.ee.removeAllListeners(type);
|
|
||||||
} else {
|
|
||||||
this.ee.off(type, handler);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -15,9 +15,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Publisher } from '../../OpenVidu/Publisher';
|
import { MediaManager } from '../../OpenVidu/MediaManager';
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
import { Subscriber } from '../../OpenVidu/Subscriber';
|
|
||||||
|
|
||||||
export abstract class Event {
|
export abstract class Event {
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ export abstract class Event {
|
||||||
/**
|
/**
|
||||||
* The object that dispatched the event
|
* The object that dispatched the event
|
||||||
*/
|
*/
|
||||||
target: Session | Subscriber | Publisher;
|
target: Session | MediaManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of event. This is the same string you pass as first parameter when calling method `on()` of any object implementing [[EventDispatcher]] interface
|
* The type of event. This is the same string you pass as first parameter when calling method `on()` of any object implementing [[EventDispatcher]] interface
|
||||||
|
@ -41,7 +40,7 @@ export abstract class Event {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
constructor(cancelable, target, type) {
|
constructor(cancelable: boolean, target: Session | MediaManager, type: string) {
|
||||||
this.cancelable = cancelable;
|
this.cancelable = cancelable;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class SessionDisconnectedEvent extends Event {
|
||||||
if (!!session.remoteConnections[connectionId].stream) {
|
if (!!session.remoteConnections[connectionId].stream) {
|
||||||
session.remoteConnections[connectionId].stream.disposeWebRtcPeer();
|
session.remoteConnections[connectionId].stream.disposeWebRtcPeer();
|
||||||
session.remoteConnections[connectionId].stream.disposeMediaStream();
|
session.remoteConnections[connectionId].stream.disposeMediaStream();
|
||||||
session.remoteConnections[connectionId].stream.removeVideo();
|
session.remoteConnections[connectionId].stream.removeVideos();
|
||||||
delete session.remoteStreamsCreated[session.remoteConnections[connectionId].stream.streamId];
|
delete session.remoteStreamsCreated[session.remoteConnections[connectionId].stream.streamId];
|
||||||
session.remoteConnections[connectionId].dispose();
|
session.remoteConnections[connectionId].dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ export class StreamEvent extends Event {
|
||||||
// Remote Stream
|
// Remote Stream
|
||||||
this.stream.disposeWebRtcPeer();
|
this.stream.disposeWebRtcPeer();
|
||||||
this.stream.disposeMediaStream();
|
this.stream.disposeMediaStream();
|
||||||
this.stream.removeVideo();
|
this.stream.removeVideos();
|
||||||
|
|
||||||
} else if (this.target instanceof Publisher) {
|
} else if (this.target instanceof Publisher) {
|
||||||
|
|
||||||
|
@ -73,8 +73,8 @@ export class StreamEvent extends Event {
|
||||||
|
|
||||||
// Local Stream
|
// Local Stream
|
||||||
this.stream.disposeMediaStream();
|
this.stream.disposeMediaStream();
|
||||||
this.stream.removeVideo();
|
this.stream.removeVideos();
|
||||||
this.stream.isReadyToPublish = false;
|
this.stream.isLocalStreamReadyToPublish = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete stream from Session.remoteStreamsCreated map
|
// Delete stream from Session.remoteStreamsCreated map
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Event } from './Event';
|
import { Event } from './Event';
|
||||||
|
import { MediaManager } from '../../OpenVidu/MediaManager';
|
||||||
import { Publisher } from '../../OpenVidu/Publisher';
|
import { Publisher } from '../../OpenVidu/Publisher';
|
||||||
import { Subscriber } from '../../OpenVidu/Subscriber';
|
import { Subscriber } from '../../OpenVidu/Subscriber';
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ export class VideoElementEvent extends Event {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
constructor(element: HTMLVideoElement, target: Publisher | Subscriber, type: string) {
|
constructor(element: HTMLVideoElement, target: MediaManager, type: string) {
|
||||||
super(false, target, type);
|
super(false, target, type);
|
||||||
this.element = element;
|
this.element = element;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,17 +21,23 @@ export interface EventDispatcher {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds function `handler` to handle event `type`
|
* Adds function `handler` to handle event `type`
|
||||||
|
*
|
||||||
|
* @returns The EventDispatcher object
|
||||||
*/
|
*/
|
||||||
on(type: string, handler: (event: Event) => void): EventDispatcher;
|
on(type: string, handler: (event: Event) => void): EventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
|
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
|
||||||
|
*
|
||||||
|
* @returns The object that dispatched the event
|
||||||
*/
|
*/
|
||||||
once(type: string, handler: (event: Event) => void): any;
|
once(type: string, handler: (event: Event) => void): Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
|
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
|
||||||
|
*
|
||||||
|
* @returns The object that dispatched the event
|
||||||
*/
|
*/
|
||||||
off(type: string, handler?: (event: Event) => void): any;
|
off(type: string, handler?: (event: Event) => void): Object;
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ export { OpenVidu } from './OpenVidu/OpenVidu';
|
||||||
export { Session } from './OpenVidu/Session';
|
export { Session } from './OpenVidu/Session';
|
||||||
export { Publisher } from './OpenVidu/Publisher';
|
export { Publisher } from './OpenVidu/Publisher';
|
||||||
export { Subscriber } from './OpenVidu/Subscriber';
|
export { Subscriber } from './OpenVidu/Subscriber';
|
||||||
|
export { MediaManager } from './OpenVidu/MediaManager';
|
||||||
export { Stream } from './OpenVidu/Stream';
|
export { Stream } from './OpenVidu/Stream';
|
||||||
export { Connection } from './OpenVidu/Connection';
|
export { Connection } from './OpenVidu/Connection';
|
||||||
export { LocalRecorder } from './OpenVidu/LocalRecorder';
|
export { LocalRecorder } from './OpenVidu/LocalRecorder';
|
||||||
|
|
Loading…
Reference in New Issue