diff --git a/openvidu-browser/old/OldConnection.ts b/openvidu-browser/old/OldConnection.ts
new file mode 100644
index 00000000000..327c920be9d
--- /dev/null
+++ b/openvidu-browser/old/OldConnection.ts
@@ -0,0 +1,226 @@
+/*
+ * (C) Copyright 2017-2022 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 { OldSession as Session } from './OldSession';
+import { Stream } from './OldStream';
+import { LocalConnectionOptions } from '../../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
+import { RemoteConnectionOptions } from '../../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
+import { InboundStreamOptions } from '../../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
+import { StreamOptionsServer } from '../../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
+import { OpenViduLogger } from '../../OpenViduInternal/Logger/OpenViduLogger';
+import { ExceptionEvent, ExceptionEventName } from '../../OpenViduInternal/Events/ExceptionEvent';
+
+/**
+ * @hidden
+ */
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+
+/**
+ * Represents each one of the user's connection to the session (the local one and other user's connections).
+ * Therefore each {@link Session} and {@link Stream} object has an attribute of type Connection
+ */
+export class OldConnection {
+ /**
+ * Unique identifier of the connection
+ */
+ connectionId: string;
+
+ /**
+ * Time when this connection was created in OpenVidu Server (UTC milliseconds)
+ */
+ creationTime: number;
+
+ /**
+ * Data associated to this connection (and therefore to certain user). This is an important field:
+ * it allows you to broadcast all the information you want for each user (a username, for example)
+ */
+ data: string;
+
+ /**
+ * Role of the connection.
+ * - `SUBSCRIBER`: can subscribe to published Streams of other users by calling {@link Session.subscribe}
+ * - `PUBLISHER`: SUBSCRIBER permissions + can publish their own Streams by calling {@link Session.publish}
+ * - `MODERATOR`: SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection by call {@link Session.forceUnpublish} and {@link Session.forceDisconnect}
+ *
+ * **Only defined for the local connection. In remote connections will be `undefined`**
+ */
+ role: string;
+
+ /**
+ * Whether the streams published by this Connection will be recorded or not. This only affects [INDIVIDUAL recording](/en/stable/advanced-features/recording/#individual-recording-selection) PRO
+ *
+ * **Only defined for the local connection. In remote connections will be `undefined`**
+ */
+ record: boolean;
+
+ /**
+ * @hidden
+ */
+ stream?: Stream;
+
+ /**
+ * @hidden
+ */
+ localOptions: LocalConnectionOptions | undefined;
+
+ /**
+ * @hidden
+ */
+ remoteOptions: RemoteConnectionOptions | undefined;
+
+ /**
+ * @hidden
+ */
+ disposed = false;
+
+ /**
+ * @hidden
+ */
+ rpcSessionId: string;
+
+ /**
+ * @hidden
+ */
+ constructor(protected session: Session, connectionOptions: LocalConnectionOptions | RemoteConnectionOptions) {
+ console.warn("'Connection' class is not ready yet.");
+ let msg = "'Connection' created ";
+ if (!!(connectionOptions).role) {
+ // Connection is local
+ this.localOptions = connectionOptions;
+ this.connectionId = this.localOptions.id;
+ this.creationTime = this.localOptions.createdAt;
+ this.data = this.localOptions.metadata;
+ // this.rpcSessionId = this.localOptions.sessionId;
+ this.role = this.localOptions.role;
+ this.record = this.localOptions.record;
+ msg += '(local)';
+ } else {
+ // Connection is remote
+ this.remoteOptions = connectionOptions;
+ this.connectionId = this.remoteOptions.id;
+ this.creationTime = this.remoteOptions.createdAt;
+ if (this.remoteOptions.metadata) {
+ this.data = this.remoteOptions.metadata;
+ }
+ if (this.remoteOptions.streams) {
+ this.initRemoteStreams(this.remoteOptions.streams);
+ }
+ msg += "(remote) with 'connectionId' [" + this.remoteOptions.id + ']';
+ }
+ logger.info(msg);
+ }
+
+ /* Hidden methods */
+
+ /**
+ * @hidden
+ */
+ sendIceCandidate(candidate: RTCIceCandidate): void {
+ throw new Error("'sendIceCandidate' method is not ready yet.");
+
+ if (!this.disposed) {
+ logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' + this.connectionId, candidate);
+
+ this.session.openvidu.sendRequest(
+ 'onIceCandidate',
+ {
+ endpointName: this.connectionId,
+ candidate: candidate.candidate,
+ sdpMid: candidate.sdpMid,
+ sdpMLineIndex: candidate.sdpMLineIndex
+ },
+ (error, response) => {
+ if (error) {
+ logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
+ this.session.emitEvent('exception', [
+ new ExceptionEvent(
+ this.session,
+ ExceptionEventName.ICE_CANDIDATE_ERROR,
+ this.session,
+ 'There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side',
+ error
+ )
+ ]);
+ }
+ }
+ );
+ } else {
+ logger.warn(`Connection ${this.connectionId} disposed when trying to send an ICE candidate. ICE candidate not sent`);
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ initRemoteStreams(options: StreamOptionsServer[]): void {
+
+ console.warn("'initRemoteStreams' method is not ready yet.");
+
+ // This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
+ // this.stream should also be replaced by a collection of streams to support multiple streams per Connection
+ options.forEach((opts) => {
+ const streamOptions: InboundStreamOptions = {
+ id: opts.id,
+ createdAt: opts.createdAt,
+ connection: this,
+ hasAudio: opts.hasAudio,
+ hasVideo: opts.hasVideo,
+ audioActive: opts.audioActive,
+ videoActive: opts.videoActive,
+ typeOfVideo: opts.typeOfVideo,
+ frameRate: opts.frameRate,
+ videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined,
+ filter: !!opts.filter ? opts.filter : undefined
+ };
+ const stream = new Stream(this.session, streamOptions);
+
+ this.addStream(stream);
+ });
+
+ logger.info(
+ "Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ',
+ this.stream!.inboundStreamOpts
+ );
+ }
+
+ /**
+ * @hidden
+ */
+ addStream(stream: Stream): void {
+ stream.connection = this;
+ this.stream = stream;
+ }
+
+ /**
+ * @hidden
+ */
+ removeStream(): void {
+
+ // throw new Error("'removeStream' method is not ready yet.");
+ delete this.stream;
+ }
+
+ /**
+ * @hidden
+ */
+ dispose(): void {
+
+ // throw new Error("'dispose' method is not ready yet.");
+ this.disposed = true;
+ this.removeStream();
+ }
+}
diff --git a/openvidu-browser/old/OldOpenVidu.ts b/openvidu-browser/old/OldOpenVidu.ts
new file mode 100644
index 00000000000..e7cfc57f51e
--- /dev/null
+++ b/openvidu-browser/old/OldOpenVidu.ts
@@ -0,0 +1,1423 @@
+/*
+ * (C) Copyright 2017-2022 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 { LocalRecorder } from '../LocalRecorder';
+import { Publisher } from './OldPublisher';
+import { Session } from '../Session';
+import { Stream } from './OldStream';
+import { SessionDisconnectedEvent } from '../../OpenViduInternal/Events/SessionDisconnectedEvent';
+import { StreamPropertyChangedEvent } from '../../OpenViduInternal/Events/StreamPropertyChangedEvent';
+import { Device } from '../../OpenViduInternal/Interfaces/Public/Device';
+import { OpenViduAdvancedConfiguration } from '../../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
+import { PublisherProperties } from '../../OpenViduInternal/Interfaces/Public/PublisherProperties';
+import { CustomMediaStreamConstraints } from '../../OpenViduInternal/Interfaces/Private/CustomMediaStreamConstraints';
+import { OpenViduError, OpenViduErrorName } from '../../OpenViduInternal/Enums/OpenViduError';
+import { VideoInsertMode } from '../../OpenViduInternal/Enums/VideoInsertMode';
+import { OpenViduLogger } from '../../OpenViduInternal/Logger/OpenViduLogger';
+import { PlatformUtils } from '../../OpenViduInternal/Utils/Platform';
+import { StreamPropertyChangedEventReason, ChangedPropertyType } from '../../OpenViduInternal/Events/Types/Types';
+
+import * as screenSharingAuto from '../../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
+import * as screenSharing from '../../OpenViduInternal/ScreenSharing/Screen-Capturing';
+import { OpenViduLoggerConfiguration } from '../../OpenViduInternal/Logger/OpenViduLoggerConfiguration';
+
+/**
+ * @hidden
+ */
+import EventEmitter = require('wolfy87-eventemitter');
+/**
+ * @hidden
+ */
+import RpcBuilder = require('../../OpenViduInternal/KurentoUtils/kurento-jsonrpc');
+import { AudioCaptureOptions, CreateLocalTracksOptions, LocalTrack, Room, VideoCaptureOptions, createLocalTracks } from 'livekit-client';
+
+/**
+ * @hidden
+ */
+const packageJson = require('../../package.json');
+/**
+ * @hidden
+ */
+declare var cordova: any;
+/**
+ * @hidden
+ */
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+
+/**
+ * @hidden
+ */
+let platform: PlatformUtils;
+
+/**
+ * Entrypoint of OpenVidu Browser library.
+ * Use it to initialize objects of type {@link Session}, {@link Publisher} and {@link LocalRecorder}
+ */
+export class OpenVidu {
+ private jsonRpcClient: any;
+ private masterNodeHasCrashed = false;
+
+ /**
+ * @hidden
+ */
+ session: Session;
+
+ /**
+ * @hidden
+ */
+ publishers: Publisher[] = [];
+ /**
+ * @hidden
+ */
+ wsUri: string;
+ /**
+ * @hidden
+ */
+ httpUri: string;
+ /**
+ * @hidden
+ */
+ secret = '';
+ /**
+ * @hidden
+ */
+ recorder = false;
+ /**
+ * @hidden
+ */
+ stt = false;
+ /**
+ * @hidden
+ */
+ iceServers: RTCIceServer[];
+ /**
+ * @hidden
+ */
+ role: string;
+ /**
+ * @hidden
+ */
+ finalUserId: string;
+ /**
+ * @hidden
+ */
+ mediaServer: string;
+ /**
+ * @hidden
+ */
+ videoSimulcast: boolean;
+ /**
+ * @hidden
+ */
+ life: number = -1;
+ /**
+ * @hidden
+ */
+ advancedConfiguration: OpenViduAdvancedConfiguration = {};
+ /**
+ * @hidden
+ */
+ webrtcStatsInterval: number = -1;
+ /**
+ * @hidden
+ */
+ sendBrowserLogs: OpenViduLoggerConfiguration = OpenViduLoggerConfiguration.disabled;
+ /**
+ * @hidden
+ */
+ isAtLeastPro: boolean = false;
+ /**
+ * @hidden
+ */
+ isEnterprise: boolean = false;
+ /**
+ * @hidden
+ */
+ libraryVersion: string;
+ /**
+ * @hidden
+ */
+ ee = new EventEmitter();
+
+ constructor() {
+ console.warn("'OpenVidu' class is not ready yet.");
+ console.log(
+ `
+ %c🚨 Important: Migration to OpenVidu Browser v3 🚨%c
+
+ Version 3 of OpenVidu Browser has been released to ease the transition from v2 to v3. However, it should be noted that this version is not a long-term solution, and we strongly encourage a full migration to the latest version of OpenVidu Browser.
+ `,
+ 'font-weight: bold; font-size: 20px; color: red;',
+ 'font-weight: bold; font-size: 15px;'
+ );
+
+ platform = PlatformUtils.getInstance();
+ this.libraryVersion = packageJson.version;
+ logger.info('OpenVidu initialized');
+ logger.info('Platform detected: ' + platform.getDescription());
+ logger.info('openvidu-browser version: ' + this.libraryVersion);
+
+ // if (platform.isMobileDevice() || platform.isReactNative()) {
+ // // Listen to orientationchange only on mobile devices
+ // this.onOrientationChanged(() => {
+ // this.publishers.forEach((publisher) => {
+ // if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
+ // this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
+ // }
+ // });
+ // });
+ // }
+ }
+
+ /**
+ * Returns new session
+ */
+ initSession(): Session {
+ this.session = new Session(this);
+ return this.session;
+ }
+
+ /**
+ *
+ * !WARNING: This method now requires a Promise to be returned.
+ *
+ */
+ async initPublisher(targetElement: string | HTMLElement | undefined): Promise;
+ async initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Promise;
+ async initPublisher(
+ targetElement: string | HTMLElement | undefined,
+ completionHandler: (error: Error | undefined) => void
+ ): Promise;
+ async initPublisher(
+ targetElement: string | HTMLElement | undefined,
+ properties: PublisherProperties,
+ completionHandler: (error: Error | undefined) => void
+ ): Promise;
+
+ /**
+ * Returns a new Promise
+ *
+ * #### Events dispatched
+ *
+ * The {@link Publisher} object will dispatch an `accessDialogOpened` event, only if the pop-up shown by the browser to request permissions for the camera is opened. You can use this event to alert the user about granting permissions
+ * for your website. An `accessDialogClosed` event will also be dispatched after user clicks on "Allow" or "Block" in the pop-up.
+ *
+ * The {@link Publisher} object will dispatch an `accessAllowed` or `accessDenied` event once it has been granted access to the requested input devices or not.
+ *
+ * The {@link Publisher} object will dispatch a `videoElementCreated` event once a HTML video element has been added to DOM (only if you
+ * [let OpenVidu take care of the video players](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)). See {@link VideoElementEvent} to learn more.
+ *
+ * The {@link Publisher} object will dispatch a `streamPlaying` event once the local streams starts playing. See {@link StreamManagerEvent} to learn more.
+ *
+ * @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher will be inserted (see {@link PublisherProperties.insertMode}). If *null* or *undefined* no default video will be created for this Publisher.
+ * You can always call method {@link Publisher.addVideoElement} or {@link Publisher.createVideoElement} to manage the video elements on your own (see [Manage video players](/en/stable/cheatsheet/manage-videos) section)
+ * @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
+ */
+ async initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Promise {
+
+ let ovPublisherProperties: PublisherProperties = {};
+
+ // Check if param2 exists and is not a function
+ if (param2 && typeof param2 !== 'function') {
+ // Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
+ ovPublisherProperties = param2;
+ }
+
+ // Initialize completionHandler with a default empty function
+ let completionHandler: (error: Error | undefined) => void =
+ typeof param2 === 'function'
+ ? param2 // If param2 is a function, use it as completionHandler
+ : param3 || (() => {}); // If param3 exists, use it as completionHandler; otherwise, use a default empty function
+
+ // Create a new Publisher instance with the specified target element, properties, and context (this)
+ //TODO Parsear las ovPublisherProperties
+ const publisher = new Publisher(targetElement, ovPublisherProperties, this);
+ try {
+ await publisher.initialize();
+ completionHandler(undefined);
+ console.log('publisher initialized');
+ publisher.emitEvent('accessAllowed', []);
+ } catch (error) {
+ // if (completionHandler !== undefined) {
+ completionHandler(error);
+ // }
+ console.error('publisher initialized error', error);
+ publisher.emitEvent('accessDenied', [error]);
+ } finally {
+ this.publishers.push(publisher);
+ return publisher;
+ }
+
+ // let properties: PublisherProperties;
+
+ // if (!!param2 && typeof param2 !== 'function') {
+ // // Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
+
+ // properties = param2;
+
+ // properties = {
+ // audioSource: typeof properties.audioSource !== 'undefined' ? properties.audioSource : undefined,
+ // frameRate:
+ // typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
+ // ? undefined
+ // : typeof properties.frameRate !== 'undefined'
+ // ? properties.frameRate
+ // : undefined,
+ // insertMode:
+ // typeof properties.insertMode !== 'undefined'
+ // ? typeof properties.insertMode === 'string'
+ // ? VideoInsertMode[properties.insertMode]
+ // : properties.insertMode
+ // : VideoInsertMode.APPEND,
+ // mirror: typeof properties.mirror !== 'undefined' ? properties.mirror : true,
+ // publishAudio: typeof properties.publishAudio !== 'undefined' ? properties.publishAudio : true,
+ // publishVideo: typeof properties.publishVideo !== 'undefined' ? properties.publishVideo : true,
+ // resolution:
+ // typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
+ // ? undefined
+ // : typeof properties.resolution !== 'undefined'
+ // ? properties.resolution
+ // : '640x480',
+ // videoSource: typeof properties.videoSource !== 'undefined' ? properties.videoSource : undefined,
+ // videoSimulcast: properties.videoSimulcast,
+ // filter: properties.filter
+ // };
+ // } else {
+ // // Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
+
+ // properties = {
+ // insertMode: VideoInsertMode.APPEND,
+ // mirror: true,
+ // publishAudio: true,
+ // publishVideo: true,
+ // resolution: '640x480'
+ // };
+ // }
+
+ }
+
+ /**
+ * Promisified version of {@link OpenVidu.initPublisher}
+ *
+ * > WARNING: events `accessDialogOpened` and `accessDialogClosed` will not be dispatched if using this method instead of {@link OpenVidu.initPublisher}
+ */
+ initPublisherAsync(targetElement: string | HTMLElement | undefined): Promise;
+ initPublisherAsync(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Promise;
+
+ initPublisherAsync(targetElement: string | HTMLElement | undefined, properties?: PublisherProperties): Promise {
+ if (!!properties) {
+ return this.initPublisher(targetElement, properties);
+ } else {
+ return this.initPublisher(targetElement);
+ }
+ // throw new Error("'initPublisherAsync' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // let publisher: Publisher;
+
+ // const callback = (error: Error) => {
+ // if (!!error) {
+ // return reject(error);
+ // } else {
+ // return resolve(publisher);
+ // }
+ // };
+
+ // if (!!properties) {
+ // return this.initPublisher(targetElement, properties);
+ // } else {
+ // return this.initPublisher(targetElement);
+ // }
+ // });
+ }
+
+ /**
+ * Returns a new local recorder for recording streams straight away from the browser
+ * @param stream Stream to record
+ */
+ initLocalRecorder(stream: Stream): LocalRecorder {
+ throw new Error("'initLocalRecorder' method is not ready yet.");
+
+ return new LocalRecorder(stream);
+ }
+
+ /**
+ * Checks if the browser supports OpenVidu
+ * @returns 1 if the browser supports OpenVidu, 0 otherwise
+ */
+ checkSystemRequirements(): boolean {
+ // throw new Error("'checkSystemRequirements' method is not ready yet.");
+ // Specific iOS platform support (iPhone, iPad)
+ if (platform.isIPhoneOrIPad()) {
+ return (
+ platform.isIOSWithSafari() ||
+ platform.isChromeMobileBrowser() ||
+ platform.isFirefoxMobileBrowser() ||
+ platform.isOperaMobileBrowser() ||
+ platform.isEdgeMobileBrowser() ||
+ platform.isIonicIos() // Ionic apps for iOS
+ );
+ }
+
+ // General platform support for web clients (Desktop, Mobile)
+ return (
+ platform.isChromeBrowser() ||
+ platform.isChromeMobileBrowser() ||
+ platform.isFirefoxBrowser() ||
+ platform.isFirefoxMobileBrowser() ||
+ platform.isOperaBrowser() ||
+ platform.isOperaMobileBrowser() ||
+ platform.isEdgeBrowser() ||
+ platform.isEdgeMobileBrowser() ||
+ platform.isSamsungBrowser() ||
+ platform.isSafariBrowser() ||
+ platform.isAndroidBrowser() || // Android WebView & Ionic apps for Android
+ platform.isElectron() ||
+ platform.isNodeJs() ||
+ // TODO: remove when updating platform detection library
+ platform.isMotorolaEdgeDevice()
+ );
+ }
+
+ /**
+ * Checks if the browser supports screen-sharing. Desktop Chrome, Firefox and Opera support screen-sharing
+ * @returns 1 if the browser supports screen-sharing, 0 otherwise
+ */
+ checkScreenSharingCapabilities(): boolean {
+ return platform.canScreenShare();
+ }
+
+ /**
+ * Collects information about the media input devices available on the system. You can pass property `deviceId` of a {@link Device} object as value of `audioSource` or `videoSource` properties in {@link initPublisher} method
+ */
+ async getDevices(): Promise {
+ const devices: MediaDeviceInfo[] = await Room.getLocalDevices();
+ const deviceList: Device[] = [];
+ devices.forEach((device) => {
+ if (device.kind === 'audioinput' || device.kind === 'videoinput') {
+ deviceList.push({
+ deviceId: device.deviceId,
+ label: device.label,
+ kind: device.kind
+ });
+ }
+ });
+ console.log('getDevices', deviceList);
+ return deviceList;
+ // throw new Error("'getDevices' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // navigator.mediaDevices
+ // .enumerateDevices()
+ // .then((deviceInfos) => {
+ // const devices: Device[] = [];
+
+ // // Ionic Android devices
+ // if (platform.isIonicAndroid() && typeof cordova != 'undefined' && cordova?.plugins?.EnumerateDevicesPlugin) {
+ // cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => {
+ // let pluginAudioDevices: Device[] = [];
+ // let videoDevices: Device[] = [];
+ // let audioDevices: Device[] = [];
+ // pluginAudioDevices = pluginDevices.filter((device: Device) => device.kind === 'audioinput');
+ // videoDevices = deviceInfos.filter((device: MediaDeviceInfo) => device.kind === 'videoinput') as any;
+ // audioDevices = deviceInfos.filter((device: MediaDeviceInfo) => device.kind === 'audioinput') as any;
+ // videoDevices.forEach((deviceInfo, index) => {
+ // if (!deviceInfo.label) {
+ // let label = '';
+ // if (index === 0) {
+ // label = 'Front Camera';
+ // } else if (index === 1) {
+ // label = 'Back Camera';
+ // } else {
+ // label = 'Unknown Camera';
+ // }
+ // devices.push({
+ // kind: deviceInfo.kind,
+ // deviceId: deviceInfo.deviceId,
+ // label: label
+ // });
+ // } else {
+ // devices.push({
+ // kind: deviceInfo.kind,
+ // deviceId: deviceInfo.deviceId,
+ // label: deviceInfo.label
+ // });
+ // }
+ // });
+ // audioDevices.forEach((deviceInfo, index) => {
+ // if (!deviceInfo.label) {
+ // let label = '';
+ // switch (index) {
+ // case 0: // Default Microphone
+ // label = 'Default';
+ // break;
+ // case 1: // Microphone + Speakerphone
+ // const defaultMatch = pluginAudioDevices.filter((d) => d.label.includes('Built'))[0];
+ // label = defaultMatch ? defaultMatch.label : 'Built-in Microphone';
+ // break;
+ // case 2: // Headset Microphone
+ // const wiredMatch = pluginAudioDevices.filter((d) => d.label.includes('Wired'))[0];
+ // if (wiredMatch) {
+ // label = wiredMatch.label;
+ // } else {
+ // label = 'Headset earpiece';
+ // }
+ // break;
+ // case 3:
+ // const wirelessMatch = pluginAudioDevices.filter((d) => d.label.includes('Bluetooth'))[0];
+ // label = wirelessMatch ? wirelessMatch.label : 'Wireless';
+ // break;
+ // default:
+ // label = 'Unknown Microphone';
+ // break;
+ // }
+ // devices.push({
+ // kind: deviceInfo.kind,
+ // deviceId: deviceInfo.deviceId,
+ // label: label
+ // });
+ // } else {
+ // devices.push({
+ // kind: deviceInfo.kind,
+ // deviceId: deviceInfo.deviceId,
+ // label: deviceInfo.label
+ // });
+ // }
+ // });
+ // return resolve(devices);
+ // });
+ // } else {
+ // // Rest of platforms
+ // deviceInfos.forEach((deviceInfo) => {
+ // if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
+ // devices.push({
+ // kind: deviceInfo.kind,
+ // deviceId: deviceInfo.deviceId,
+ // label: deviceInfo.label
+ // });
+ // }
+ // });
+ // return resolve(devices);
+ // }
+ // })
+ // .catch((error) => {
+ // logger.error('Error getting devices', error);
+ // return reject(error);
+ // });
+ // });
+ }
+
+ /**
+ * Get a MediaStream object that you can customize before calling {@link initPublisher} (pass _MediaStreamTrack_ property of the _MediaStream_ value resolved by the Promise as `audioSource` or `videoSource` properties in {@link initPublisher})
+ *
+ * Parameter `options` is the same as in {@link initPublisher} second parameter (of type {@link PublisherProperties}), but only the following properties will be applied: `audioSource`, `videoSource`, `frameRate`, `resolution`
+ *
+ * To customize the Publisher's video, the API for HTMLCanvasElement is very useful. For example, to get a black-and-white video at 10 fps and HD resolution with no sound:
+ * ```
+ * var OV = new OpenVidu();
+ * var FRAME_RATE = 10;
+ *
+ * OV.getUserMedia({
+ * audioSource: false,
+ * videoSource: undefined,
+ * resolution: '1280x720',
+ * frameRate: FRAME_RATE
+ * })
+ * .then(mediaStream => {
+ *
+ * var videoTrack = mediaStream.getVideoTracks()[0];
+ * var video = document.createElement('video');
+ * video.srcObject = new MediaStream([videoTrack]);
+ *
+ * var canvas = document.createElement('canvas');
+ * var ctx = canvas.getContext('2d');
+ * ctx.filter = 'grayscale(100%)';
+ *
+ * video.addEventListener('play', () => {
+ * var loop = () => {
+ * if (!video.paused && !video.ended) {
+ * ctx.drawImage(video, 0, 0, 300, 170);
+ * setTimeout(loop, 1000/ FRAME_RATE); // Drawing at 10 fps
+ * }
+ * };
+ * loop();
+ * });
+ * video.play();
+ *
+ * var grayVideoTrack = canvas.captureStream(FRAME_RATE).getVideoTracks()[0];
+ * var publisher = this.OV.initPublisher(
+ * myHtmlTarget,
+ * {
+ * audioSource: false,
+ * videoSource: grayVideoTrack
+ * });
+ * });
+ * ```
+ */
+ async getUserMedia(options: PublisherProperties): Promise {
+ console.log('getUserMedia', options);
+
+ // throw new Error("'getUserMedia' method is not ready yet.");
+ // const askForAudioStreamOnly = async (previousMediaStream: MediaStream, constraints: MediaStreamConstraints) => {
+ // const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
+ // const constraintsAux: MediaStreamConstraints = { audio: definedAudioConstraint, video: false };
+ // try {
+ // const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
+ // previousMediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
+ // return previousMediaStream;
+ // } catch (error) {
+ // previousMediaStream.getAudioTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // previousMediaStream.getVideoTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // throw this.generateAudioDeviceError(error, constraintsAux);
+ // }
+ // };
+
+ try {
+ const myConstraints = await this.generateMediaConstraints(options);
+ if (
+ (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
+ (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
+ (!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
+ ) {
+ // No need to call getUserMedia at all. Both tracks provided, or only AUDIO track provided or only VIDEO track provided
+ //TODO: Implement case when AUDIO or VIDEO tracks are provided
+ return this.addAlreadyProvidedTracks(myConstraints, new MediaStream());
+ } else {
+ // getUserMedia must be called. AUDIO or VIDEO are requesting a new track
+ // Delete already provided constraints for audio or video
+ if (!!myConstraints.videoTrack) {
+ delete myConstraints.constraints!.video;
+ }
+ if (!!myConstraints.audioTrack) {
+ delete myConstraints.constraints!.audio;
+ }
+
+ let mustAskForAudioTrackLater = false;
+ if (typeof options.videoSource === 'string') {
+ // Video is deviceId or screen sharing
+ if (
+ options.videoSource === 'screen' ||
+ options.videoSource === 'window' ||
+ (platform.isElectron() && options.videoSource.startsWith('screen:'))
+ ) {
+ // Video is screen sharing
+ throw new Error('screen sharing not implemented yet');
+
+ // mustAskForAudioTrackLater =
+ // !myConstraints.audioTrack && options.audioSource !== null && options.audioSource !== false;
+ // if (navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
+ // // getDisplayMedia supported
+ // try {
+ // const mediaStream = await navigator.mediaDevices['getDisplayMedia']({ video: true, audio: options.audioSource === 'screen' });
+ // this.addAlreadyProvidedTracks(myConstraints, mediaStream);
+ // if (mustAskForAudioTrackLater) {
+ // return await askForAudioStreamOnly(mediaStream, myConstraints.constraints);
+ // } else {
+ // return mediaStream;
+ // }
+ // } catch (error) {
+ // let errorName: OpenViduErrorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
+ // const errorMessage = error.toString();
+ // throw new OpenViduError(errorName, errorMessage);
+ // }
+ } else {
+ // getDisplayMedia NOT supported. Can perform getUserMedia below with already calculated constraints
+ }
+ } else {
+ // Video is deviceId. Can perform getUserMedia below with already calculated constraints
+ }
+ // Use already calculated constraints
+ const constraintsAux: MediaStreamConstraints = mustAskForAudioTrackLater
+ ? { video: myConstraints.constraints!.video }
+ : myConstraints.constraints;
+ try {
+ console.log('getUserMedia constraintsAux', constraintsAux);
+
+ let audioOpts: AudioCaptureOptions | boolean;
+ let videoOpts: VideoCaptureOptions | boolean;
+ const trackOpts: CreateLocalTracksOptions = {};
+
+ if (typeof constraintsAux.audio === 'boolean') {
+ audioOpts = constraintsAux.audio;
+ } else {
+ audioOpts = {
+ deviceId: constraintsAux.audio?.deviceId
+ };
+ }
+
+ if (typeof constraintsAux.video === 'boolean') {
+ videoOpts = constraintsAux.video;
+ } else {
+ videoOpts = {
+ deviceId: constraintsAux.video?.deviceId
+ };
+ }
+
+ trackOpts.audio = audioOpts;
+ trackOpts.video = videoOpts;
+ const tracks: LocalTrack[] = await createLocalTracks(trackOpts);
+ const mediaStream = new MediaStream();
+ tracks.forEach((track) => {
+ mediaStream.addTrack(track.mediaStreamTrack);
+ });
+
+ return mediaStream;
+
+ // const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
+ // this.addAlreadyProvidedTracks(myConstraints, mediaStream);
+ // if (mustAskForAudioTrackLater) {
+ // return await askForAudioStreamOnly(mediaStream, myConstraints.constraints);
+ // } else {
+ // return mediaStream;
+ // }
+ } catch (error) {
+ let errorName: OpenViduErrorName;
+ const errorMessage = error.toString();
+ if (!(options.videoSource === 'screen')) {
+ errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED;
+ } else {
+ errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
+ }
+ throw new OpenViduError(errorName, errorMessage);
+ }
+ }
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /* tslint:disable:no-empty */
+ /**
+ * Disable all logging except error level
+ */
+ enableProdMode(): void {
+ throw new Error("'enableProdMode' method is not ready yet.");
+ logger.enableProdMode();
+ }
+ /* tslint:enable:no-empty */
+
+ /**
+ * Set OpenVidu advanced configuration options. `configuration` is an object of type {@link OpenViduAdvancedConfiguration}. Call this method to override previous values at any moment.
+ */
+ setAdvancedConfiguration(configuration: OpenViduAdvancedConfiguration): void {
+ //TODO: apply this configuration in the room
+ this.advancedConfiguration = configuration;
+ }
+
+ /* Hidden methods */
+
+ /**
+ * @hidden
+ */
+ onOrientationChanged(handler): void {
+ throw new Error("'onOrientationChanged' method is not ready yet.");
+ (globalThis as any).addEventListener('orientationchange', handler);
+ }
+
+ /**
+ * @hidden
+ */
+ sendNewVideoDimensionsIfRequired(
+ publisher: Publisher,
+ reason: StreamPropertyChangedEventReason,
+ WAIT_INTERVAL: number,
+ MAX_ATTEMPTS: number
+ ) {
+ // throw new Error("'sendNewVideoDimensionsIfRequired' method is not ready yet.");
+ let attempts = 0;
+ const oldWidth = publisher?.stream?.videoDimensions?.width || 0;
+ const oldHeight = publisher?.stream?.videoDimensions?.height || 0;
+
+ const repeatUntilChangeOrMaxAttempts: NodeJS.Timeout = setInterval(() => {
+ attempts++;
+ if (attempts > MAX_ATTEMPTS) {
+ clearTimeout(repeatUntilChangeOrMaxAttempts);
+ }
+ publisher.getVideoDimensions().then((newDimensions) => {
+ if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
+ clearTimeout(repeatUntilChangeOrMaxAttempts);
+ this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
+ }
+ });
+ }, WAIT_INTERVAL);
+ }
+
+ /**
+ * @hidden
+ */
+ sendVideoDimensionsChangedEvent(
+ publisher: Publisher,
+ reason: StreamPropertyChangedEventReason,
+ oldWidth: number,
+ oldHeight: number,
+ newWidth: number,
+ newHeight: number
+ ) {
+ // throw new Error("'sendVideoDimensionsChangedEvent' method is not ready yet.");
+ publisher.stream.videoDimensions = {
+ width: newWidth || 0,
+ height: newHeight || 0
+ };
+ // this.sendRequest(
+ // 'streamPropertyChanged',
+ // {
+ // streamId: publisher.stream.streamId,
+ // property: 'videoDimensions',
+ // newValue: JSON.stringify(publisher.stream.videoDimensions),
+ // reason
+ // },
+ // (error, response) => {
+ // if (error) {
+ // logger.error("Error sending 'streamPropertyChanged' event", error);
+ // } else {
+ this.session.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(
+ this.session,
+ publisher.stream,
+ 'videoDimensions',
+ publisher.stream.videoDimensions,
+ { width: oldWidth, height: oldHeight },
+ reason
+ )
+ ]);
+ publisher.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(
+ publisher,
+ publisher.stream,
+ 'videoDimensions',
+ publisher.stream.videoDimensions,
+ { width: oldWidth, height: oldHeight },
+ reason
+ )
+ ]);
+ // this.session.sendVideoData(publisher);
+ // }
+ // }
+ // );
+ }
+
+ /**
+ * @hidden
+ */
+ sendTrackChangedEvent(publisher: Publisher, oldLabel: string, newLabel: string, propertyType: ChangedPropertyType) {
+ throw new Error("'sendTrackChangedEvent' method is not ready yet.");
+ const oldValue = { label: oldLabel };
+ const newValue = { label: newLabel };
+ const reason = 'trackReplaced';
+
+ if (publisher.stream.isLocalStreamPublished) {
+ this.sendRequest(
+ 'streamPropertyChanged',
+ {
+ streamId: publisher.stream.streamId,
+ property: propertyType,
+ newValue: newValue,
+ reason
+ },
+ (error, response) => {
+ if (error) {
+ logger.error("Error sending 'streamPropertyChanged' event", error);
+ } else {
+ this.session.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
+ ]);
+ publisher.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
+ ]);
+ }
+ }
+ );
+ } else {
+ this.session.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
+ ]);
+ publisher.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
+ ]);
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ generateMediaConstraints(publisherProperties: PublisherProperties): Promise {
+ // throw new Error("'generateMediaConstraints' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ const myConstraints: CustomMediaStreamConstraints = {
+ audioTrack: undefined,
+ videoTrack: undefined,
+ constraints: {
+ audio: undefined,
+ video: undefined
+ }
+ };
+ const audioSource = publisherProperties.audioSource;
+ const videoSource = publisherProperties.videoSource;
+
+ // CASE 1: null/false
+ if (audioSource === null || audioSource === false) {
+ // No audio track
+ myConstraints.constraints!.audio = false;
+ }
+ if (videoSource === null || videoSource === false) {
+ // No video track
+ myConstraints.constraints!.video = false;
+ }
+ if (myConstraints.constraints!.audio === false && myConstraints.constraints!.video === false) {
+ // ERROR! audioSource and videoSource cannot be both false at the same time
+ return reject(
+ new OpenViduError(
+ OpenViduErrorName.NO_INPUT_SOURCE_SET,
+ "Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time"
+ )
+ );
+ }
+
+ // CASE 2: MediaStreamTracks
+ if (typeof MediaStreamTrack !== 'undefined' && audioSource instanceof MediaStreamTrack) {
+ // Already provided audio track
+ myConstraints.audioTrack = audioSource;
+ }
+ if (typeof MediaStreamTrack !== 'undefined' && videoSource instanceof MediaStreamTrack) {
+ // Already provided video track
+ myConstraints.videoTrack = videoSource;
+ }
+
+ // CASE 3: Default tracks
+ if (audioSource === undefined) {
+ myConstraints.constraints!.audio = true;
+ }
+ if (videoSource === undefined) {
+ myConstraints.constraints!.video = {
+ width: {
+ ideal: 640
+ },
+ height: {
+ ideal: 480
+ }
+ };
+ }
+
+ // CASE 3.5: give values to resolution and frameRate if video not null/false
+ if (videoSource !== null && videoSource !== false) {
+ if (!!publisherProperties.resolution) {
+ const widthAndHeight = publisherProperties.resolution.toLowerCase().split('x');
+ const idealWidth = Number(widthAndHeight[0]);
+ const idealHeight = Number(widthAndHeight[1]);
+ myConstraints.constraints!.video = {
+ width: {
+ ideal: idealWidth
+ },
+ height: {
+ ideal: idealHeight
+ }
+ };
+ }
+ if (!!publisherProperties.frameRate) {
+ (myConstraints.constraints!.video).frameRate = { ideal: publisherProperties.frameRate };
+ }
+ }
+
+ // CASE 4: deviceId or screen sharing
+ this.configureDeviceIdOrScreensharing(myConstraints, publisherProperties, resolve, reject);
+
+ return resolve(myConstraints);
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ // startWs(onConnectSucces: (error: Error) => void): void {
+ // throw new Error("'startWs' method is not ready yet.");
+ // const config = {
+ // heartbeat: 5000,
+ // ws: {
+ // uri: this.wsUri + '?sessionId=' + this.session.sessionId,
+ // onconnected: onConnectSucces,
+ // ondisconnect: this.disconnectCallback.bind(this),
+ // onreconnecting: this.reconnectingCallback.bind(this),
+ // onreconnected: this.reconnectedCallback.bind(this),
+ // ismasternodecrashed: this.isMasterNodeCrashed.bind(this)
+ // },
+ // rpc: {
+ // requestTimeout: 10000,
+ // heartbeatRequestTimeout: 5000,
+ // participantJoined: this.session.onParticipantJoined.bind(this.session),
+ // participantPublished: this.session.onParticipantPublished.bind(this.session),
+ // participantUnpublished: this.session.onParticipantUnpublished.bind(this.session),
+ // participantLeft: this.session.onParticipantLeft.bind(this.session),
+ // participantEvicted: this.session.onParticipantEvicted.bind(this.session),
+ // recordingStarted: this.session.onRecordingStarted.bind(this.session),
+ // recordingStopped: this.session.onRecordingStopped.bind(this.session),
+ // broadcastStarted: this.session.onBroadcastStarted.bind(this.session),
+ // broadcastStopped: this.session.onBroadcastStopped.bind(this.session),
+ // sendMessage: this.session.onNewMessage.bind(this.session),
+ // streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session),
+ // connectionPropertyChanged: this.session.onConnectionPropertyChanged.bind(this.session),
+ // networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session),
+ // filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
+ // iceCandidate: this.session.recvIceCandidate.bind(this.session),
+ // mediaError: this.session.onMediaError.bind(this.session),
+ // masterNodeCrashedNotification: this.onMasterNodeCrashedNotification.bind(this),
+ // forciblyReconnectSubscriber: this.session.onForciblyReconnectSubscriber.bind(this.session),
+ // speechToTextMessage: this.session.onSpeechToTextMessage.bind(this.session),
+ // speechToTextDisconnected: this.session.onSpeechToTextDisconnected.bind(this.session)
+ // }
+ // };
+ // this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
+ // }
+
+ /**
+ * @hidden
+ */
+ onMasterNodeCrashedNotification(response): void {
+ throw new Error("'onMasterNodeCrashedNotification' method is not ready yet.");
+ console.error('Master Node has crashed');
+ this.masterNodeHasCrashed = true;
+ this.session.onLostConnection('nodeCrashed');
+ this.jsonRpcClient.close(4103, 'Master Node has crashed');
+ }
+
+ /**
+ * @hidden
+ */
+ getWsReadyState(): number {
+ throw new Error("'getWsReadyState' method is not ready yet.");
+ return this.jsonRpcClient.getReadyState();
+ }
+
+ /**
+ * @hidden
+ */
+ closeWs(): void {
+ throw new Error("'closeWs' method is not ready yet.");
+ this.jsonRpcClient.close(4102, 'Connection closed by client');
+ }
+
+ /**
+ * @hidden
+ */
+ sendRequest(method: string, params: any, callback?): void {
+ throw new Error("'sendRequest' method is not ready yet.");
+ if (params && params instanceof Function) {
+ callback = params;
+ params = {};
+ }
+ logger.debug('Sending request: {method:"' + method + '", params: ' + JSON.stringify(params) + '}');
+ this.jsonRpcClient?.send(method, params, callback);
+ }
+
+ /**
+ * @hidden
+ */
+ getWsUri(): string {
+ throw new Error("'getWsUri' method is not ready yet.");
+ return this.wsUri;
+ }
+
+ /**
+ * @hidden
+ */
+ getSecret(): string {
+ throw new Error("'getSecret' method is not ready yet.");
+ return this.secret;
+ }
+
+ /**
+ * @hidden
+ */
+ getRecorder(): boolean {
+ throw new Error("'getRecorder' method is not ready yet.");
+ return this.recorder;
+ }
+
+ /**
+ * @hidden
+ */
+ getStt(): boolean {
+ throw new Error("'getStt' method is not ready yet.");
+ return this.stt;
+ }
+
+ /**
+ * @hidden
+ */
+ generateAudioDeviceError(error, constraints: MediaStreamConstraints): OpenViduError {
+ throw new Error("'generateAudioDeviceError' method is not ready yet.");
+ if (error.name === 'Error') {
+ // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
+ error.name = error.constructor.name;
+ }
+ let errorName, errorMessage: string;
+ switch (error.name.toLowerCase()) {
+ case 'notfounderror':
+ errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
+ errorMessage = error.toString();
+ return new OpenViduError(errorName, errorMessage);
+ case 'notallowederror':
+ errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED;
+ errorMessage = error.toString();
+ return new OpenViduError(errorName, errorMessage);
+ case 'overconstrainederror':
+ if (error.constraint.toLowerCase() === 'deviceid') {
+ errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
+ errorMessage =
+ "Audio input device with deviceId '" +
+ ((constraints.audio).deviceId!!).exact +
+ "' not found";
+ } else {
+ errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
+ errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
+ }
+ return new OpenViduError(errorName, errorMessage);
+ case 'notreadableerror':
+ errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
+ errorMessage = error.toString();
+ return new OpenViduError(errorName, errorMessage);
+ default:
+ return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString());
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ addAlreadyProvidedTracks(myConstraints: CustomMediaStreamConstraints, mediaStream: MediaStream, stream?: Stream): MediaStream {
+ // throw new Error("'addAlreadyProvidedTracks' method is not ready yet.");
+ if (!!myConstraints.videoTrack) {
+ mediaStream.addTrack(myConstraints.videoTrack);
+ if (!!stream) {
+ if (!!myConstraints.constraints.video) {
+ stream.lastVideoTrackConstraints = myConstraints.constraints.video;
+ } else {
+ stream.lastVideoTrackConstraints = myConstraints.videoTrack.getConstraints();
+ }
+ }
+ }
+ if (!!myConstraints.audioTrack) {
+ mediaStream.addTrack(myConstraints.audioTrack);
+ }
+ return mediaStream;
+ }
+
+ /**
+ * @hidden
+ */
+ protected configureDeviceIdOrScreensharing(
+ myConstraints: CustomMediaStreamConstraints,
+ publisherProperties: PublisherProperties,
+ resolve,
+ reject
+ ) {
+ const audioSource = publisherProperties.audioSource;
+ const videoSource = publisherProperties.videoSource;
+ if (typeof audioSource === 'string' && audioSource !== 'screen') {
+ myConstraints.constraints!.audio = { deviceId: { exact: audioSource } };
+ }
+
+ if (typeof videoSource === 'string') {
+ if (!this.isScreenShare(videoSource)) {
+ this.setVideoSource(myConstraints, videoSource);
+ if (audioSource === 'screen') {
+ logger.warn(
+ 'Parameter "audioSource" is set to "screen", which means rquesting audio from screen sharing source. But "videoSource" is not set to "screen". No audio source will be requested'
+ );
+ myConstraints.constraints!.audio = false;
+ }
+ } else {
+ // Screen sharing
+
+ if (!this.checkScreenSharingCapabilities()) {
+ const error = new OpenViduError(
+ OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED,
+ 'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0), Edge (>= 80) or Electron. Detected client: ' +
+ platform.getName() +
+ ' ' +
+ platform.getVersion()
+ );
+ logger.error(error);
+ return reject(error);
+ } else {
+ if (platform.isElectron()) {
+ const prefix = 'screen:';
+ const videoSourceString: string = videoSource;
+ const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length);
+ (myConstraints.constraints!.video) = {
+ mandatory: {
+ chromeMediaSource: 'desktop',
+ chromeMediaSourceId: electronScreenId
+ }
+ };
+ return resolve(myConstraints);
+ } else {
+ if (
+ !!this.advancedConfiguration.screenShareChromeExtension &&
+ !(platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) &&
+ !navigator.mediaDevices['getDisplayMedia']
+ ) {
+ // Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia()
+
+ screenSharing.getScreenConstraints((error, screenConstraints) => {
+ if (
+ !!error ||
+ (!!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen')
+ ) {
+ if (error === 'permission-denied' || error === 'PermissionDeniedError') {
+ const error = new OpenViduError(
+ OpenViduErrorName.SCREEN_CAPTURE_DENIED,
+ 'You must allow access to one window of your desktop'
+ );
+ logger.error(error);
+ return reject(error);
+ } else {
+ const extensionId = this.advancedConfiguration
+ .screenShareChromeExtension!.split('/')
+ .pop()!!
+ .trim();
+ screenSharing.getChromeExtensionStatus(extensionId, (status) => {
+ if (status === 'installed-disabled') {
+ const error = new OpenViduError(
+ OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
+ 'You must enable the screen extension'
+ );
+ logger.error(error);
+ return reject(error);
+ }
+ if (status === 'not-installed') {
+ const error = new OpenViduError(
+ OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED,
+ this.advancedConfiguration.screenShareChromeExtension
+ );
+ logger.error(error);
+ return reject(error);
+ }
+ });
+ return;
+ }
+ } else {
+ myConstraints.constraints!.video = screenConstraints;
+ return resolve(myConstraints);
+ }
+ });
+ return;
+ } else {
+ // if (navigator.mediaDevices['getDisplayMedia']) {
+ // getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13)
+ return resolve(myConstraints);
+ // } else {
+ // // Default screen sharing extension for Chrome/Opera, or is Firefox < 66
+ // const firefoxString =
+ // platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()
+ // ? publisherProperties.videoSource
+ // : undefined;
+
+ // screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => {
+ // if (!!error) {
+ // if (error === 'not-installed') {
+ // const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension
+ // ? this.advancedConfiguration.screenShareChromeExtension
+ // : 'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
+ // const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl);
+ // logger.error(err);
+ // return reject(err);
+ // } else if (error === 'installed-disabled') {
+ // const err = new OpenViduError(
+ // OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
+ // 'You must enable the screen extension'
+ // );
+ // logger.error(err);
+ // return reject(err);
+ // } else if (error === 'permission-denied') {
+ // const err = new OpenViduError(
+ // OpenViduErrorName.SCREEN_CAPTURE_DENIED,
+ // 'You must allow access to one window of your desktop'
+ // );
+ // logger.error(err);
+ // return reject(err);
+ // } else {
+ // const err = new OpenViduError(
+ // OpenViduErrorName.GENERIC_ERROR,
+ // 'Unknown error when accessing screen share'
+ // );
+ // logger.error(err);
+ // logger.error(error);
+ // return reject(err);
+ // }
+ // } else {
+ // myConstraints.constraints!.video = screenConstraints.video;
+ // return resolve(myConstraints);
+ // }
+ // });
+ // return;
+ // }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ protected setVideoSource(myConstraints: CustomMediaStreamConstraints, videoSource: string) {
+ // throw new Error("'setVideoSource' method is not ready yet.");
+ if (!myConstraints.constraints!.video) {
+ myConstraints.constraints!.video = {};
+ }
+ (myConstraints.constraints!.video)['deviceId'] = { exact: videoSource };
+ }
+
+ /* Private methods */
+
+ private disconnectCallback(): void {
+ throw new Error("'disconnectCallback' method is not ready yet.");
+
+ logger.warn('Websocket connection lost');
+ if (this.isRoomAvailable()) {
+ this.session.onLostConnection('networkDisconnect');
+ } else {
+ alert('Connection error. Please reload page.');
+ }
+ }
+
+ private reconnectingCallback(): void {
+ logger.warn('Websocket connection lost (reconnecting)');
+ if (!this.isRoomAvailable()) {
+ alert('Connection error. Please reload page.');
+ } else {
+ this.session.emitEvent('reconnecting', []);
+ }
+ }
+
+ private reconnectWebsocketThroughRpcConnectMethod(rpcSessionId) {
+ throw new Error("'reconnectWebsocketThroughRpcConnectMethod' method is not ready yet.");
+ // This RPC method allows checking:
+ // Single Master: if success, connection recovered
+ // if error, no Master Node crashed and life will be -1. onLostConnection with reason networkDisconnect will be triggered
+ // Multi Master: if success, connection recovered
+ // if error and Master Node crashed notification was already received, nothing must be done
+ // if error and Master Node NOT crashed, sessionStatus method must be sent:
+ // if life is equal, networkDisconnect
+ // if life is greater, nodeCrashed
+ this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => {
+ if (!!error) {
+ if (this.isMasterNodeCrashed()) {
+ logger.warn('Master Node has crashed!');
+ } else {
+ logger.error(error);
+
+ const notifyLostConnection = (reason, errorMsg) => {
+ logger.warn(errorMsg);
+ this.session.onLostConnection(reason);
+ this.jsonRpcClient.close(4101, 'Reconnection fault: ' + errorMsg);
+ };
+
+ const rpcSessionStatus = () => {
+ if (this.life === -1) {
+ // Single Master
+ notifyLostConnection(
+ 'networkDisconnect',
+ 'WS successfully reconnected but the user was already evicted due to timeout'
+ );
+ } else {
+ // Multi Master
+ // This RPC method is only required to find out the reason of the disconnection:
+ // whether the client lost its network connection or a Master Node crashed
+ this.sendRequest('sessionStatus', { sessionId: this.session.sessionId }, (error, response) => {
+ if (error != null) {
+ console.error('Error checking session status', error);
+ } else {
+ if (this.life === response.life) {
+ // If the life stored in the client matches the life stored in the server, it means that the client lost its network connection
+ notifyLostConnection(
+ 'networkDisconnect',
+ 'WS successfully reconnected but the user was already evicted due to timeout'
+ );
+ } else {
+ // If the life stored in the client is below the life stored in the server, it means that the Master Node has crashed
+ notifyLostConnection(
+ 'nodeCrashed',
+ 'WS successfully reconnected to OpenVidu Server but your Master Node crashed'
+ );
+ }
+ }
+ });
+ }
+ };
+
+ if (error.code === 40007 && error.message === 'reconnection error') {
+ // Kurento error: invalid RPC sessionId. This means that the kurento-jsonrpc-server of openvidu-server where kurento-jsonrpc-client
+ // is trying to reconnect does not know about this sessionId. This can mean two things:
+ // 1) openvidu-browser managed to reconnect after a while, but openvidu-server already evicted the user for not receiving ping.
+ // 2) openvidu-server process is a different one because of a node crash.
+ // Send a "sessionStatus" method to check the reason
+ console.error('Invalid RPC sessionId. Client network disconnection or Master Node crash');
+ rpcSessionStatus();
+ } else {
+ rpcSessionStatus();
+ }
+ }
+ } else {
+ this.jsonRpcClient.resetPing();
+ this.session.onRecoveredConnection();
+ }
+ });
+ }
+
+ private reconnectedCallback(): void {
+ throw new Error("'reconnectedCallback' method is not ready yet.");
+ logger.warn('Websocket reconnected');
+ if (this.isRoomAvailable()) {
+ if (!!this.session.connection) {
+ this.reconnectWebsocketThroughRpcConnectMethod(this.session.connection.rpcSessionId);
+ } else {
+ logger.warn('There was no previous connection when running reconnection callback');
+ // Make Session object dispatch 'sessionDisconnected' event
+ const sessionDisconnectEvent = new SessionDisconnectedEvent(this.session, 'networkDisconnect');
+ this.session.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]);
+ sessionDisconnectEvent.callDefaultBehavior();
+ }
+ } else {
+ alert('Connection error. Please reload page.');
+ }
+ }
+
+ private isMasterNodeCrashed() {
+ throw new Error("'isMasterNodeCrashed' method is not ready yet.");
+ return this.masterNodeHasCrashed;
+ }
+
+ private isRoomAvailable(): boolean {
+ throw new Error("'isRoomAvailable' method is not ready yet.");
+ if (this.session !== undefined && this.session instanceof Session) {
+ return true;
+ } else {
+ logger.warn('Session instance not found');
+ return false;
+ }
+ }
+
+ private isScreenShare(videoSource: string) {
+ // throw new Error("'isScreenShare' method is not ready yet.");
+ return videoSource === 'screen' || videoSource === 'window' || (platform.isElectron() && videoSource.startsWith('screen:'));
+ }
+}
diff --git a/openvidu-browser/old/OldPublisher.ts b/openvidu-browser/old/OldPublisher.ts
new file mode 100644
index 00000000000..244bbad2903
--- /dev/null
+++ b/openvidu-browser/old/OldPublisher.ts
@@ -0,0 +1,1080 @@
+/*
+ * (C) Copyright 2017-2022 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 { OpenVidu } from '../OpenVidu';
+import { Session } from '../Session';
+import { Stream } from './OldStream';
+import { StreamManager } from '../StreamManager';
+import { PublisherProperties } from '../../OpenViduInternal/Interfaces/Public/PublisherProperties';
+import { PublisherEventMap } from '../../OpenViduInternal/Events/EventMap/PublisherEventMap';
+import { StreamEvent } from '../../OpenViduInternal/Events/StreamEvent';
+import { StreamPropertyChangedEvent } from '../../OpenViduInternal/Events/StreamPropertyChangedEvent';
+import { OpenViduError, OpenViduErrorName } from '../../OpenViduInternal/Enums/OpenViduError';
+import { VideoInsertMode } from '../../OpenViduInternal/Enums/VideoInsertMode';
+import { OpenViduLogger } from '../../OpenViduInternal/Logger/OpenViduLogger';
+import { PlatformUtils } from '../../OpenViduInternal/Utils/Platform';
+import { TypeOfVideo } from '../../OpenViduInternal/Enums/TypeOfVideo';
+import { StreamEventReason } from '../../OpenViduInternal/Events/Types/Types';
+import {
+ AudioCaptureOptions,
+ LocalAudioTrack,
+ LocalTrack,
+ LocalTrackPublication,
+ LocalVideoTrack,
+ Room,
+ ScreenShareCaptureOptions,
+ Track,
+ VideoCaptureOptions,
+ createLocalAudioTrack,
+ createLocalScreenTracks,
+ createLocalVideoTrack
+} from 'livekit-client';
+
+/**
+ * @hidden
+ */
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+
+/**
+ * @hidden
+ */
+let platform: PlatformUtils;
+
+/**
+ * Packs local media streams. Participants can publish it to a session. Initialized with {@link OpenVidu.initPublisher} method.
+ *
+ * See available event listeners at {@link PublisherEventMap}.
+ */
+export class Publisher extends StreamManager {
+ /**
+ * Whether the Publisher has been granted access to the requested input devices or not
+ */
+ accessAllowed = false;
+
+ /**
+ * Whether you have called {@link Publisher.subscribeToRemote} with value `true` or `false` (*false* by default)
+ */
+ isSubscribedToRemote = false;
+
+ /**
+ * The {@link Session} to which the Publisher belongs
+ */
+ session: Session; // Initialized by Session.publish(Publisher)
+ private accessDenied = false;
+ protected properties: PublisherProperties;
+ private permissionDialogTimeout: NodeJS.Timer;
+
+ /**
+ * @hidden
+ */
+ openvidu: OpenVidu;
+
+ /**
+ * @hidden
+ */
+ screenShareResizeInterval: NodeJS.Timer;
+
+ videoOptions?: VideoCaptureOptions;
+ audioOptions?: AudioCaptureOptions;
+
+ /**
+ * @hidden
+ */
+ constructor(targetElement: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) {
+ // TODO: initialize with real values
+ const session = openvidu.session ? openvidu.session : new Session(openvidu);
+ const options = {
+ publisherProperties: properties,
+ mediaConstraints: {}
+ };
+
+ const stream = new Stream(session, options);
+ super(stream, targetElement);
+
+ platform = PlatformUtils.getInstance();
+ this.stream = stream;
+ this.properties = properties;
+ this.openvidu = openvidu;
+ this.stream.ee.on('local-stream-destroyed', (reason: StreamEventReason) => {
+ this.stream.isLocalStreamPublished = false;
+ const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
+ this.emitEvent('streamDestroyed', [streamEvent]);
+ streamEvent.callDefaultBehavior();
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ get videoReference(): HTMLVideoElement {
+ let element!: HTMLVideoElement;
+ this.stream.session.room.localParticipant.videoTracks.forEach((track) => {
+ element = track.track?.attachedElements[0] as HTMLVideoElement;
+ });
+
+ if (!element) {
+
+ return this.stream.temporalVideoTrack?.attachedElements[0] as HTMLVideoElement;
+ }
+ return element;
+ }
+
+ /**
+ * @hidden
+ */
+ // setOptions(videoOptions?: VideoCaptureOptions, audioOptions?: AudioCaptureOptions): void {
+ // // if(Boolean(videoOptions)) {
+ // this.videoOptions = videoOptions;
+ // // }
+ // // if(Boolean(audioOptions)) {
+ // this.audioOptions = audioOptions;
+ // // }
+ // // console.warn('setOptions not implemented in Publisher');
+ // }
+
+ /**
+ * Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
+ *
+ * #### Events dispatched
+ *
+ * > _Only if `Session.publish(Publisher)` has been called for this Publisher_
+ *
+ * The {@link Session} object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
+ * The {@link Publisher} object of the local participant will also dispatch the exact same event
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
+ * The respective {@link Subscriber} object of every other participant receiving this Publisher's stream will also dispatch the exact same event
+ *
+ * See {@link StreamPropertyChangedEvent} to learn more.
+ *
+ * @param enabled `true` to publish the audio stream, `false` to unpublish it
+ */
+ publishAudio(enabled: boolean): void {
+ this.session.localParticipant.setMicrophoneEnabled(enabled);
+ this.stream.audioActive = enabled;
+
+ return;
+ throw new Error("'publishAudio' method is not ready yet");
+ if (this.stream.audioActive !== enabled) {
+ const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
+ ? this.stream.localMediaStreamWhenSubscribedToRemote!
+ : this.stream.getMediaStream();
+ affectedMediaStream.getAudioTracks().forEach((track) => {
+ track.enabled = enabled;
+ });
+ if (!!this.session && !!this.stream.streamId) {
+ this.session.openvidu.sendRequest(
+ 'streamPropertyChanged',
+ {
+ streamId: this.stream.streamId,
+ property: 'audioActive',
+ newValue: enabled,
+ reason: 'publishAudio'
+ },
+ (error, response) => {
+ if (error) {
+ logger.error("Error sending 'streamPropertyChanged' event", error);
+ } else {
+ this.session.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
+ ]);
+ this.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
+ ]);
+ }
+ }
+ );
+ }
+ this.stream.audioActive = enabled;
+ logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
+ }
+ }
+
+ /**
+ * Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
+ *
+ * #### Events dispatched
+ *
+ * > _Only if `Session.publish(Publisher)` has been called for this Publisher_
+ *
+ * The {@link Session} object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
+ * The {@link Publisher} object of the local participant will also dispatch the exact same event
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
+ * The respective {@link Subscriber} object of every other participant receiving this Publisher's stream will also dispatch the exact same event
+ *
+ * See {@link StreamPropertyChangedEvent} to learn more.
+ *
+ * @param enabled `true` to publish the video stream, `false` to unpublish it
+ * @param resource
+ *
+ * If parameter **`enabled`** is `false`, this optional parameter is of type boolean. It can be set to `true` to forcibly free the hardware resource associated to the video track, or can be set to `false` to keep the access to the hardware resource.
+ * Not freeing the resource makes the operation much more efficient, but depending on the platform two side-effects can be introduced: the video device may not be accessible by other applications and the access light of
+ * webcams may remain on. This is platform-dependent: some browsers will not present the side-effects even when not freeing the resource.
+ *
+ * If parameter **`enabled`** is `true`, this optional parameter is of type [MediaStreamTrack](https://developer.mozilla.org/docs/Web/API/MediaStreamTrack). It can be set to force the restoration of the video track with a custom track. This may be
+ * useful if the Publisher was unpublished freeing the hardware resource, and openvidu-browser is not able to successfully re-create the video track as it was before unpublishing. In this way previous track settings will be ignored and this MediaStreamTrack
+ * will be used instead.
+ */
+ async publishVideo(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise {
+ console.warn("'publishVideo' method is not ready yet");
+ await this.session.localParticipant.setCameraEnabled(enabled);
+ this.stream.videoActive = enabled;
+ return;
+
+ return new Promise(async (resolve, reject) => {
+ if (this.stream.videoActive !== enabled) {
+ const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
+ ? this.stream.localMediaStreamWhenSubscribedToRemote!
+ : this.stream.getMediaStream();
+ let mustRestartMediaStream = false;
+ affectedMediaStream.getVideoTracks().forEach((track) => {
+ track.enabled = enabled;
+ if (!enabled && resource === true) {
+ track.stop();
+ } else if (enabled && track.readyState === 'ended') {
+ // Resource was freed
+ mustRestartMediaStream = true;
+ }
+ });
+
+ // There is a Virtual Background filter applied that must be removed in case the hardware must be freed
+ // if (!enabled && resource === true && !!this.stream.filter && this.stream.filter.type.startsWith('VB:')) {
+ // this.stream.lastVBFilter = this.stream.filter; // Save the filter to be re-applied in case of unmute
+ // await this.stream.removeFilterAux(true);
+ // }
+
+ if (mustRestartMediaStream) {
+ const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
+ affectedMediaStream.removeTrack(oldVideoTrack);
+
+ const replaceVideoTrack = async (tr: MediaStreamTrack) => {
+ affectedMediaStream.addTrack(tr);
+ if (this.stream.isLocalStreamPublished) {
+ await this.replaceTrackInRtcRtpSender(tr);
+ }
+ if (!!this.stream.lastVBFilter) {
+ setTimeout(async () => {
+ let options = this.stream.lastVBFilter!.options;
+ const lastExecMethod = this.stream.lastVBFilter!.lastExecMethod;
+ if (!!lastExecMethod && lastExecMethod.method === 'update') {
+ options = Object.assign({}, options, lastExecMethod.params);
+ }
+ await this.stream.applyFilter(this.stream.lastVBFilter!.type, options);
+ delete this.stream.lastVBFilter;
+ }, 1);
+ }
+ };
+
+ if (!!resource && resource instanceof MediaStreamTrack) {
+ await replaceVideoTrack(resource);
+ } else {
+ try {
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
+ audio: false,
+ video: this.stream.lastVideoTrackConstraints
+ });
+ await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
+ } catch (error) {
+ return reject(error);
+ }
+ }
+ }
+
+ if (!!this.session && !!this.stream.streamId) {
+ this.session.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
+ ]);
+ this.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
+ ]);
+ }
+ this.stream.videoActive = enabled;
+ logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
+ return resolve();
+ }
+ });
+ }
+
+ /**
+ * Call this method before {@link Session.publish} if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
+ */
+ subscribeToRemote(value?: boolean): void {
+ throw new Error("'subscribeToRemote' method is not ready yet");
+ // value = value !== undefined ? value : true;
+ // this.isSubscribedToRemote = value;
+ // this.stream.subscribeToMyRemote(value);
+ }
+
+ /**
+ * See {@link EventDispatcher.on}
+ */
+ on(type: K, handler: (event: PublisherEventMap[K]) => void): this {
+ console.warn("'on' method is not ready yet");
+ super.on(type, handler);
+
+ if (type === 'streamCreated') {
+ // throw new Error("'streamCreated' event is not ready yet");
+ // TODO: Comprobar este evento
+
+ if (!!this.stream && this.stream.isLocalStreamPublished) {
+ this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
+ } else {
+ this.stream.ee.on('stream-created-by-publisher', () => {
+ this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
+ });
+ }
+ }
+ if (type === 'accessAllowed') {
+ if (this.accessAllowed) {
+ this.emitEvent('accessAllowed', []);
+ }
+ }
+ if (type === 'accessDenied') {
+ if (this.accessDenied) {
+ this.emitEvent('accessDenied', []);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * See {@link EventDispatcher.once}
+ */
+ once(type: K, handler: (event: PublisherEventMap[K]) => void): this {
+ throw new Error("'once' method is not ready yet");
+ super.once(type, handler);
+
+ if (type === 'streamCreated') {
+ if (!!this.stream && this.stream.isLocalStreamPublished) {
+ this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
+ } else {
+ this.stream.ee.once('stream-created-by-publisher', () => {
+ this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
+ });
+ }
+ }
+ if (type === 'accessAllowed') {
+ if (this.accessAllowed) {
+ this.emitEvent('accessAllowed', []);
+ }
+ }
+ if (type === 'accessDenied') {
+ if (this.accessDenied) {
+ this.emitEvent('accessDenied', []);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * See {@link EventDispatcher.off}
+ */
+ off(type: K, handler?: (event: PublisherEventMap[K]) => void): this {
+ // throw new Error("'off' method is not ready yet");
+ super.off(type, handler);
+ return this;
+ }
+
+ /**
+ * Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one
+ * without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one
+ * and publishing the new one).
+ *
+ * You can get this new MediaStreamTrack by using the native Web API or simply with {@link OpenVidu.getUserMedia} method.
+ *
+ * **WARNING: this method has been proven to work in the majority of cases, but there may be some combinations of published/replaced tracks that may be incompatible
+ * between them and break the connection in OpenVidu Server. A complete renegotiation may be the only solution in this case.
+ * Visit [RTCRtpSender.replaceTrack](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack) documentation for further details.**
+ *
+ * @param track The [MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) object to replace the current one.
+ * If it is an audio track, the current audio track will be the replaced one. If it is a video track, the current video track will be the replaced one.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved if the track was successfully replaced and rejected with an Error object in other case
+ */
+ async replaceTrack(track: MediaStreamTrack): Promise {
+ const localTracks = this.session.localParticipant.getTracks();
+ const trackKind = track.kind;
+
+ debugger;
+ console.log(this.videoReference);
+ if (localTracks.some((t) => t.isMuted)) {
+ console.error('Cannot replace track while muted');
+ return;
+ }
+ const kind: MediaDeviceKind = track.kind === 'audio' ? 'audioinput' : 'videoinput';
+ const deviceId = track.getSettings().deviceId;
+ if (!!kind && !!deviceId) {
+ await this.session.room.switchActiveDevice(kind, deviceId);
+ const trackMatched = localTracks.find((t) => t.kind === trackKind);
+ if (trackMatched) {
+ const videoTrack: LocalVideoTrack = trackMatched.track as LocalVideoTrack;
+ videoTrack.attach(this.videoReference);
+ this.stream.temporalVideoTrack = videoTrack;
+ } else {
+ console.error(`The participant does not have a ${trackKind} track`);
+ }
+
+ track.stop();
+ // this.replaceTrackAux(track, true);
+ } else {
+ throw new Error('Invalid MediaStreamTrack provided');
+ }
+ }
+
+ /* Hidden methods */
+
+ /**
+ * @hidden
+ */
+ async initialize(): Promise {
+ const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
+
+ let videoOptions: VideoCaptureOptions | undefined = undefined;
+ let audioOptions: AudioCaptureOptions | undefined = undefined;
+
+ let width: number;
+ let height: number;
+ const { constraints, audioTrack, videoTrack } = myConstraints;
+ const { audio: audioSource, video: videoSource } = constraints;
+ try {
+ width = (typeof constraints.video !== 'boolean' && Number(constraints?.video?.width?.toString())) || 640;
+ height = (typeof constraints.video !== 'boolean' && Number(constraints?.video?.height?.toString())) || 480;
+
+ if (typeof videoSource !== 'boolean' || videoSource) {
+ videoOptions = {
+ deviceId: videoSource?.['deviceId'].exact || videoSource?.['deviceId'] || undefined,
+ facingMode: 'user',
+ resolution: {
+ width,
+ height,
+ frameRate: videoSource?.['frameRate'] || 30
+ }
+ };
+ }
+ if (typeof audioSource !== 'boolean' || audioSource) {
+ audioOptions = {
+ deviceId: audioTrack?.getSettings().deviceId
+ };
+ }
+ } catch (error) {
+ console.warn('Invalid resolution provided in PublisherProperties. Setting default value (640x480)');
+ width = 640;
+ height = 480;
+ }
+
+ if (
+ (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
+ (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
+ (!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
+ ) {
+ // No need to call getUserMedia at all. MediaStreamTracks already provided
+ // TODO
+ throw new Error('MediaStreamTracks already provided. It is not implemented yet');
+ } else {
+ const outboundStreamOptions = {
+ mediaConstraints: constraints,
+ publisherProperties: this.properties
+ };
+ let constraintsAux: MediaStreamConstraints = {};
+ let startTime;
+
+ this.stream.setOutboundStreamOptions(outboundStreamOptions);
+ const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
+ constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
+ constraintsAux.video = constraints.video;
+ startTime = Date.now();
+
+ // this.setPermissionDialogTimer(timeForDialogEvent);
+ try {
+ if (this.stream.isSendScreen() /*&& navigator.mediaDevices['getDisplayMedia']*/ && !platform.isElectron()) {
+ // throw new Error('SCREEN getDisplayMedia not implemented yet');
+
+ const screenTrackOptions: ScreenShareCaptureOptions = {
+ audio: this.properties.audioSource === 'screen',
+ video: true,
+ systemAudio: 'include'
+ };
+
+ const myScreenTracks = await createLocalScreenTracks(screenTrackOptions);
+ this.accessAllowed = true;
+ this.accessDenied = false;
+ const screenVideoTrack = myScreenTracks.find((t) => t.kind === 'video') as LocalVideoTrack;
+ screenVideoTrack.mediaStreamTrack.contentHint = 'detail';
+ console.log('screenVideoTrack', screenVideoTrack);
+
+ console.log('screenVideoTrack', screenVideoTrack.mediaStreamTrack.contentHint);
+ const screenAdudioTrack = myScreenTracks.find((t) => t.kind === 'audio') as LocalAudioTrack;
+
+ // Generate a MediaStream with the screen track and the audio track
+ // const mediaStream = new MediaStream();
+ if (screenVideoTrack) {
+ // mediaStream.addTrack(screenVideoTrack.mediaStreamTrack);
+ this.stream.temporalVideoScreenTrack = screenVideoTrack;
+ this.stream.temporalVideoTrack = undefined;
+ }
+ if (screenAdudioTrack) {
+ // mediaStream.addTrack(screenVideoTrack.mediaStreamTrack);
+ this.stream.temporalAudioScreenTrack = screenAdudioTrack;
+ this.stream.temporalAudioTrack = undefined;
+ }
+ // this.stream.setMediaStream(mediaStream);
+
+ // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
+ // await getMediaSuccess(mediaStream, definedAudioConstraint);
+
+ this.stream.isLocalStreamReadyToPublish = true;
+ this.stream.ee.emitEvent('stream-ready-to-publish', []);
+ } else {
+ this.stream.lastVideoTrackConstraints = constraintsAux.video;
+ // const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
+ const [myVideoTrack, myAudioTrack] = await Promise.all([
+ createLocalVideoTrack(videoOptions),
+ createLocalAudioTrack(audioOptions)
+ ]);
+
+ // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
+ // await getMediaSuccess(mediaStream, definedAudioConstraint);
+
+ this.accessAllowed = true;
+ this.accessDenied = false;
+ this.stream.temporalVideoTrack = myVideoTrack;
+ this.stream.temporalAudioTrack = myAudioTrack;
+ this.stream.temporalVideoScreenTrack = undefined;
+ this.stream.temporalAudioScreenTrack = undefined;
+
+ // Generate a MediaStream with the screen track and the audio track
+ // const mediaStream = new MediaStream();
+ // mediaStream.addTrack(myVideoTrack.mediaStreamTrack);
+ // mediaStream.addTrack(myAudioTrack.mediaStreamTrack);
+ // this.stream.setMediaStream(mediaStream);
+ // console.log('setting media stream', mediaStream);
+
+ this.stream.isLocalStreamReadyToPublish = true;
+ this.stream.ee.emitEvent('stream-ready-to-publish', []);
+ }
+ } catch (error) {
+ console.error('Error getting user media', error);
+ // await getMediaError(error);
+ }
+ }
+
+ // throw new Error("'initialize' method is not ready yet");
+ // return new Promise(async (resolve, reject) => {
+ // let constraints: MediaStreamConstraints = {};
+ // let constraintsAux: MediaStreamConstraints = {};
+ // const timeForDialogEvent = 2000;
+ // let startTime;
+
+ // const errorCallback = (openViduError: OpenViduError) => {
+ // this.accessDenied = true;
+ // this.accessAllowed = false;
+ // logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
+ // return reject(openViduError);
+ // };
+
+ // const successCallback = (mediaStream: MediaStream) => {
+ // this.accessAllowed = true;
+ // this.accessDenied = false;
+
+ // if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
+ // mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
+ // mediaStream.addTrack(this.properties.audioSource);
+ // }
+
+ // if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
+ // mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
+ // mediaStream.addTrack(this.properties.videoSource);
+ // }
+
+ // // Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
+ // if (!!mediaStream.getAudioTracks()[0]) {
+ // const enabled =
+ // this.stream.audioActive !== undefined && this.stream.audioActive !== null
+ // ? this.stream.audioActive
+ // : !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
+ // mediaStream.getAudioTracks()[0].enabled = enabled;
+ // }
+ // if (!!mediaStream.getVideoTracks()[0]) {
+ // const enabled =
+ // this.stream.videoActive !== undefined && this.stream.videoActive !== null
+ // ? this.stream.videoActive
+ // : !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
+ // mediaStream.getVideoTracks()[0].enabled = enabled;
+ // }
+
+ // // Set Content Hint on all MediaStreamTracks
+ // for (const track of mediaStream.getAudioTracks()) {
+ // if (!track.contentHint?.length) {
+ // // contentHint for audio: "", "speech", "speech-recognition", "music".
+ // // https://w3c.github.io/mst-content-hint/#audio-content-hints
+ // track.contentHint = '';
+ // logger.info(`Audio track Content Hint set: '${track.contentHint}'`);
+ // }
+ // }
+ // for (const track of mediaStream.getVideoTracks()) {
+ // if (!track.contentHint?.length) {
+ // // contentHint for video: "", "motion", "detail", "text".
+ // // https://w3c.github.io/mst-content-hint/#video-content-hints
+ // switch (this.stream.typeOfVideo) {
+ // case TypeOfVideo.SCREEN:
+ // track.contentHint = 'detail';
+ // break;
+ // case TypeOfVideo.CUSTOM:
+ // logger.warn('CUSTOM type video track was provided without Content Hint!');
+ // track.contentHint = 'motion';
+ // break;
+ // case TypeOfVideo.CAMERA:
+ // case TypeOfVideo.IPCAM:
+ // default:
+ // track.contentHint = 'motion';
+ // break;
+ // }
+ // logger.info(`Video track Content Hint set: '${track.contentHint}'`);
+ // }
+ // }
+
+ // this.initializeVideoReference(mediaStream);
+
+ // if (!this.stream.displayMyRemote()) {
+ // // When we are subscribed to our remote we don't still set the MediaStream object in the video elements to
+ // // avoid early 'streamPlaying' event
+ // this.stream.updateMediaStreamInVideos();
+ // }
+ // delete this.firstVideoElement;
+
+ // if (this.stream.isSendVideo()) {
+ // // Has video track
+ // this.getVideoDimensions().then((dimensions) => {
+ // this.stream.videoDimensions = {
+ // width: dimensions.width,
+ // height: dimensions.height
+ // };
+
+ // if (this.stream.isSendScreen()) {
+
+ // if(this.stream.isSendAudio() && mediaStream.getAudioTracks().length === 0){
+ // // If sending audio is enabled and there are no audio tracks in the mediaStream, disable audio for screen sharing.
+ // this.stream.audioActive = false;
+ // this.stream.hasAudio = false;
+ // this.stream.outboundStreamOpts.publisherProperties.publishAudio = false;
+ // this.stream.outboundStreamOpts.publisherProperties.audioSource = false;
+ // }
+
+ // // Set interval to listen for screen resize events
+ // this.screenShareResizeInterval = setInterval(() => {
+ // const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
+ // const newWidth = settings.width;
+ // const newHeight = settings.height;
+ // const widthChanged = newWidth != null && newWidth !== this.stream.videoDimensions.width;
+ // const heightChanged = newHeight != null && newHeight !== this.stream.videoDimensions.height;
+ // if (this.stream.isLocalStreamPublished && (widthChanged || heightChanged)) {
+ // this.openvidu.sendVideoDimensionsChangedEvent(
+ // this,
+ // 'screenResized',
+ // this.stream.videoDimensions.width,
+ // this.stream.videoDimensions.height,
+ // newWidth || 0,
+ // newHeight || 0
+ // );
+ // }
+ // }, 650);
+ // }
+
+ // this.stream.isLocalStreamReadyToPublish = true;
+ // this.stream.ee.emitEvent('stream-ready-to-publish', []);
+ // });
+ // } else {
+ // // Only audio track (no videoDimensions)
+ // this.stream.isLocalStreamReadyToPublish = true;
+ // this.stream.ee.emitEvent('stream-ready-to-publish', []);
+ // }
+
+ // return resolve();
+ // };
+
+ // const getMediaSuccess = async (mediaStream: MediaStream, definedAudioConstraint) => {
+ // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
+ // if (this.stream.isSendScreen() && this.properties.audioSource !== 'screen' && this.stream.isSendAudio()) {
+ // // When getting desktop as user media audio constraint must be false. Now we can ask for it if required
+ // constraintsAux.audio = definedAudioConstraint;
+ // constraintsAux.video = false;
+ // startTime = Date.now();
+ // this.setPermissionDialogTimer(timeForDialogEvent);
+
+ // try {
+ // const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
+ // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
+ // mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
+ // successCallback(mediaStream);
+ // } catch (error) {
+ // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
+ // mediaStream.getAudioTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // mediaStream.getVideoTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // errorCallback(this.openvidu.generateAudioDeviceError(error, constraints));
+ // return;
+ // }
+ // } else {
+ // successCallback(mediaStream);
+ // }
+ // };
+
+ // const getMediaError = async (error) => {
+ // logger.error(`getMediaError: ${error.toString()}`);
+ // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
+ // if (error.name === 'Error') {
+ // // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
+ // error.name = error.constructor.name;
+ // }
+ // let errorName, errorMessage;
+ // switch (error.name.toLowerCase()) {
+ // case 'notfounderror':
+ // try {
+ // const mediaStream = await navigator.mediaDevices.getUserMedia({
+ // audio: false,
+ // video: constraints.video
+ // });
+ // mediaStream.getVideoTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
+ // errorMessage = error.toString();
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // } catch (error) {
+ // errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
+ // errorMessage = error.toString();
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // }
+
+ // break;
+ // case 'notallowederror':
+ // errorName = this.stream.isSendScreen()
+ // ? OpenViduErrorName.SCREEN_CAPTURE_DENIED
+ // : OpenViduErrorName.DEVICE_ACCESS_DENIED;
+ // errorMessage = error.toString();
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // break;
+ // case 'overconstrainederror':
+ // try {
+ // const mediaStream = await navigator.mediaDevices.getUserMedia({
+ // audio: false,
+ // video: constraints.video
+ // });
+ // mediaStream.getVideoTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // if (error.constraint.toLowerCase() === 'deviceid') {
+ // errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
+ // errorMessage =
+ // "Audio input device with deviceId '" +
+ // ((constraints.audio).deviceId!!).exact +
+ // "' not found";
+ // } else {
+ // errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
+ // errorMessage =
+ // "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
+ // }
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // } catch (error) {
+ // if (error.constraint.toLowerCase() === 'deviceid') {
+ // errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
+ // errorMessage =
+ // "Video input device with deviceId '" +
+ // ((constraints.video).deviceId!!).exact +
+ // "' not found";
+ // } else {
+ // errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
+ // errorMessage =
+ // "Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
+ // }
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // }
+
+ // break;
+ // case 'aborterror':
+ // case 'notreadableerror':
+ // errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
+ // errorMessage = error.toString();
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // break;
+ // default:
+ // errorName = OpenViduErrorName.GENERIC_ERROR;
+ // errorMessage = error.toString();
+ // errorCallback(new OpenViduError(errorName, errorMessage));
+ // break;
+ // }
+ // };
+
+ // try {
+ // const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
+ // if (
+ // (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
+ // (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
+ // (!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
+ // ) {
+ // // No need to call getUserMedia at all. MediaStreamTracks already provided
+ // successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
+ // } else {
+ // constraints = myConstraints.constraints;
+
+ // const outboundStreamOptions = {
+ // mediaConstraints: constraints,
+ // publisherProperties: this.properties
+ // };
+ // this.stream.setOutboundStreamOptions(outboundStreamOptions);
+
+ // const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
+ // constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
+ // constraintsAux.video = constraints.video;
+ // startTime = Date.now();
+ // this.setPermissionDialogTimer(timeForDialogEvent);
+
+ // try {
+ // if (this.stream.isSendScreen() && navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
+ // const mediaStream = await navigator.mediaDevices['getDisplayMedia']({ video: true, audio: this.properties.audioSource === 'screen' });
+ // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
+ // await getMediaSuccess(mediaStream, definedAudioConstraint);
+ // } else {
+ // this.stream.lastVideoTrackConstraints = constraintsAux.video;
+ // const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
+ // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
+ // await getMediaSuccess(mediaStream, definedAudioConstraint);
+ // }
+ // } catch (error) {
+ // await getMediaError(error);
+ // }
+ // }
+ // } catch (error) {
+ // errorCallback(error);
+ // }
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ async replaceTrackAux(track: MediaStreamTrack, updateLastConstraints: boolean): Promise {
+ // throw new Error("'replaceTrackAux' method is not ready yet");
+ // Set field "enabled" of the new track to the previous value
+ const trackOriginalEnabledValue: boolean = track.enabled;
+ if (track.kind === 'video') {
+ track.enabled = this.stream.videoActive;
+ } else if (track.kind === 'audio') {
+ track.enabled = this.stream.audioActive;
+ }
+ try {
+ if (this.stream.isLocalStreamPublished) {
+ // Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
+ // If it has not been published yet, replacing it on the MediaStream object is enough
+ this.replaceTrackInMediaStream(track, updateLastConstraints);
+ // return await this.replaceTrackInRtcRtpSender(track);
+ } else {
+ // Publisher not published. Simply replace the track on the local MediaStream
+ return this.replaceTrackInMediaStream(track, updateLastConstraints);
+ }
+ } catch (error) {
+ track.enabled = trackOriginalEnabledValue;
+ throw error;
+ }
+ }
+
+ /**
+ * @hidden
+ *
+ * To obtain the videoDimensions we wait for the video reference to have enough metadata
+ * and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
+ * use the HTMLVideoElement properties videoWidth and videoHeight
+ */
+ getVideoDimensions(): Promise<{ width: number; height: number }> {
+ throw new Error("'getVideoDimensions' method is not ready yet");
+ return new Promise((resolve, reject) => {
+ // Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
+ const requiresDomInsertion: boolean =
+ (platform.isIonicIos() || platform.isIOSWithSafari()) && this.videoReference.readyState < 1;
+
+ let loadedmetadataListener;
+ const resolveDimensions = () => {
+ let width: number;
+ let height: number;
+ if (typeof this.stream.getMediaStream().getVideoTracks()[0].getSettings === 'function') {
+ const settings = this.stream.getMediaStream().getVideoTracks()[0].getSettings();
+ width = settings.width || this.videoReference.videoWidth;
+ height = settings.height || this.videoReference.videoHeight;
+ } else {
+ logger.warn('MediaStreamTrack does not have getSettings method on ' + platform.getDescription());
+ width = this.videoReference.videoWidth;
+ height = this.videoReference.videoHeight;
+ }
+
+ if (loadedmetadataListener != null) {
+ this.videoReference.removeEventListener('loadedmetadata', loadedmetadataListener);
+ }
+ if (requiresDomInsertion) {
+ document.body.removeChild(this.videoReference);
+ }
+
+ return resolve({ width, height });
+ };
+
+ if (this.videoReference.readyState >= 1) {
+ // The video already has metadata available
+ // No need of loadedmetadata event
+ resolveDimensions();
+ } else {
+ // The video does not have metadata available yet
+ // Must listen to loadedmetadata event
+ loadedmetadataListener = () => {
+ if (!this.videoReference.videoWidth) {
+ let interval = setInterval(() => {
+ if (!!this.videoReference.videoWidth) {
+ clearInterval(interval);
+ resolveDimensions();
+ }
+ }, 40);
+ } else {
+ resolveDimensions();
+ }
+ };
+ this.videoReference.addEventListener('loadedmetadata', loadedmetadataListener);
+ if (requiresDomInsertion) {
+ document.body.appendChild(this.videoReference);
+ }
+ }
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ reestablishStreamPlayingEvent() {
+ // throw new Error("'reestablishStreamPlayingEvent' method is not ready yet");
+ if (this.ee.getListeners('streamPlaying').length > 0) {
+ this.addPlayEventToFirstVideo();
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ initializeVideoReference(mediaStream: MediaStream) {
+ throw new Error("'initializeVideoReference' method is not ready yet");
+ // this.videoReference = document.createElement('video');
+ // this.videoReference.style.display = 'none';
+ // this.videoReference.muted = true;
+ // this.videoReference.autoplay = true;
+ // this.videoReference.controls = false;
+ // if (
+ // platform.isSafariBrowser() ||
+ // (platform.isIPhoneOrIPad() &&
+ // (platform.isChromeMobileBrowser() ||
+ // platform.isEdgeMobileBrowser() ||
+ // platform.isOperaMobileBrowser() ||
+ // platform.isFirefoxMobileBrowser()))
+ // ) {
+ // this.videoReference.playsInline = true;
+ // }
+ // this.stream.setMediaStream(mediaStream);
+ // if (!!this.firstVideoElement) {
+ // this.createVideoElement(this.firstVideoElement.targetElement, this.properties.insertMode);
+ // }
+ // this.videoReference.srcObject = this.stream.getMediaStream();
+ }
+
+ /**
+ * @hidden
+ */
+ replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
+ // throw new Error("'replaceTrackInMediaStream' method is not ready yet");
+ const mediaStream: MediaStream = this.stream.displayMyRemote()
+ ? this.stream.localMediaStreamWhenSubscribedToRemote!
+ : this.stream.getMediaStream();
+ let removedTrack: MediaStreamTrack;
+ if (track.kind === 'video') {
+ removedTrack = mediaStream.getVideoTracks()[0];
+ if (updateLastConstraints) {
+ this.stream.lastVideoTrackConstraints = track.getConstraints();
+ }
+ } else {
+ removedTrack = mediaStream.getAudioTracks()[0];
+ }
+ removedTrack.enabled = false;
+ removedTrack.stop();
+ mediaStream.removeTrack(removedTrack);
+ mediaStream.addTrack(track);
+ const trackInfo = {
+ oldLabel: removedTrack?.label || '',
+ newLabel: track?.label || ''
+ };
+ if (track.kind === 'video' && updateLastConstraints) {
+ this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
+ this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'videoTrack');
+ // if (this.stream.isLocalStreamPublished) {
+ // this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
+ // }
+ } else if (track.kind === 'audio' && updateLastConstraints) {
+ this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'audioTrack');
+ }
+ // if (track.kind === 'audio') {
+ // this.stream.disableHarkSpeakingEvent(false);
+ // this.stream.disableHarkStoppedSpeakingEvent(false);
+ // this.stream.disableHarkVolumeChangeEvent(false);
+ // this.stream.initHarkEvents();
+ // }
+ }
+
+ /* Private methods */
+
+ private setPermissionDialogTimer(waitTime: number): void {
+ throw new Error("'setPermissionDialogTimer' method is not ready yet");
+ this.permissionDialogTimeout = setTimeout(() => {
+ this.emitEvent('accessDialogOpened', []);
+ }, waitTime);
+ }
+
+ private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
+ throw new Error("'clearPermissionDialogTimer' method is not ready yet");
+ clearTimeout(this.permissionDialogTimeout);
+ if (Date.now() - startTime > waitTime) {
+ // Permission dialog was shown and now is closed
+ this.emitEvent('accessDialogClosed', []);
+ }
+ }
+
+ private async replaceTrackInRtcRtpSender(track: MediaStreamTrack): Promise {
+ throw new Error("'replaceTrackInRtcRtpSender' method is not ready yet");
+ const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
+ let sender: RTCRtpSender | undefined;
+ if (track.kind === 'video') {
+ sender = senders.find((s) => !!s.track && s.track.kind === 'video');
+ if (!sender) {
+ throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
+ }
+ } else if (track.kind === 'audio') {
+ sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
+ if (!sender) {
+ throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
+ }
+ } else {
+ throw new Error('Unknown track kind ' + track.kind);
+ }
+ await (sender as RTCRtpSender).replaceTrack(track);
+ }
+}
diff --git a/openvidu-browser/old/OldSession.ts b/openvidu-browser/old/OldSession.ts
new file mode 100644
index 00000000000..29f374633af
--- /dev/null
+++ b/openvidu-browser/old/OldSession.ts
@@ -0,0 +1,2160 @@
+/*
+ * (C) Copyright 2017-2022 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 { OldConnection as Connection } from './OldConnection';
+import { Filter } from '../Filter';
+import { OpenVidu } from './OldOpenVidu';
+import { Publisher } from './OldPublisher';
+import { Stream } from './OldStream';
+import { StreamManager } from '../StreamManager';
+import { Subscriber } from '../Subscriber';
+import { Capabilities } from '../../OpenViduInternal/Interfaces/Public/Capabilities';
+import { EventDispatcher } from '../EventDispatcher';
+import { SignalOptions } from '../../OpenViduInternal/Interfaces/Public/SignalOptions';
+import { SubscriberProperties } from '../../OpenViduInternal/Interfaces/Public/SubscriberProperties';
+import { RemoteConnectionOptions } from '../../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
+import { LocalConnectionOptions } from '../../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
+import { SessionOptions } from '../../OpenViduInternal/Interfaces/Private/SessionOptions';
+import { SessionEventMap } from '../../OpenViduInternal/Events/EventMap/SessionEventMap';
+import { ConnectionEvent } from '../../OpenViduInternal/Events/ConnectionEvent';
+import { FilterEvent } from '../../OpenViduInternal/Events/FilterEvent';
+import { RecordingEvent } from '../../OpenViduInternal/Events/RecordingEvent';
+import { SessionDisconnectedEvent } from '../../OpenViduInternal/Events/SessionDisconnectedEvent';
+import { SignalEvent } from '../../OpenViduInternal/Events/SignalEvent';
+import { SpeechToTextEvent } from '../../OpenViduInternal/Events/SpeechToTextEvent';
+import { StreamEvent } from '../../OpenViduInternal/Events/StreamEvent';
+import { StreamPropertyChangedEvent } from '../../OpenViduInternal/Events/StreamPropertyChangedEvent';
+import { ConnectionPropertyChangedEvent } from '../../OpenViduInternal/Events/ConnectionPropertyChangedEvent';
+import { NetworkQualityLevelChangedEvent } from '../../OpenViduInternal/Events/NetworkQualityLevelChangedEvent';
+import { OpenViduError, OpenViduErrorName } from '../../OpenViduInternal/Enums/OpenViduError';
+import { VideoInsertMode } from '../../OpenViduInternal/Enums/VideoInsertMode';
+import { OpenViduLogger } from '../../OpenViduInternal/Logger/OpenViduLogger';
+import { PlatformUtils } from '../../OpenViduInternal/Utils/Platform';
+import {
+ StreamPropertyChangedEventReason,
+ ChangedPropertyType,
+ RecordingEventReason,
+ ConnectionEventReason,
+ StreamEventReason
+} from '../../OpenViduInternal/Events/Types/Types';
+/**
+ * @hidden
+ */
+import semverMajor = require('semver/functions/major');
+/**
+ * @hidden
+ */
+import semverMinor = require('semver/functions/minor');
+import { ExceptionEvent, ExceptionEventName } from '../../OpenViduInternal/Events/ExceptionEvent';
+import {
+ ConnectionQuality,
+ ConnectionState,
+ DataPublishOptions,
+ LocalTrack,
+ LocalTrackPublication,
+ Participant,
+ RemoteAudioTrack,
+ RemoteParticipant,
+ RemoteTrack,
+ RemoteTrackPublication,
+ RemoteVideoTrack,
+ Room,
+ RoomEvent,
+ Track,
+ DataPacket_Kind
+} from 'livekit-client';
+import { StreamOptionsServer } from '../../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
+import { TypeOfVideo } from '../../OpenViduInternal/Enums/TypeOfVideo';
+
+/**
+ * @hidden
+ */
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+
+/**
+ * @hidden
+ */
+let platform: PlatformUtils;
+
+/**
+ * Represents a video call. It can also be seen as a videoconference room where multiple users can connect.
+ * Participants who publish their videos to a session can be seen by the rest of users connected to that specific session.
+ * Initialized with {@link OpenVidu.initSession} method.
+ *
+ * See available event listeners at {@link SessionEventMap}.
+ */
+export class OldSession extends EventDispatcher {
+ /**
+ * Local connection to the Session. This object is defined only after {@link Session.connect} has been successfully executed, and can be retrieved subscribing to `connectionCreated` event
+ */
+ connection: Connection;
+
+ /**
+ * Collection of all StreamManagers of this Session ({@link Publisher} and {@link Subscriber})
+ */
+ streamManagers: StreamManager[] = [];
+
+ /**
+ * Object defining the methods that the client is able to call. These are defined by the {@link Connection.role}.
+ * This object is only defined after {@link Session.connect} has been successfully resolved
+ */
+ capabilities: Capabilities;
+
+ // This map is only used to avoid race condition between 'joinRoom' response and 'onParticipantPublished' notification
+ /**
+ * @hidden
+ */
+ remoteStreamsCreated: Map = new Map();
+
+ /**
+ * @hidden
+ */
+ openvidu: OpenVidu;
+ /**
+ * @hidden
+ */
+ // options: SessionOptions;
+ /**
+ * @hidden
+ */
+ token: string;
+
+ /**
+ * @hidden
+ */
+ room: Room;
+
+ private streamCreatedInterval: NodeJS.Timeout | undefined = undefined;
+ private streamDestroyedInterval: NodeJS.Timeout | undefined = undefined;
+ private ovRemoteConnections: Map = new Map();
+ private temporalPublications: Map = new Map();
+
+ private networkQualityLevelValue: number = 5;
+
+ /**
+ * @hidden
+ */
+ constructor(openvidu: OpenVidu) {
+ super();
+ this.room = new Room();
+ platform = PlatformUtils.getInstance();
+ this.openvidu = openvidu;
+ }
+
+ /**
+ * Unique identifier of the Session
+ */
+ get sessionId(): string {
+ return this.room.name;
+ }
+
+ get remoteConnections(): Map {
+ //TODO
+ if (!this.room) return new Map();
+
+ const participants = this.room.participants;
+ participants.forEach((participant: RemoteParticipant) => {
+ const opt: RemoteConnectionOptions = {
+ id: participant.sid,
+ createdAt: participant.joinedAt?.getTime() ?? -1, // miliseconds
+ metadata: participant.metadata || '',
+ streams: []
+ };
+ this.ovRemoteConnections.set(participant.sid, new Connection(this, opt));
+ });
+ return this.ovRemoteConnections;
+ }
+
+ connect(token: string): Promise;
+ connect(token: string, metadata: any): Promise;
+
+ /**
+ * Connects to the session using `token`. Parameter `metadata` allows you to pass extra data to share with other users when
+ * they receive `streamCreated` event. The structure of `metadata` string is up to you (maybe some standardized format
+ * as JSON or XML is a good idea).
+ *
+ * This metadata is not considered secure, as it is generated in the client side. To pass secure data, add it as a parameter in the
+ * token generation operation (through the API REST, openvidu-java-client or openvidu-node-client).
+ *
+ * Only after the returned Promise is successfully resolved {@link Session.connection} object will be available and properly defined.
+ *
+ * #### Events dispatched
+ *
+ * The {@link Session} object of the local participant will first dispatch one or more `connectionCreated` events upon successful termination of this method:
+ * - First one for your own local Connection object, so you can retrieve {@link Session.connection} property.
+ * - Then one for each remote Connection previously connected to the Session, if any. Any other remote user connecting to the Session after you have
+ * successfully connected will also dispatch a `connectionCreated` event when they do so.
+ *
+ * The {@link Session} object of the local participant will also dispatch a `streamCreated` event for each remote active {@link Publisher} that was already streaming
+ * when connecting, just after dispatching all remote `connectionCreated` events.
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `connectionCreated` event.
+ *
+ * See {@link ConnectionEvent} and {@link StreamEvent} to learn more.
+ *
+ * @returns A Promise to which you must subscribe that is resolved if the the connection to the Session was successful and rejected with an Error object if not
+ *
+ */
+ async connect(token: string, metadata?: any): Promise {
+ try {
+ //'ws://localhost:7880/';
+ const url = this.getLivekitUrlFromMetadata(token);
+ this.token = token;
+ console.log('connecting to', url, token, metadata);
+ if (this.room) {
+ this.room.on(RoomEvent.Connected, () => {
+ //TODO Generate this info properly with the room and server-client-adapter info
+ const opts: LocalConnectionOptions = {
+ id: this.room?.localParticipant.sid,
+ finalUserId: this.room?.localParticipant.identity,
+ createdAt: Number(BigInt(this.room?.['roomInfo']?.creationTime)) * 1000, // miliseconds
+ metadata: this.room?.localParticipant.metadata || '',
+ value: [],
+ session: this.room?.['roomInfo']?.sid,
+ sessionId: this.room?.['roomInfo']?.name,
+ role: 'PUBLISHER',
+ record: false,
+ coturnIp: '',
+ coturnPort: 0,
+ turnUsername: '',
+ turnCredential: '',
+ version: '3.0.0',
+ mediaServer: '',
+ videoSimulcast: true,
+ life: 0
+ };
+ this.processSessionConnected(token, opts);
+ console.log('SESSION CONNECTED!!!!!!!!', this.room);
+ });
+
+ return this.room.connect(url, token);
+ }
+ console.error('Room not defined. Cannot connect');
+ } catch (error) {
+ console.error(
+ `Error connecting to ${platform.getName()} (version ${platform.getVersion()}) for ${platform.getFamily()}:`,
+ error
+ );
+
+ throw new Error(`Error connecting to session: ${error}`);
+ }
+ }
+
+ private processSessionConnected(token: string, options: LocalConnectionOptions) {
+ // this.processJoinRoomResponse(options, token);
+
+ // Initialize local Connection object with values returned by openvidu-server
+ this.connection = new Connection(this, options);
+
+ // Initialize remote Connections with value returned by openvidu-server
+ // const events = {
+ // connections: new Array(),
+ // streams: new Array()
+ // };
+ // const existingParticipants: RemoteConnectionOptions[] = response.value;
+ // existingParticipants.forEach((remoteConnectionOptions: RemoteConnectionOptions) => {
+ // const connection = new Connection(this, remoteConnectionOptions);
+ // this.remoteConnections.set(connection.connectionId, connection);
+ // events.connections.push(connection);
+ // if (!!connection.stream) {
+ // this.remoteStreamsCreated.set(connection.stream.streamId, true);
+ // events.streams.push(connection.stream);
+ // }
+ // });
+
+ // // Own 'connectionCreated' event
+ // this.ee.emitEvent('connectionCreated', [
+ // new ConnectionEvent(false, this, 'connectionCreated', this.connection, '')
+ // ]);
+
+ // // One 'connectionCreated' event for each existing connection in the session
+ // events.connections.forEach((connection) => {
+ // this.ee.emitEvent('connectionCreated', [
+ // new ConnectionEvent(false, this, 'connectionCreated', connection, '')
+ // ]);
+ // });
+ // // One 'streamCreated' event for each active stream in the session
+ // events.streams.forEach((stream) => {
+ // this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', stream, '')]);
+ // });
+
+ // if (!!response.recordingId && !!response.recordingName) {
+ // this.ee.emitEvent('recordingStarted', [
+ // new RecordingEvent(this, 'recordingStarted', response.recordingId, response.recordingName)
+ // ]);
+ // }
+ }
+
+ /**
+ * Leaves the session, destroying all streams and deleting the user as a participant.
+ *
+ * #### Events dispatched
+ *
+ * The {@link Session} object of the local participant will dispatch a `sessionDisconnected` event.
+ * This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
+ * and also deletes any HTML video element associated to each Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
+ * For every video removed, each Subscriber object will dispatch a `videoElementDestroyed` event.
+ * Call `event.preventDefault()` upon event `sessionDisconnected` to avoid this behavior and take care of disposing and cleaning all the Subscriber objects yourself.
+ * See {@link SessionDisconnectedEvent} and {@link VideoElementEvent} to learn more.
+ *
+ * The {@link Publisher} object of the local participant will dispatch a `streamDestroyed` event if there is a {@link Publisher} object publishing to the session.
+ * This event will automatically stop all media tracks and delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
+ * For every video removed, the Publisher object will dispatch a `videoElementDestroyed` event.
+ * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session (to do so it is a mandatory requirement to call `Session.unpublish()`
+ * or/and `Session.disconnect()` in the previous session). See {@link StreamEvent} and {@link VideoElementEvent} to learn more.
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `streamDestroyed` event if the disconnected participant was publishing.
+ * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
+ * and also deletes any HTML video element associated to that Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
+ * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
+ * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object yourself.
+ * See {@link StreamEvent} and {@link VideoElementEvent} to learn more.
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `connectionDestroyed` event in any case. See {@link ConnectionEvent} to learn more.
+ */
+ disconnect(): void {
+ this.leave(false, 'disconnect');
+ }
+
+ subscribe(stream: Stream, targetElement: string | HTMLElement | undefined): Subscriber;
+ subscribe(stream: Stream, targetElement: string | HTMLElement | undefined, properties: SubscriberProperties): Subscriber;
+ subscribe(
+ stream: Stream,
+ targetElement: string | HTMLElement | undefined,
+ completionHandler: (error: Error | undefined) => void
+ ): Subscriber;
+ subscribe(
+ stream: Stream,
+ targetElement: string | HTMLElement | undefined,
+ properties: SubscriberProperties,
+ completionHandler: (error: Error | undefined) => void
+ ): Subscriber;
+
+ /**
+ * Subscribes to a `stream`, adding a new HTML video element to DOM with `subscriberProperties` settings. This method is usually called in the callback of `streamCreated` event.
+ *
+ * #### Events dispatched
+ *
+ * The {@link Subscriber} object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM (only if you
+ * [let OpenVidu take care of the video players](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)). See {@link VideoElementEvent} to learn more.
+ *
+ * The {@link Subscriber} object will dispatch a `streamPlaying` event once the remote stream starts playing. See {@link StreamManagerEvent} to learn more.
+ *
+ * @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 {@link SubscriberProperties.insertMode}). If *null* or *undefined* no default video will be created for this Subscriber.
+ * You can always call method {@link Subscriber.addVideoElement} or {@link Subscriber.createVideoElement} to manage the video elements on your own (see [Manage video players](/en/stable/cheatsheet/manage-videos) section)
+ * @param completionHandler `error` parameter is null if `subscribe` succeeds, and is defined if it fails.
+ */
+ subscribe(
+ stream: Stream,
+ targetElement: string | HTMLElement | undefined,
+ param3?: ((error: Error | undefined) => void) | SubscriberProperties,
+ param4?: (error: Error | undefined) => void
+ ): Subscriber {
+ console.warn("'subscribe' method is not ready yet.");
+ console.log('subscribe', stream, targetElement, param3, param4);
+ let properties: SubscriberProperties = {};
+ if (!!param3 && typeof param3 !== 'function') {
+ properties = {
+ insertMode:
+ typeof param3.insertMode !== 'undefined'
+ ? typeof param3.insertMode === 'string'
+ ? VideoInsertMode[param3.insertMode]
+ : properties.insertMode
+ : VideoInsertMode.APPEND,
+ subscribeToAudio: typeof param3.subscribeToAudio !== 'undefined' ? param3.subscribeToAudio : true,
+ subscribeToVideo: typeof param3.subscribeToVideo !== 'undefined' ? param3.subscribeToVideo : true
+ };
+ } else {
+ properties = {
+ insertMode: VideoInsertMode.APPEND,
+ subscribeToAudio: true,
+ subscribeToVideo: true
+ };
+ }
+
+ let completionHandler: (error: Error | undefined) => void = () => {};
+ if (!!param3 && typeof param3 === 'function') {
+ completionHandler = param3;
+ } else if (!!param4) {
+ completionHandler = param4;
+ }
+
+ if (!this.sessionConnected()) {
+ // if (completionHandler !== undefined) {
+ completionHandler(this.notConnectedError());
+ // }
+ throw this.notConnectedError();
+ }
+
+ logger.info('Subscribing to ' + stream.connection.connectionId);
+
+ // stream
+ // .subscribe()
+ // .then(() => {
+ // logger.info('Subscribed correctly to ' + stream.connection.connectionId);
+ // if (completionHandler !== undefined) {
+ // completionHandler(undefined);
+ // }
+ // })
+ // .catch((error) => {
+ // if (completionHandler !== undefined) {
+ // completionHandler(error);
+ // }
+ // });
+ completionHandler(undefined);
+ const subscriber = new Subscriber(stream, targetElement, properties);
+ if (!!subscriber.targetElement) {
+ stream.streamManager.createVideoElement(subscriber.targetElement, properties.insertMode);
+ }
+
+ return subscriber;
+ }
+
+ /**
+ * Promisified version of {@link Session.subscribe}
+ */
+ subscribeAsync(stream: Stream, targetElement: string | HTMLElement): Promise;
+ subscribeAsync(stream: Stream, targetElement: string | HTMLElement, properties: SubscriberProperties): Promise;
+
+ subscribeAsync(stream: Stream, targetElement: string | HTMLElement, properties?: SubscriberProperties): Promise {
+ throw new Error("'subscribeAsync' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ if (!this.sessionConnected()) {
+ return reject(this.notConnectedError());
+ }
+
+ let subscriber: Subscriber;
+
+ const callback = (error: Error) => {
+ if (!!error) {
+ return reject(error);
+ } else {
+ return resolve(subscriber);
+ }
+ };
+
+ if (!!properties) {
+ subscriber = this.subscribe(stream, targetElement, properties, callback);
+ } else {
+ subscriber = this.subscribe(stream, targetElement, callback);
+ }
+ });
+ }
+
+ /**
+ * Unsubscribes from `subscriber`, automatically removing its associated HTML video elements.
+ *
+ * #### Events dispatched
+ *
+ * The {@link Subscriber} object will dispatch a `videoElementDestroyed` event for each video associated to it that was removed from DOM.
+ * Only videos [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)) will be automatically removed
+ *
+ * See {@link VideoElementEvent} to learn more
+ */
+ unsubscribe(subscriber: Subscriber): Promise {
+ throw new Error("'unsubscribe' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ if (!this.sessionConnected()) {
+ return reject(this.notConnectedError());
+ } else {
+ const connectionId = subscriber.stream.connection.connectionId;
+
+ logger.info('Unsubscribing from ' + connectionId);
+
+ this.openvidu.sendRequest(
+ 'unsubscribeFromVideo',
+ { sender: subscriber.stream.connection.connectionId },
+ (error, response) => {
+ if (error) {
+ logger.error('Error unsubscribing from ' + connectionId);
+ return reject(error);
+ } else {
+ logger.info('Unsubscribed correctly from ' + connectionId);
+ subscriber.stream.streamManager.removeAllVideos();
+ subscriber.stream.disposeWebRtcPeer();
+ subscriber.stream.disposeMediaStream();
+ return resolve();
+ }
+ }
+ );
+ }
+ });
+ }
+
+ /**
+ * Publishes to the Session the Publisher object
+ *
+ * #### Events dispatched
+ *
+ * The local {@link Publisher} object will dispatch a `streamCreated` event upon successful termination of this method. See {@link StreamEvent} to learn more.
+ *
+ * The local {@link Publisher} object will dispatch a `streamPlaying` once the media stream starts playing. See {@link StreamManagerEvent} to learn more.
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `streamCreated` event so they can subscribe to it. See {@link StreamEvent} to learn more.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the publisher was successfully published and rejected with an Error object if not
+ */
+ async publish(publisher: Publisher): Promise {
+ console.warn('publish not ready yet in Session', this.connection);
+
+ if (!this.sessionConnected()) {
+ return Promise.reject(this.notConnectedError());
+ }
+
+ publisher.session = this;
+ publisher.stream.session = this;
+ try {
+ if (publisher.stream.publishedOnce) {
+ await publisher.initialize();
+ publisher.reestablishStreamPlayingEvent();
+ }
+
+ this.connection.addStream(publisher.stream);
+ await publisher.stream.publish();
+ } catch (error) {
+ console.error('error publishing stream', error);
+ return Promise.reject(error);
+ }
+
+ console.log('publish', publisher);
+
+ return Promise.resolve();
+ }
+
+ /**
+ * Unpublishes from the Session the Publisher object.
+ *
+ * #### Events dispatched
+ *
+ * The {@link Publisher} object of the local participant will dispatch a `streamDestroyed` event.
+ * This event will automatically stop all media tracks and delete any HTML video element associated to this Publisher
+ * (only those videos [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
+ * For every video removed, the Publisher object will dispatch a `videoElementDestroyed` event.
+ * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session.
+ *
+ * The {@link Session} object of every other participant connected to the session will dispatch a `streamDestroyed` event.
+ * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) and
+ * delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
+ * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
+ * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object on your own.
+ *
+ * See {@link StreamEvent} and {@link VideoElementEvent} to learn more.
+ */
+ async unpublish(publisher: Publisher): Promise {
+ // throw new Error("'unpublish' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ if (!this.sessionConnected()) {
+ throw this.notConnectedError();
+ }
+
+ const stream = publisher.stream;
+
+ if (!stream.connection) {
+ throw new Error('The associated Connection object of this Publisher is null');
+ } else if (stream.connection !== this.connection) {
+ throw new Error(
+ 'The associated Connection object of this Publisher is not your local Connection. ' +
+ "Only moderators can force unpublish on remote Streams via 'forceUnpublish' method"
+ );
+ } else {
+ logger.info('Unpublishing local media (' + stream.connection.connectionId + ')');
+
+ // this.openvidu.sendRequest('unpublishVideo', (error, response) => {
+ // if (error) {
+ // return reject(error);
+ // } else {
+
+ let tracks: LocalTrack[] = [];
+ this.room.localParticipant.tracks.forEach((track) => {
+ if (track.track) {
+ tracks.push(track.track);
+ }
+ });
+
+ await this.room.localParticipant.unpublishTracks(tracks);
+ logger.info('Media unpublished correctly');
+
+ // stream.disposeWebRtcPeer();
+
+ if (stream.connection.stream == stream) {
+ // The Connection.stream may have changed if Session.publish was called with other Publisher
+ delete stream.connection.stream;
+ }
+
+ const streamEvent = new StreamEvent(true, publisher, 'streamDestroyed', publisher.stream, 'unpublish');
+ publisher.emitEvent('streamDestroyed', [streamEvent]);
+ streamEvent.callDefaultBehavior();
+
+ // return resolve();
+ // }
+ // });
+ }
+ // });
+ }
+
+ /**
+ * Forces some user to leave the session
+ *
+ * #### Events dispatched
+ *
+ * The behavior is the same as when some user calls {@link Session.disconnect}, but `reason` property in all events will be `"forceDisconnectByUser"`.
+ *
+ * The {@link Session} object of every participant will dispatch a `streamDestroyed` event if the evicted user was publishing a stream, with property `reason` set to `"forceDisconnectByUser"`.
+ * The {@link Session} object of every participant except the evicted one will dispatch a `connectionDestroyed` event for the evicted user, with property `reason` set to `"forceDisconnectByUser"`.
+ *
+ * If any, the {@link Publisher} object of the evicted participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`.
+ * The {@link Session} object of the evicted participant will dispatch a `sessionDisconnected` event with property `reason` set to `"forceDisconnectByUser"`.
+ *
+ * See {@link StreamEvent}, {@link ConnectionEvent} and {@link SessionDisconnectedEvent} to learn more.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the participant has been successfully evicted from the session and rejected with an Error object if not
+ */
+ forceDisconnect(connection: Connection): Promise {
+ throw new Error("'forceDisconnect' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ if (!this.sessionConnected()) {
+ return reject(this.notConnectedError());
+ }
+
+ logger.info('Forcing disconnect for connection ' + connection.connectionId);
+ this.openvidu.sendRequest('forceDisconnect', { connectionId: connection.connectionId }, (error, response) => {
+ if (error) {
+ logger.error('Error forcing disconnect for Connection ' + connection.connectionId, error);
+ if (error.code === 401) {
+ return reject(
+ new OpenViduError(
+ OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
+ "You don't have permissions to force a disconnection"
+ )
+ );
+ } else {
+ return reject(error);
+ }
+ } else {
+ logger.info('Forcing disconnect correctly for Connection ' + connection.connectionId);
+ return resolve();
+ }
+ });
+ });
+ }
+
+ /**
+ * Forces some user to unpublish a Stream
+ *
+ * #### Events dispatched
+ *
+ * The behavior is the same as when some user calls {@link Session.unpublish}, but `reason` property in all events will be `"forceUnpublishByUser"`
+ *
+ * The {@link Session} object of every participant will dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
+ *
+ * The {@link Publisher} object of the affected participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
+ *
+ * See {@link StreamEvent} to learn more.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the remote Stream has been successfully unpublished from the session and rejected with an Error object if not
+ */
+ forceUnpublish(stream: Stream): Promise {
+ throw new Error("'forceUnpublish' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ if (!this.sessionConnected()) {
+ return reject(this.notConnectedError());
+ }
+
+ logger.info('Forcing unpublish for stream ' + stream.streamId);
+ this.openvidu.sendRequest('forceUnpublish', { streamId: stream.streamId }, (error, response) => {
+ if (error) {
+ logger.error('Error forcing unpublish for Stream ' + stream.streamId, error);
+ if (error.code === 401) {
+ return reject(
+ new OpenViduError(
+ OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
+ "You don't have permissions to force an unpublishing"
+ )
+ );
+ } else {
+ return reject(error);
+ }
+ } else {
+ logger.info('Forcing unpublish correctly for Stream ' + stream.streamId);
+ return resolve();
+ }
+ });
+ });
+ }
+
+ /**
+ * Sends one signal. `signal` object has the following optional properties:
+ * ```json
+ * {data:string, to:Connection[], type:string}
+ * ```
+ * All users subscribed to that signal (`session.on('signal:type', ...)` or `session.on('signal', ...)` for all signals) and whose Connection objects are in `to` array will receive it. Their local
+ * Session objects will dispatch a `signal` or `signal:type` event. See {@link SignalEvent} to learn more.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved if the message successfully reached openvidu-server and rejected with an Error object if not. _This doesn't
+ * mean that openvidu-server could resend the message to all the listed receivers._
+ */
+ signal(signal: SignalOptions): Promise {
+ if (!this.sessionConnected()) {
+ return Promise.reject(this.notConnectedError());
+ }
+
+ const signalMessage: { data: string; type: string; to: string[] } = {
+ to: [],
+ data: signal.data ? signal.data : '',
+ type: ''
+ };
+
+ if (signal.to && signal.to.length > 0) {
+ const connectionIds: string[] = [];
+ signal.to.forEach((connection) => {
+ if (!!connection.connectionId) {
+ connectionIds.push(connection.connectionId);
+ }
+ });
+ signalMessage['to'] = connectionIds;
+ }
+
+ let typeAux: string = signal.type ? signal.type : 'signal';
+ if (!!typeAux) {
+ if (typeAux.substring(0, 7) !== 'signal:') {
+ typeAux = 'signal:' + typeAux;
+ }
+ }
+ signalMessage['type'] = typeAux;
+
+ const data: Uint8Array = new TextEncoder().encode(signalMessage.data);
+ const kind: DataPacket_Kind = DataPacket_Kind.RELIABLE;
+ const publishOptions: DataPublishOptions = {
+ topic: signalMessage.type,
+ destination: signalMessage.to
+ };
+ return this.room.localParticipant.publishData(data, kind, publishOptions);
+ }
+
+ /**
+ * Subscribe to the Speech-To-Text events for this {@link Stream}. The Session object will emit {@link SpeechToTextEvent} for the Stream
+ * when speech is detected in its audio track.
+ *
+ * @param stream - The Stream for which you want to start receiving {@link SpeechToTextEvent}.
+ * @param lang - The language of the Stream's audio track. It must be a valid [BCP-47](https://tools.ietf.org/html/bcp47) language tag like "en-US" or "es-ES".
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved if the speech-to-text subscription
+ * was successful and rejected with an Error object if not.
+ */
+ subscribeToSpeechToText(stream: Stream, lang: string): Promise {
+ throw new Error("'subscribeToSpeechToText' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ this.openvidu.sendRequest(
+ 'subscribeToSpeechToText',
+ {
+ connectionId: stream.connection.connectionId,
+ lang
+ },
+ (error, response) => {
+ if (!!error) {
+ return reject(error);
+ } else {
+ return resolve();
+ }
+ }
+ );
+ });
+ }
+
+ /**
+ * Unsubscribe from the Speech-To-Text events for this {@link Stream}.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved if the speech-to-text subscription
+ * was successful and rejected with an Error object if not.
+ */
+ unsubscribeFromSpeechToText(stream: Stream): Promise {
+ throw new Error("'unsubscribeFromSpeechToText' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ this.openvidu.sendRequest(
+ 'unsubscribeFromSpeechToText',
+ {
+ connectionId: stream.connection.connectionId
+ },
+ (error, response) => {
+ if (!!error) {
+ return reject(error);
+ } else {
+ return resolve();
+ }
+ }
+ );
+ });
+ }
+
+ /**
+ * See {@link EventDispatcher.on}
+ */
+ on(type: K, handler: (event: SessionEventMap[K]) => void): this {
+ super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler);
+
+ switch (type) {
+ case 'connectionCreated':
+ this.room.on(RoomEvent.ParticipantConnected, (participant: RemoteParticipant) => {
+ console.log('participant connected', participant);
+ // TODO invoke handler
+ console.warn('PARTICIPANT CONNECTED event must be linked with connectionCreated');
+ // this.onParticipantJoined(participant);
+ });
+ break;
+ case 'connectionDestroyed':
+ this.room.on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {
+ console.log('participant disconnected', participant);
+ // TODO invoke handler
+ console.warn('PARTICIPANT DISCONNECTED event must be linked with connectionDestroyed');
+ this.onParticipantLeft(participant);
+ });
+ break;
+ case 'sessionDisconnected':
+ this.room.on(RoomEvent.Disconnected, () => {
+ console.log('disconnected');
+ // TODO invoke handler
+ console.warn('DISCONNECTED event must be linked with sessionDisconnected');
+ });
+ break;
+ case 'streamCreated':
+ this.room.on(
+ RoomEvent.TrackSubscribed,
+ (track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant) => {
+ this.temporalPublications.set(`${participant.sid}-${publication.kind}`, publication);
+ if (this.streamCreatedInterval) {
+ console.debug('streamCreatedInterval already set. Skipped');
+ return;
+ }
+ console.log('track published', publication, participant);
+ // TODO the publication doesn't have the source track value properly. Its value is 'unknown'
+ // TODO the participant shows the microphone and camera enabled to false. Maybe the unknown source is the reason
+
+ // console.log('streamManagers', this.streamManagers);
+ // const s = this.streamManagers.find((sm) => sm.id === publication.);
+
+ this.streamCreatedInterval = setTimeout(async () => {
+ console.warn('TRACK PUBLISHED event must be linked with streamCreated');
+ this.streamCreatedInterval = undefined;
+ this.onParticipantPublished(participant);
+ }, 200);
+ }
+ );
+ break;
+ case 'streamDestroyed':
+ this.room.on(
+ RoomEvent.TrackUnsubscribed,
+ (track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant) => {
+ if (this.streamDestroyedInterval) {
+ console.debug('streamDestroyedInterval already set. Skipped');
+ return;
+ }
+ console.log('track unpublished', publication, participant);
+ // TODO invoke handler
+ console.warn('TRACK UNPUBLISHED event must be linked with streamDestroyed');
+
+ this.streamDestroyedInterval = setTimeout(async () => {
+ console.warn('TRACK UNPUBLISHED event must be linked with streamDestroyed');
+ this.streamDestroyedInterval = undefined;
+ this.onParticipantUnpublished(participant);
+ }, 200);
+ }
+ );
+ break;
+
+ // case 'signal':
+ // break;
+ // case 'recordingStarted':
+ // break;
+ // case 'recordingStopped':
+ // break;
+ // case 'streamPropertyChanged':
+ // break;
+ // case 'connectionPropertyChanged':
+ // break;
+ case 'networkQualityLevelChanged':
+ this.room.on(RoomEvent.ConnectionQualityChanged, (quality: ConnectionQuality, participant: Participant) => {
+ const oldValue = this.networkQualityLevelValue;
+ this.processNetworkQualityValue(quality);
+
+ const openviduEvent: { connectionId: string; newValue: number; oldValue: number } = {
+ connectionId: participant.sid,
+ newValue: this.networkQualityLevelValue,
+ oldValue
+ };
+ this.onNetworkQualityLevelChangedChanged(openviduEvent);
+ });
+ break;
+ case 'exception':
+ console.warn('exception event not implemented in SessionAdapter');
+ break;
+ // case 'publisherStartSpeaking':
+ // this.room.on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {
+
+ // console.log('speakers', speakers);
+ // });
+ // break;
+ // case 'publisherStopSpeaking':
+
+ // break;
+ // case 'broadcastStarted':
+ // break;
+ // case 'broadcastStopped':
+ // break;
+ // case 'speechToTextMessage':
+ // break;
+ // case 'reconnecting':
+ // break;
+
+ // case 'reconnected':
+ // break;
+
+ default:
+ console.warn('on', type, 'not implemented');
+ break;
+ }
+ return this;
+
+ // throw new Error("'on' method is not ready yet.");
+ // super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler);
+
+ // if (type === 'publisherStartSpeaking') {
+ // // If there are already available remote streams with audio, enable hark 'speaking' event in all of them
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream?.hasAudio) {
+ // remoteConnection.stream.enableHarkSpeakingEvent();
+ // }
+ // });
+ // if (!!this.connection?.stream?.hasAudio) {
+ // // If connected to the Session and publishing with audio, also enable hark 'speaking' event for the Publisher
+ // this.connection.stream.enableHarkSpeakingEvent();
+ // }
+ // }
+ // if (type === 'publisherStopSpeaking') {
+ // // If there are already available remote streams with audio, enable hark 'stopped_speaking' event in all of them
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream?.hasAudio) {
+ // remoteConnection.stream.enableHarkStoppedSpeakingEvent();
+ // }
+ // });
+ // if (!!this.connection?.stream?.hasAudio) {
+ // // If connected to the Session and publishing with audio, also enable hark 'stopped_speaking' event for the Publisher
+ // this.connection.stream.enableHarkStoppedSpeakingEvent();
+ // }
+ // }
+
+ // return this;
+ }
+
+ /**
+ * See {@link EventDispatcher.once}
+ */
+ once(type: K, handler: (event: SessionEventMap[K]) => void): this {
+ throw new Error("'once' method is not ready yet.");
+ // super.onceAux(type, "Event '" + type + "' triggered once by 'Session'", handler);
+
+ // if (type === 'publisherStartSpeaking') {
+ // // If there are already available remote streams with audio, enable hark 'speaking' event (once) in all of them once
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream?.hasAudio) {
+ // remoteConnection.stream.enableOnceHarkSpeakingEvent();
+ // }
+ // });
+ // if (!!this.connection?.stream?.hasAudio) {
+ // // If connected to the Session and publishing with audio, also enable hark 'speaking' event (once) for the Publisher
+ // this.connection.stream.enableOnceHarkSpeakingEvent();
+ // }
+ // }
+ // if (type === 'publisherStopSpeaking') {
+ // // If there are already available remote streams with audio, enable hark 'stopped_speaking' event (once) in all of them once
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream?.hasAudio) {
+ // remoteConnection.stream.enableOnceHarkStoppedSpeakingEvent();
+ // }
+ // });
+ // if (!!this.connection?.stream?.hasAudio) {
+ // // If connected to the Session and publishing with audio, also enable hark 'stopped_speaking' event (once) for the Publisher
+ // this.connection.stream.enableOnceHarkStoppedSpeakingEvent();
+ // }
+ // }
+
+ // return this;
+ }
+
+ /**
+ * See {@link EventDispatcher.off}
+ */
+ off(type: K, handler?: (event: SessionEventMap[K]) => void): this {
+ switch (type) {
+ // case 'connectionCreated':
+ // break;
+ // case 'connectionDestroyed':
+ // break;
+ case 'sessionDisconnected':
+ this.room?.off(RoomEvent.Disconnected, () => {
+ console.log('disconnected OFF');
+ });
+ break;
+ case 'streamCreated':
+ this.room?.off(RoomEvent.TrackPublished, () => {
+ console.log('streamCreated OFF');
+ });
+ break;
+ case 'streamDestroyed':
+ this.room?.off(RoomEvent.TrackUnpublished, () => {
+ console.warn('streamDestroyed OFF');
+ });
+ break;
+ // case 'signal':
+ // break;
+ // case 'recordingStarted':
+ // break;
+ // case 'recordingStopped':
+ // break;
+ // case 'streamPropertyChanged':
+ // break;
+ // case 'connectionPropertyChanged':
+ // break;
+ case 'networkQualityLevelChanged':
+ this.room?.off(RoomEvent.ConnectionQualityChanged, () => {
+ console.warn('networkQualityLevelChanged OFF');
+ });
+ break;
+ // case 'exception':
+ // break;
+ // case 'publisherStartSpeaking':
+ // break;
+ // case 'publisherStopSpeaking':
+ // break;
+ // case 'broadcastStarted':
+ // break;
+ // case 'broadcastStopped':
+ // break;
+ // case 'speechToTextMessage':
+ // break;
+ // case 'reconnecting':
+ // break;
+
+ // case 'reconnected':
+ // break;
+
+ default:
+ console.warn('OFF', type, 'not implemented');
+ break;
+ }
+ return this;
+
+ // throw new Error("'off' method is not ready yet.");
+ // super.offAux(type, handler);
+
+ // if (type === 'publisherStartSpeaking') {
+ // // Check if Session object still has some listener for the event
+ // if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false)) {
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream?.streamManager) {
+ // // Check if Subscriber object still has some listener for the event
+ // if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false, remoteConnection.stream.streamManager)) {
+ // remoteConnection.stream.disableHarkSpeakingEvent(false);
+ // }
+ // }
+ // });
+ // if (!!this.connection?.stream?.streamManager) {
+ // // Check if Publisher object still has some listener for the event
+ // if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false, this.connection.stream.streamManager)) {
+ // this.connection.stream.disableHarkSpeakingEvent(false);
+ // }
+ // }
+ // }
+ // }
+ // if (type === 'publisherStopSpeaking') {
+ // // Check if Session object still has some listener for the event
+ // if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false)) {
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream?.streamManager) {
+ // // Check if Subscriber object still has some listener for the event
+ // if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false, remoteConnection.stream.streamManager)) {
+ // remoteConnection.stream.disableHarkStoppedSpeakingEvent(false);
+ // }
+ // }
+ // });
+ // if (!!this.connection?.stream?.streamManager) {
+ // // Check if Publisher object still has some listener for the event
+ // if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false, this.connection.stream.streamManager)) {
+ // this.connection.stream.disableHarkStoppedSpeakingEvent(false);
+ // }
+ // }
+ // }
+ // }
+ // return this;
+ }
+
+ /* Hidden methods */
+
+ /**
+ * @hidden
+ */
+ onParticipantJoined(event: RemoteConnectionOptions): void {
+ throw new Error("'onParticipantJoined' method is not ready yet.");
+ // Connection shouldn't exist
+ this.getConnection(event.id, '')
+ .then((connection) => {
+ logger.warn('Connection ' + connection.connectionId + ' already exists in connections list');
+ })
+ .catch((openViduError) => {
+ const connection = new Connection(this, event);
+ this.remoteConnections.set(event.id, connection);
+ this.ee.emitEvent('connectionCreated', [new ConnectionEvent(false, this, 'connectionCreated', connection, '')]);
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ onParticipantLeft(participant: RemoteParticipant /*event: { connectionId: string; reason: ConnectionEventReason }*/): void {
+ const connectionId = participant.sid; //??
+ // throw new Error("'onParticipantLeft' method is not ready yet.");
+ this.getRemoteConnection(connectionId, 'onParticipantLeft')
+ .then((connection) => {
+ if (!!connection.stream) {
+ const stream = connection.stream;
+
+ const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, 'disconnect');
+ this.ee.emitEvent('streamDestroyed', [streamEvent]);
+ streamEvent.callDefaultBehavior();
+
+ this.remoteStreamsCreated.delete(stream.streamId);
+ }
+ connection.dispose();
+ this.remoteConnections.delete(connection.connectionId);
+ this.ee.emitEvent('connectionDestroyed', [
+ new ConnectionEvent(false, this, 'connectionDestroyed', connection, 'disconnect')
+ ]);
+ })
+ .catch((openViduError) => {
+ logger.error(openViduError);
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ async onParticipantPublished(participant: RemoteParticipant): Promise {
+ const videoPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-video`);
+ const audioPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-audio`);
+
+ let createdAt = new Date().getTime();
+ if (participant?.['participantInfo']) {
+ createdAt = Number(BigInt(participant['participantInfo'].joinedAt)) * 1000; // miliseconds
+ }
+
+ let videoDimensions = '';
+ let typeOfVideo = TypeOfVideo.CAMERA;
+ if (videoPublication?.videoTrack && videoPublication.videoTrack.mediaStreamTrack) {
+ const { width, height } = videoPublication.videoTrack.mediaStreamTrack.getSettings();
+ videoDimensions = JSON.stringify({ height, width });
+ //TODO videoPublication.source is always 'unknown'
+ // typeOfVideo = videoPublication.source === Track.Source.Camera ? TypeOfVideo.CAMERA : TypeOfVideo.SCREEN;
+ }
+
+ const streams: StreamOptionsServer[] = [
+ {
+ id: participant.sid,
+ createdAt: createdAt,
+ hasAudio: audioPublication !== undefined,
+ hasVideo: videoPublication !== undefined,
+ audioActive: audioPublication ? audioPublication.isEnabled : false,
+ videoActive: videoPublication ? videoPublication.isEnabled : false,
+ typeOfVideo,
+ frameRate: 30,
+ videoDimensions
+ // filter: ,
+ }
+ ];
+
+ const opt: RemoteConnectionOptions = {
+ createdAt,
+ metadata: participant?.metadata || '',
+ streams,
+ id: participant.sid
+ };
+
+ // Get the existing Connection created on 'onParticipantJoined' for
+ // existing participants or create a new one for new participants
+ let connection!: Connection;
+ try {
+ // Update existing Connection
+
+ debugger;
+ connection = await this.getRemoteConnection(opt.id, 'onParticipantPublished');
+ connection.stream;
+ opt.metadata = connection.data;
+ connection.remoteOptions = opt;
+ connection.initRemoteStreams(opt.streams);
+ // afterConnectionFound(connection);
+ } catch (error) {
+ // Create new Connection
+ connection = new Connection(this, opt);
+ // afterConnectionFound(connection);
+ } finally {
+ // if (!connection) {
+ // connection = new Connection(this, opt);
+ // }
+
+ // const mediaStream = new MediaStream();
+ // if (videoPublication?.videoTrack?.mediaStreamTrack) {
+ // mediaStream.addTrack(videoPublication.videoTrack.mediaStreamTrack);
+ // }
+ // if (audioPublication?.audioTrack?.mediaStreamTrack) {
+ // mediaStream.addTrack(audioPublication.audioTrack.mediaStreamTrack);
+ // }
+
+ // connection.stream?.setMediaStream(mediaStream);
+ if (connection.stream) {
+ if (videoPublication?.videoTrack) {
+ connection.stream.temporalVideoTrack = videoPublication.videoTrack as RemoteVideoTrack;
+ }
+
+ if (audioPublication?.audioTrack) {
+ connection.stream.temporalAudioTrack = audioPublication.audioTrack as RemoteAudioTrack;
+ }
+ }
+
+ this.remoteConnections.set(connection.connectionId, connection);
+
+ if (connection.stream?.streamId) {
+ if (!this.remoteStreamsCreated.get(connection.stream.streamId)) {
+ // Avoid race condition between stream.subscribe() in "onParticipantPublished" and in "joinRoom" rpc callback
+ // This condition is false if openvidu-server sends "participantPublished" event to a subscriber participant that has
+ // already subscribed to certain stream in the callback of "joinRoom" method
+
+ this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', connection.stream, '')]);
+ }
+
+ this.remoteStreamsCreated.set(connection.stream.streamId, true);
+ }
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onParticipantUnpublished(participant: RemoteParticipant /*, event: { connectionId: string; reason: StreamEventReason }*/): void {
+ // const videoPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-video`);
+ // const audioPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-audio`);
+ this.temporalPublications.delete(`${participant.sid}-video`);
+ this.temporalPublications.delete(`${participant.sid}-audio`);
+ const connectionId = participant.sid; //??
+
+ // throw new Error("'onParticipantUnpublished' method is not ready yet.");
+ if (connectionId === this.connection.connectionId) {
+ // Your stream has been forcedly unpublished from the session
+ this.stopPublisherStream('unpublish');
+ } else {
+ this.getRemoteConnection(connectionId, 'onParticipantUnpublished')
+
+ .then((connection) => {
+ const streamEvent = new StreamEvent(true, this, 'streamDestroyed', connection.stream!, 'unpublish');
+ this.ee.emitEvent('streamDestroyed', [streamEvent]);
+ streamEvent.callDefaultBehavior();
+
+ // Deleting the remote stream if it exists
+ if (connection.stream != null) {
+ const streamId: string = connection.stream!.streamId;
+ this.remoteStreamsCreated.delete(streamId);
+ connection.removeStream();
+ }
+ })
+ .catch((openViduError) => {
+ logger.error(openViduError);
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onParticipantEvicted(event: { connectionId: string; reason: ConnectionEventReason }): void {
+ throw new Error("'onParticipantEvicted' method is not ready yet.");
+ if (event.connectionId === this.connection.connectionId) {
+ // You have been evicted from the session
+ if (!!this.sessionId && !this.connection.disposed) {
+ this.leave(true, event.reason);
+ }
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onNewMessage(event: { type?: string; data?: string; from?: string }): void {
+ throw new Error("'onNewMessage' method is not ready yet.");
+ // logger.info('New signal: ' + JSON.stringify(event));
+
+ // const strippedType = !!event.type ? event.type.replace(/^(signal:)/, '') : undefined;
+
+ // if (!!event.from) {
+ // // Signal sent by other client
+ // this.getConnection(
+ // event.from,
+ // "Connection '" +
+ // event.from +
+ // "' unknown when 'onNewMessage'. Existing remote connections: " +
+ // JSON.stringify(this.remoteConnections.keys()) +
+ // '. Existing local connection: ' +
+ // this.connection.connectionId
+ // )
+
+ // .then((connection) => {
+ // this.ee.emitEvent('signal', [new SignalEvent(this, strippedType, event.data, connection)]);
+ // if (!!event.type && event.type !== 'signal') {
+ // this.ee.emitEvent(event.type, [new SignalEvent(this, strippedType, event.data, connection)]);
+ // }
+ // })
+ // .catch((openViduError) => {
+ // logger.error(openViduError);
+ // });
+ // } else {
+ // // Signal sent by server
+ // this.ee.emitEvent('signal', [new SignalEvent(this, strippedType, event.data, undefined)]);
+ // if (!!event.type && event.type !== 'signal') {
+ // this.ee.emitEvent(event.type, [new SignalEvent(this, strippedType, event.data, undefined)]);
+ // }
+ // }
+ }
+
+ /**
+ * @hidden
+ */
+ onStreamPropertyChanged(event: {
+ connectionId: string;
+ streamId: string;
+ property: ChangedPropertyType;
+ newValue: any;
+ reason: StreamPropertyChangedEventReason;
+ }): void {
+ throw new Error("'onStreamPropertyChanged' method is not ready yet.");
+ const callback = (connection: Connection) => {
+ if (!!connection.stream && connection.stream.streamId === event.streamId) {
+ const stream = connection.stream;
+ let oldValue;
+ switch (event.property) {
+ case 'audioActive':
+ oldValue = stream.audioActive;
+ event.newValue = event.newValue === 'true';
+ stream.audioActive = event.newValue;
+ break;
+ case 'videoActive':
+ oldValue = stream.videoActive;
+ event.newValue = event.newValue === 'true';
+ stream.videoActive = event.newValue;
+ const videoTrack = stream?.getMediaStream()?.getVideoTracks()?.[0];
+ if (videoTrack && !videoTrack.enabled && stream.videoActive) {
+ videoTrack.enabled = true;
+ }
+ break;
+ case 'videoTrack':
+ event.newValue = JSON.parse(event.newValue);
+ break;
+ case 'audioTrack':
+ event.newValue = JSON.parse(event.newValue);
+ break;
+ case 'videoDimensions':
+ oldValue = stream.videoDimensions;
+ event.newValue = JSON.parse(JSON.parse(event.newValue));
+ stream.videoDimensions = event.newValue;
+ break;
+ case 'filter':
+ oldValue = stream.filter;
+ event.newValue = Object.keys(event.newValue).length > 0 ? event.newValue : undefined;
+ if (event.newValue !== undefined) {
+ stream.filter = new Filter(event.newValue.type, event.newValue.options);
+ stream.filter.stream = stream;
+ if (event.newValue.lastExecMethod) {
+ stream.filter.lastExecMethod = event.newValue.lastExecMethod;
+ }
+ } else {
+ delete stream.filter;
+ }
+ event.newValue = stream.filter;
+ break;
+ }
+ this.ee.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this, stream, event.property, event.newValue, oldValue, event.reason)
+ ]);
+ if (!!stream.streamManager) {
+ stream.streamManager.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(stream.streamManager, stream, event.property, event.newValue, oldValue, event.reason)
+ ]);
+ }
+ } else {
+ logger.error(
+ "No stream with streamId '" +
+ event.streamId +
+ "' found for connection '" +
+ event.connectionId +
+ "' on 'streamPropertyChanged' event"
+ );
+ }
+ };
+
+ if (event.connectionId === this.connection.connectionId) {
+ // Your stream has been forcedly changed (filter feature)
+ callback(this.connection);
+ } else {
+ this.getRemoteConnection(event.connectionId, 'onStreamPropertyChanged')
+ .then((connection) => {
+ callback(connection);
+ })
+ .catch((openViduError) => {
+ logger.error(openViduError);
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onConnectionPropertyChanged(event: { property: string; newValue: any }): void {
+ throw new Error("'onConnectionPropertyChanged' method is not ready yet.");
+ let oldValue;
+ switch (event.property) {
+ case 'role':
+ oldValue = this.connection.role.slice();
+ this.connection.role = event.newValue;
+ this.connection.localOptions!.role = event.newValue;
+ break;
+ case 'record':
+ oldValue = this.connection.record;
+ event.newValue = event.newValue === 'true';
+ this.connection.record = event.newValue;
+ this.connection.localOptions!.record = event.newValue;
+ break;
+ }
+ this.ee.emitEvent('connectionPropertyChanged', [
+ new ConnectionPropertyChangedEvent(this, this.connection, event.property, event.newValue, oldValue)
+ ]);
+ }
+
+ /**
+ * @hidden
+ */
+ onNetworkQualityLevelChangedChanged(event: { connectionId: string; newValue: number; oldValue: number }): void {
+ if (event.connectionId === this.connection.connectionId) {
+ this.ee.emitEvent('networkQualityLevelChanged', [
+ new NetworkQualityLevelChangedEvent(this, event.newValue, event.oldValue, this.connection)
+ ]);
+ } else {
+ this.getConnection(event.connectionId, 'Connection not found for connectionId ' + event.connectionId)
+ .then((connection: Connection) => {
+ this.ee.emitEvent('networkQualityLevelChanged', [
+ new NetworkQualityLevelChangedEvent(this, event.newValue, event.oldValue, connection)
+ ]);
+ })
+ .catch((openViduError) => {
+ logger.error(openViduError);
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ recvIceCandidate(event: {
+ senderConnectionId: string;
+ endpointName: string;
+ sdpMLineIndex: number;
+ sdpMid: string;
+ candidate: string;
+ }): void {
+ throw new Error("'recvIceCandidate' method is not ready yet.");
+ // The event contains fields that can be used to obtain a proper candidate,
+ // using the RTCIceCandidate constructor:
+ // https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-constructor
+ // const candidateInit: RTCIceCandidateInit = {
+ // candidate: event.candidate,
+ // sdpMLineIndex: event.sdpMLineIndex,
+ // sdpMid: event.sdpMid
+ // };
+ // const iceCandidate = new RTCIceCandidate(candidateInit);
+
+ // this.getConnection(
+ // event.senderConnectionId,
+ // 'Connection not found for connectionId ' +
+ // event.senderConnectionId +
+ // ' owning endpoint ' +
+ // event.endpointName +
+ // '. Ice candidate will be ignored: ' +
+ // iceCandidate
+ // )
+ // .then((connection) => {
+ // const stream: Stream = connection.stream!;
+ // stream
+ // .getWebRtcPeer()
+ // .addIceCandidate(iceCandidate)
+ // .catch((error) => {
+ // logger.error(
+ // 'Error adding candidate for ' + stream!.streamId + ' stream of endpoint ' + event.endpointName + ': ' + error
+ // );
+ // });
+ // })
+ // .catch((openViduError) => {
+ // logger.error(openViduError);
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ onSessionClosed(msg): void {
+ throw new Error("'onSessionClosed' method is not ready yet.");
+ logger.info('Session closed: ' + JSON.stringify(msg));
+ const s = msg.sessionId;
+ if (s !== undefined) {
+ this.ee.emitEvent('session-closed', [
+ {
+ session: s
+ }
+ ]);
+ } else {
+ logger.warn('Session undefined on session closed', msg);
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onLostConnection(reason: ConnectionEventReason): void {
+ throw new Error("'onLostConnection' method is not ready yet.");
+ logger.warn('Lost connection in Session ' + this.sessionId);
+ if (!!this.sessionId && !!this.connection && !this.connection.disposed) {
+ this.leave(true, reason);
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onRecoveredConnection(): void {
+ throw new Error("'onRecoveredConnection' method is not ready yet.");
+ logger.info('Recovered connection in Session ' + this.sessionId);
+ this.reconnectBrokenStreams();
+ this.ee.emitEvent('reconnected', []);
+ }
+
+ /**
+ * @hidden
+ */
+ onMediaError(event: { error: string }): void {
+ throw new Error("'onMediaError' method is not ready yet.");
+ logger.error('Media error: ' + JSON.stringify(event));
+ const err = event.error;
+ if (err) {
+ this.ee.emitEvent('error-media', [{ error: err }]);
+ } else {
+ logger.warn('Received undefined media error:', event);
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ onRecordingStarted(event: { id: string; name: string }): void {
+ throw new Error("'onRecordingStarted' method is not ready yet.");
+ this.ee.emitEvent('recordingStarted', [new RecordingEvent(this, 'recordingStarted', event.id, event.name)]);
+ }
+
+ /**
+ * @hidden
+ */
+ onRecordingStopped(event: { id: string; name: string; reason: RecordingEventReason }): void {
+ throw new Error("'onRecordingStopped' method is not ready yet.");
+ this.ee.emitEvent('recordingStopped', [new RecordingEvent(this, 'recordingStopped', event.id, event.name, event.reason)]);
+ }
+
+ /**
+ * @hidden
+ */
+ onBroadcastStarted(): void {
+ throw new Error("'onBroadcastStarted' method is not ready yet.");
+ this.ee.emitEvent('broadcastStarted', []);
+ }
+
+ /**
+ * @hidden
+ */
+ onBroadcastStopped(): void {
+ throw new Error("'onBroadcastStopped' method is not ready yet.");
+ this.ee.emitEvent('broadcastStopped', []);
+ }
+
+ /**
+ * @hidden
+ */
+ onFilterEventDispatched(event: { connectionId: string; streamId: string; filterType: string; eventType: string; data: string }): void {
+ throw new Error("'onFilterEventDispatched' method is not ready yet.");
+ const connectionId: string = event.connectionId;
+ this.getConnection(connectionId, 'No connection found for connectionId ' + connectionId).then((connection) => {
+ logger.info(`Filter event of type "${event.eventType}" dispatched`);
+ const stream: Stream = connection.stream!;
+ if (!stream || !stream.filter) {
+ return logger.error(
+ `Filter event of type "${event.eventType}" dispatched for stream ${stream.streamId} but there is no ${
+ !stream ? 'stream' : 'filter'
+ } defined`
+ );
+ }
+ const eventHandler = stream.filter.handlers.get(event.eventType);
+ if (!eventHandler || typeof eventHandler !== 'function') {
+ const actualHandlers: string[] = Array.from(stream.filter.handlers.keys());
+ return logger.error(
+ `Filter event of type "${event.eventType}" not handled or not a function! Active filter events: ${actualHandlers.join(
+ ','
+ )}`
+ );
+ } else {
+ eventHandler.call(this, new FilterEvent(stream.filter, event.eventType, event.data));
+ }
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ onForciblyReconnectSubscriber(event: { connectionId: string; streamId: string; sdpOffer: string }): Promise {
+ throw new Error("'onForciblyReconnectSubscriber' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ this.getRemoteConnection(event.connectionId, 'onForciblyReconnectSubscriber')
+ .then((connection) => {
+ if (!!connection.stream && connection.stream.streamId === event.streamId) {
+ const stream = connection.stream;
+
+ if (stream.setupReconnectionEventEmitter(resolve, reject)) {
+ // Ongoing reconnection
+ // Wait for the event emitter to be free (with success or error) and call the method again
+ if (stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'] != null) {
+ // Two or more onForciblyReconnectSubscriber events were received while a reconnection process
+ // of the subscriber was already taking place. Always use the last one to retry the re-subscription
+ // process, as that SDP offer will be the only one available at the server side. Ignore previous ones
+ stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'] = event;
+ return reject('Ongoing forced subscriber reconnection');
+ } else {
+ // One onForciblyReconnectSubscriber even has been received while a reconnection process
+ // of the subscriber was already taking place. Set up a listener to wait for it to retry the
+ // forced reconnection process
+ stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'] = event;
+ const callback = () => {
+ const eventAux = stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'];
+ delete stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'];
+ this.onForciblyReconnectSubscriber(eventAux);
+ };
+ stream.reconnectionEventEmitter!.once('success', () => {
+ callback();
+ });
+ stream.reconnectionEventEmitter!.once('error', () => {
+ callback();
+ });
+ }
+ return;
+ }
+
+ stream
+ .completeWebRtcPeerReceive(true, true, event.sdpOffer)
+ .then(() => stream.finalResolveForSubscription(true, resolve))
+ .catch((error) =>
+ stream.finalRejectForSubscription(
+ true,
+ `Error while forcibly reconnecting remote stream ${event.streamId}: ${error.toString()}`,
+ reject
+ )
+ );
+ } else {
+ const errMsg =
+ "No stream with streamId '" +
+ event.streamId +
+ "' found for connection '" +
+ event.connectionId +
+ "' on 'streamPropertyChanged' event";
+ logger.error(errMsg);
+ return reject(errMsg);
+ }
+ })
+ .catch((openViduError) => {
+ logger.error(openViduError);
+ return reject(openViduError);
+ });
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ reconnectBrokenStreams(): void {
+ throw new Error("'reconnectBrokenStreams' method is not ready yet.");
+ // logger.info('Re-establishing media connections...');
+ // let someReconnection = false;
+ // // Re-establish Publisher stream
+ // if (!!this.connection.stream && this.connection.stream.streamIceConnectionStateBroken()) {
+ // logger.warn('Re-establishing Publisher ' + this.connection.stream.streamId);
+ // this.connection.stream.initWebRtcPeerSend(true);
+ // someReconnection = true;
+ // }
+ // // Re-establish Subscriber streams
+ // this.remoteConnections.forEach((remoteConnection) => {
+ // if (!!remoteConnection.stream && remoteConnection.stream.streamIceConnectionStateBroken()) {
+ // logger.warn('Re-establishing Subscriber ' + remoteConnection.stream.streamId);
+ // remoteConnection.stream.initWebRtcPeerReceive(true);
+ // someReconnection = true;
+ // }
+ // });
+ // if (!someReconnection) {
+ // logger.info('There were no media streams in need of a reconnection');
+ // }
+ }
+
+ /**
+ * @hidden
+ */
+ async onSpeechToTextMessage(event: {
+ timestamp?: Date;
+ streamId: string;
+ connectionId: string;
+ sessionId: string;
+ text: string;
+ reason: string;
+ raw: string;
+ lang: string;
+ }): Promise {
+ throw new Error("'onSpeechToTextMessage' method is not ready yet.");
+ const connection = await this.getConnection(event.connectionId, 'No connection found for connectionId ' + event.connectionId);
+ const ev = new SpeechToTextEvent(this, connection, event.text, event.reason.toLowerCase(), event.raw, event.lang);
+ this.ee.emitEvent('speechToTextMessage', [ev]);
+ }
+
+ /**
+ * @hidden
+ */
+ async onSpeechToTextDisconnected(event: { message: string }): Promise {
+ throw new Error("'onSpeechToTextDisconnected' method is not ready yet.");
+ this.emitEvent('exception', [new ExceptionEvent(this, ExceptionEventName.SPEECH_TO_TEXT_DISCONNECTED, this, event.message)]);
+ }
+
+ /**
+ * @hidden
+ */
+ emitEvent(type: string, eventArray: any[]): void {
+ // throw new Error("'emitEvent' method is not ready yet.");
+ this.ee.emitEvent(type, eventArray);
+ }
+
+ /**
+ * @hidden
+ */
+ leave(forced: boolean, reason: ConnectionEventReason): void {
+ // throw new Error("'leave' method is not ready yet.");
+ forced = !!forced;
+ logger.info('Leaving Session (forced=' + forced + ')');
+ // this.stopVideoDataIntervals();
+
+ if (!!this.connection) {
+ // if (!this.connection.disposed && !forced) {
+ // this.openvidu.sendRequest('leaveRoom', (error, response) => {
+ // if (error) {
+ // logger.error(`leaveRoom error: ${JSON.stringify(error)}`);
+ // }
+ // this.openvidu.closeWs();
+ // });
+ // }
+ // else {
+ // this.openvidu.closeWs();
+ // }
+
+ // this.stopPublisherStream(reason);
+ if (this.room) this.room.disconnect();
+
+ // if (!this.connection.disposed) {
+ // Make Session object dispatch 'sessionDisconnected' event (if it is not already disposed)
+ const sessionDisconnectEvent = new SessionDisconnectedEvent(this, reason);
+ this.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]);
+ sessionDisconnectEvent.callDefaultBehavior();
+ // }
+ } else {
+ logger.warn('You were not connected to the session ' + this.sessionId);
+ }
+ logger.flush();
+ }
+
+ /**
+ * @hidden
+ */
+ // initializeParams(token: string) {
+ // throw new Error("'initializeParams' method is not ready yet.");
+ // const joinParams = {
+ // token: !!token ? token : '',
+ // session: this.sessionId,
+ // platform: !!platform.getDescription() ? platform.getDescription() : 'unknown',
+ // sdkVersion: this.openvidu.libraryVersion,
+ // metadata: !!this.options.metadata ? this.options.metadata : '',
+ // secret: this.openvidu.getSecret(),
+ // recorder: this.openvidu.getRecorder(),
+ // stt: this.openvidu.getStt()
+ // };
+ // return joinParams;
+ // }
+
+ /**
+ * @hidden
+ */
+ sessionConnected() {
+ return this.room.state === ConnectionState.Connected;
+ // throw new Error("'sessionConnected' method is not ready yet.");
+ // return this.connection != null;
+ }
+
+ /**
+ * @hidden
+ */
+ notConnectedError(): OpenViduError {
+ return new OpenViduError(
+ OpenViduErrorName.OPENVIDU_NOT_CONNECTED,
+ "There is no connection to the session. Method 'Session.connect' must be successfully completed first"
+ );
+ }
+
+ /**
+ * @hidden
+ */
+ anySpeechEventListenerEnabled(event: string, onlyOnce: boolean, streamManager?: StreamManager): boolean {
+ throw new Error("'anySpeechEventListenerEnabled' method is not ready yet.");
+ // let handlersInSession = this.ee.getListeners(event);
+ // if (onlyOnce) {
+ // handlersInSession = handlersInSession.filter((h) => (h as any).once);
+ // }
+ // let listenersInSession = handlersInSession.length;
+ // if (listenersInSession > 0) return true;
+ // let listenersInStreamManager = 0;
+ // if (!!streamManager) {
+ // let handlersInStreamManager = streamManager.ee.getListeners(event);
+ // if (onlyOnce) {
+ // handlersInStreamManager = handlersInStreamManager.filter((h) => (h as any).once);
+ // }
+ // listenersInStreamManager = handlersInStreamManager.length;
+ // }
+ // return listenersInStreamManager > 0;
+ }
+
+ /**
+ * @hidden
+ */
+ getTokenParams(token: string) {
+ throw new Error("'getTokenParams' method is not ready yet.");
+ // const match = token.match(/^(wss?)\:\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
+ // if (!!match) {
+ // const url = {
+ // protocol: match[1],
+ // host: match[2],
+ // hostname: match[3],
+ // port: match[4],
+ // pathname: match[5],
+ // search: match[6],
+ // hash: match[7]
+ // };
+
+ // const params = token.split('?');
+ // const queryParams = decodeURI(params[1])
+ // .split('&')
+ // .map((param) => param.split('='))
+ // .reduce((values, [key, value]) => {
+ // values[key] = value;
+ // return values;
+ // }, {});
+
+ // return {
+ // sessionId: queryParams['sessionId'],
+ // secret: queryParams['secret'],
+ // recorder: queryParams['recorder'],
+ // stt: queryParams['stt'],
+ // webrtcStatsInterval: queryParams['webrtcStatsInterval'],
+ // sendBrowserLogs: queryParams['sendBrowserLogs'],
+ // edition: queryParams['edition'],
+ // wsUri: url.protocol + '://' + url.host + '/openvidu',
+ // httpUri: 'https://' + url.host
+ // };
+ // } else {
+ // throw new Error(`Token not valid: "${token}"`);
+ // }
+ }
+
+ /* Private methods */
+
+ // private connectAux(token: string): Promise {
+ // // throw new Error("'connectAux' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // // this.openvidu.startWs((error) => {
+ // // if (!!error) {
+ // // return reject(error);
+ // // } else {
+ // // const joinParams = this.initializeParams(token);
+
+ // // this.openvidu.sendRequest('joinRoom', joinParams, (error, response: LocalConnectionOptions) => {
+ // // if (!!error) {
+ // // return reject(error);
+ // // } else {
+ // // // Process join room response
+ // this.processJoinRoomResponse(response, token);
+
+ // // Initialize local Connection object with values returned by openvidu-server
+ // this.connection = new Connection(this, response);
+
+ // // Initialize remote Connections with value returned by openvidu-server
+ // const events = {
+ // connections: new Array(),
+ // streams: new Array()
+ // };
+ // const existingParticipants: RemoteConnectionOptions[] = response.value;
+ // existingParticipants.forEach((remoteConnectionOptions: RemoteConnectionOptions) => {
+ // const connection = new Connection(this, remoteConnectionOptions);
+ // this.remoteConnections.set(connection.connectionId, connection);
+ // events.connections.push(connection);
+ // if (!!connection.stream) {
+ // this.remoteStreamsCreated.set(connection.stream.streamId, true);
+ // events.streams.push(connection.stream);
+ // }
+ // });
+
+ // // Own 'connectionCreated' event
+ // this.ee.emitEvent('connectionCreated', [
+ // new ConnectionEvent(false, this, 'connectionCreated', this.connection, '')
+ // ]);
+
+ // // One 'connectionCreated' event for each existing connection in the session
+ // events.connections.forEach((connection) => {
+ // this.ee.emitEvent('connectionCreated', [
+ // new ConnectionEvent(false, this, 'connectionCreated', connection, '')
+ // ]);
+ // });
+
+ // // // One 'streamCreated' event for each active stream in the session
+ // // events.streams.forEach((stream) => {
+ // // this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', stream, '')]);
+ // // });
+
+ // // if (!!response.recordingId && !!response.recordingName) {
+ // // this.ee.emitEvent('recordingStarted', [
+ // // new RecordingEvent(this, 'recordingStarted', response.recordingId, response.recordingName)
+ // // ]);
+ // // }
+
+ // return resolve();
+ // // }
+ // // });
+ // // }
+ // // });
+ // });
+ // }
+
+ private stopPublisherStream(reason: StreamEventReason) {
+ console.warn("'stopPublisherStream' method is not ready yet.");
+ if (!!this.connection.stream) {
+ // Dispose Publisher's local stream
+ // this.connection.stream.disposeWebRtcPeer();
+ if (this.connection.stream.isLocalStreamPublished) {
+ // Make Publisher object dispatch 'streamDestroyed' event if the Stream was published
+ this.connection.stream.ee.emitEvent('local-stream-destroyed', [reason]);
+ }
+ }
+ }
+
+ private stringClientMetadata(metadata: any): string {
+ throw new Error("'stringClientMetadata' method is not ready yet.");
+ if (typeof metadata !== 'string') {
+ return JSON.stringify(metadata);
+ } else {
+ return metadata;
+ }
+ }
+
+ protected getConnection(connectionId: string, errorMessage: string): Promise {
+ // throw new Error("'getConnection' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ const connection = this.remoteConnections.get(connectionId);
+ if (!!connection) {
+ // Resolve remote connection
+ return resolve(connection);
+ } else {
+ if (this.connection.connectionId === connectionId) {
+ // Resolve local connection
+ return resolve(this.connection);
+ } else {
+ // Connection not found. Reject with OpenViduError
+ return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
+ }
+ }
+ });
+ }
+
+ private getRemoteConnection(connectionId: string, operation: string): Promise {
+ // throw new Error("'getRemoteConnection' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ const remoteConnectionKeys = Array.from(this.remoteConnections.keys());
+ const matchingKey = remoteConnectionKeys.find((key) => key.includes(connectionId));
+
+ if (matchingKey) {
+ const connection = this.remoteConnections.get(matchingKey);
+ // Resolve remote connection
+ if (!!connection) {
+ return resolve(connection);
+ }
+ return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Connection not found'));
+ } else {
+ // Remote connection not found. Reject with OpenViduError
+ const errorMessage = `
+ Remote connection ${connectionId} is unknown when '${operation}'.
+ Existing remote connections: ${JSON.stringify(this.remoteConnections.keys())}
+ `;
+ return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
+ }
+ });
+ }
+
+ private processToken(token: string): void {
+ throw new Error("'processToken' method is not ready yet.");
+ // const tokenParams = this.getTokenParams(token);
+ // this.sessionId = tokenParams.sessionId;
+
+ // if (!!tokenParams.secret) {
+ // this.openvidu.secret = tokenParams.secret;
+ // }
+ // if (!!tokenParams.recorder) {
+ // this.openvidu.recorder = true;
+ // }
+ // if (!!tokenParams.stt) {
+ // this.openvidu.stt = true;
+ // }
+ // if (!!tokenParams.webrtcStatsInterval) {
+ // this.openvidu.webrtcStatsInterval = tokenParams.webrtcStatsInterval;
+ // }
+ // if (!!tokenParams.sendBrowserLogs) {
+ // this.openvidu.sendBrowserLogs = tokenParams.sendBrowserLogs;
+ // }
+ // this.openvidu.isAtLeastPro = tokenParams.edition === 'pro' || tokenParams.edition === 'enterprise';
+ // this.openvidu.isEnterprise = tokenParams.edition === 'enterprise';
+
+ // this.openvidu.wsUri = tokenParams.wsUri;
+ // this.openvidu.httpUri = tokenParams.httpUri;
+ }
+
+ private processJoinRoomResponse(opts: LocalConnectionOptions, token: string) {
+ // throw new Error("'processJoinRoomResponse' method is not ready yet.");
+ // this.sessionId = opts.session;
+ if (opts.customIceServers != null && opts.customIceServers.length > 0) {
+ this.openvidu.iceServers = [];
+ for (const iceServer of opts.customIceServers) {
+ let rtcIceServer: RTCIceServer = {
+ urls: [iceServer.url]
+ };
+ logger.log('STUN/TURN server IP: ' + iceServer.url);
+ if (iceServer.username != null && iceServer.credential != null) {
+ rtcIceServer.username = iceServer.username;
+ rtcIceServer.credential = iceServer.credential;
+ logger.log('TURN credentials [' + iceServer.username + ':' + iceServer.credential + ']');
+ }
+ this.openvidu.iceServers.push(rtcIceServer);
+ }
+ }
+ this.openvidu.role = opts.role;
+ this.openvidu.finalUserId = opts.finalUserId;
+ this.openvidu.mediaServer = opts.mediaServer;
+ this.openvidu.videoSimulcast = opts.videoSimulcast;
+ this.capabilities = {
+ subscribe: true,
+ publish: this.openvidu.role !== 'SUBSCRIBER',
+ forceUnpublish: this.openvidu.role === 'MODERATOR',
+ forceDisconnect: this.openvidu.role === 'MODERATOR'
+ };
+ logger.info('openvidu-server version: ' + opts.version);
+ if (opts.life != null) {
+ this.openvidu.life = opts.life;
+ }
+ const minorDifference: number = semverMinor(opts.version) - semverMinor(this.openvidu.libraryVersion);
+ if (semverMajor(opts.version) !== semverMajor(this.openvidu.libraryVersion) || !(minorDifference == 0 || minorDifference == 1)) {
+ logger.error(
+ `openvidu-browser (${this.openvidu.libraryVersion}) and openvidu-server (${opts.version}) versions are incompatible. ` +
+ 'Errors are likely to occur. openvidu-browser SDK is only compatible with the same version or the immediately following minor version of an OpenVidu deployment'
+ );
+ } else if (minorDifference == 1) {
+ logger.warn(
+ `openvidu-browser version ${this.openvidu.libraryVersion} does not match openvidu-server version ${opts.version}. ` +
+ `These versions are still compatible with each other, but openvidu-browser version must be updated as soon as possible to ${semverMajor(
+ opts.version
+ )}.${semverMinor(opts.version)}.x. ` +
+ `This client using openvidu-browser ${this.openvidu.libraryVersion} will become incompatible with the next release of openvidu-server`
+ );
+ }
+
+ // Configure JSNLogs
+ // OpenViduLogger.configureJSNLog(this.openvidu, token);
+
+ // Store token
+ this.token = token;
+ }
+
+ private getLivekitUrlFromMetadata(token: string): string {
+ if (!token) throw new Error('Trying to get room metadata from an empty token');
+ try {
+ const base64Url = token.split('.')[1];
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+ const jsonPayload = decodeURIComponent(
+ window
+ .atob(base64)
+ .split('')
+ .map((c) => {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ })
+ .join('')
+ );
+
+ const payload = JSON.parse(jsonPayload);
+ if (!payload?.metadata) throw new Error('Token does not contain metadata');
+ const metadata = JSON.parse(payload.metadata);
+ return metadata.livekitUrl;
+ } catch (error) {
+ throw new Error('Error decoding and parsing token: ' + error);
+ }
+ }
+
+ private processNetworkQualityValue(quality: ConnectionQuality): void {
+ switch (quality) {
+ case ConnectionQuality.Excellent:
+ this.networkQualityLevelValue = 5;
+ break;
+
+ case ConnectionQuality.Good:
+ this.networkQualityLevelValue = 4;
+ break;
+
+ case ConnectionQuality.Poor:
+ this.networkQualityLevelValue = 2;
+ break;
+
+ case ConnectionQuality.Unknown:
+ this.networkQualityLevelValue = 1;
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/openvidu-browser/old/OldStream.ts b/openvidu-browser/old/OldStream.ts
new file mode 100644
index 00000000000..d5f842c74e3
--- /dev/null
+++ b/openvidu-browser/old/OldStream.ts
@@ -0,0 +1,2069 @@
+/*
+ * (C) Copyright 2017-2022 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 './OldConnection';
+import { Filter } from '../Filter';
+import { Publisher } from './OldPublisher';
+import { Session } from './OldSession';
+import { StreamManager } from '../StreamManager';
+import { Subscriber } from '../Subscriber';
+import { InboundStreamOptions } from '../../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
+import { OutboundStreamOptions } from '../../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
+import {
+ WebRtcPeer,
+ WebRtcPeerSendonly,
+ WebRtcPeerRecvonly,
+ WebRtcPeerSendrecv,
+ WebRtcPeerConfiguration
+} from '../../OpenViduInternal/WebRtcPeer/WebRtcPeer';
+import { WebRtcStats } from '../../OpenViduInternal/WebRtcStats/WebRtcStats';
+import { ExceptionEvent, ExceptionEventName } from '../../OpenViduInternal/Events/ExceptionEvent';
+import { PublisherSpeakingEvent } from '../../OpenViduInternal/Events/PublisherSpeakingEvent';
+import { StreamManagerEvent } from '../../OpenViduInternal/Events/StreamManagerEvent';
+import { StreamPropertyChangedEvent } from '../../OpenViduInternal/Events/StreamPropertyChangedEvent';
+import { OpenViduError, OpenViduErrorName } from '../../OpenViduInternal/Enums/OpenViduError';
+import { TypeOfVideo } from '../../OpenViduInternal/Enums/TypeOfVideo';
+import { OpenViduLogger } from '../../OpenViduInternal/Logger/OpenViduLogger';
+import { PlatformUtils } from '../../OpenViduInternal/Utils/Platform';
+
+import { v4 as uuidv4 } from 'uuid';
+
+/**
+ * @hidden
+ */
+import hark = require('hark');
+/**
+ * @hidden
+ */
+import EventEmitter = require('wolfy87-eventemitter');
+import {
+ AudioTrack,
+ LocalAudioTrack,
+ LocalParticipant,
+ LocalTrackPublication,
+ LocalVideoTrack,
+ RemoteAudioTrack,
+ RemoteParticipant,
+ RemoteVideoTrack,
+ Track,
+ TrackPublishOptions,
+ VideoTrack
+} from 'livekit-client';
+/**
+ * @hidden
+ */
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+
+/**
+ * @hidden
+ */
+let platform: PlatformUtils;
+
+/**
+ * Represents each one of the media streams available in OpenVidu Server for certain session.
+ * Each {@link Publisher} and {@link Subscriber} has an attribute of type Stream, as they give access
+ * to one of them (sending and receiving it, respectively)
+ */
+export class Stream {
+ /**
+ * The Connection object that is publishing the stream
+ */
+ connection: Connection;
+
+ /**
+ * Frame rate of the video in frames per second. This property is only defined if the {@link Publisher} of
+ * the stream was initialized passing a _frameRate_ property on {@link OpenVidu.initPublisher} method
+ */
+ frameRate?: number;
+
+ /**
+ * Whether the stream has a video track or not
+ */
+ hasVideo: boolean;
+
+ /**
+ * Whether the stream has an audio track or not
+ */
+ hasAudio: boolean;
+
+ /**
+ * Whether the stream has the video track muted or unmuted. If {@link hasVideo} is false, this property is undefined.
+ *
+ * This property may change if the Publisher publishing the stream calls {@link Publisher.publishVideo}. Whenever this happens a {@link StreamPropertyChangedEvent} will be dispatched
+ * by the Session object as well as by the affected Subscriber/Publisher object
+ */
+ videoActive: boolean;
+
+ /**
+ * Whether the stream has the audio track muted or unmuted. If {@link hasAudio} is false, this property is undefined
+ *
+ * This property may change if the Publisher publishing the stream calls {@link Publisher.publishAudio}. Whenever this happens a {@link StreamPropertyChangedEvent} will be dispatched
+ * by the Session object as well as by the affected Subscriber/Publisher object
+ */
+ audioActive: boolean;
+
+ /**
+ * Unique identifier of the stream. If the stream belongs to a...
+ * - Subscriber object: property `streamId` is always defined
+ * - Publisher object: property `streamId` is only defined after successful execution of {@link Session.publish}
+ */
+ streamId: string;
+
+ /**
+ * Time when this stream was created in OpenVidu Server (UTC milliseconds). Depending on the owner of this stream:
+ * - Subscriber object: property `creationTime` is always defined
+ * - Publisher object: property `creationTime` is only defined after successful execution of {@link Session.publish}
+ */
+ creationTime: number;
+
+ /**
+ * Can be:
+ * - `"CAMERA"`: when the video source comes from a webcam.
+ * - `"SCREEN"`: when the video source comes from screen-sharing.
+ * - `"CUSTOM"`: when {@link PublisherProperties.videoSource} has been initialized in the Publisher side with a custom MediaStreamTrack when calling {@link OpenVidu.initPublisher}).
+ * - `"IPCAM"`: when the video source comes from an IP camera participant instead of a regular participant (see [IP cameras](/en/stable/advanced-features/ip-cameras/)).
+ *
+ * If {@link hasVideo} is false, this property is undefined
+ */
+ typeOfVideo?: keyof typeof TypeOfVideo; // TODO: Change this type to enum TypeOfVideo on the next breaking-change release
+
+ /**
+ * StreamManager object ({@link Publisher} or {@link Subscriber}) in charge of displaying this stream in the DOM
+ */
+ streamManager: StreamManager;
+
+ /**
+ * Width and height in pixels of the encoded video stream. If {@link hasVideo} is false, this property is undefined
+ *
+ * This property may change if the Publisher that is publishing:
+ * - If it is a mobile device, whenever the user rotates the device.
+ * - If it is screen-sharing, whenever the user changes the size of the captured window.
+ *
+ * Whenever this happens a {@link StreamPropertyChangedEvent} will be dispatched by the Session object as well as by the affected Subscriber/Publisher object
+ */
+ videoDimensions: { width: number; height: number };
+
+ /**
+ * **WARNING**: experimental option. This interface may change in the near future
+ *
+ * Filter applied to the Stream. You can apply filters by calling {@link Stream.applyFilter}, execute methods of the applied filter with
+ * {@link Filter.execMethod} and remove it with {@link Stream.removeFilter}. Be aware that the client calling this methods must have the
+ * necessary permissions: the token owned by the client must have been initialized with the appropriated `allowedFilters` array.
+ */
+ filter?: Filter;
+
+ protected webRtcPeer: WebRtcPeer;
+ protected mediaStreama?: MediaStream;
+ private webRtcStats: WebRtcStats;
+
+ private isSubscribeToRemote = false;
+
+ private virtualBackgroundSourceElements?: { videoClone: HTMLVideoElement; mediaStreamClone: MediaStream };
+ /**
+ * @hidden
+ */
+ virtualBackgroundSinkElements?: { VB: any; video: HTMLVideoElement };
+
+ /**
+ * @hidden
+ */
+ isLocalStreamReadyToPublish = false;
+ /**
+ * @hidden
+ */
+ isLocalStreamPublished = false;
+ /**
+ * @hidden
+ */
+ publishedOnce = false;
+ /**
+ * @hidden
+ */
+ session: Session;
+ /**
+ * @hidden
+ */
+ inboundStreamOpts: InboundStreamOptions;
+ /**
+ * @hidden
+ */
+ outboundStreamOpts: OutboundStreamOptions;
+ /**
+ * @hidden
+ */
+ speechEvent: any;
+ /**
+ * @hidden
+ */
+ harkSpeakingEnabled = false;
+ /**
+ * @hidden
+ */
+ harkSpeakingEnabledOnce = false;
+ /**
+ * @hidden
+ */
+ harkStoppedSpeakingEnabled = false;
+ /**
+ * @hidden
+ */
+ harkStoppedSpeakingEnabledOnce = false;
+ /**
+ * @hidden
+ */
+ harkVolumeChangeEnabled = false;
+ /**
+ * @hidden
+ */
+ harkVolumeChangeEnabledOnce = false;
+ /**
+ * @hidden
+ */
+ harkOptions;
+ /**
+ * @hidden
+ */
+ localMediaStreamWhenSubscribedToRemote?: MediaStream;
+ /**
+ * @hidden
+ */
+ ee = new EventEmitter();
+ /**
+ * @hidden
+ */
+ reconnectionEventEmitter: EventEmitter | undefined;
+ /**
+ * @hidden
+ */
+ lastVideoTrackConstraints: MediaTrackConstraints | boolean | undefined;
+ /**
+ * @hidden
+ */
+ lastVBFilter?: Filter;
+
+ /**
+ * @hidden
+ * Track to be published
+ */
+ temporalVideoTrack: VideoTrack | undefined;
+ /**
+ * @hidden
+ * * Track to be published
+ */
+ temporalAudioTrack: AudioTrack | undefined;
+
+ /**
+ * @hidden
+ * * Track to be published
+ */
+ temporalVideoScreenTrack: VideoTrack | undefined;
+ /**
+ * @hidden
+ * * Track to be published
+ */
+ temporalAudioScreenTrack: AudioTrack | undefined;
+
+ /**
+ * @hidden
+ */
+ constructor(session: Session, options: InboundStreamOptions | OutboundStreamOptions | {}) {
+ console.warn("'Stream' class is not ready yet.");
+ // platform = PlatformUtils.getInstance();
+ this.session = session;
+
+ if (options.hasOwnProperty('id')) {
+ // InboundStreamOptions: stream belongs to a Subscriber
+ this.inboundStreamOpts = options;
+ this.streamId = this.inboundStreamOpts.id;
+ this.creationTime = this.inboundStreamOpts.createdAt;
+ this.hasAudio = this.inboundStreamOpts.hasAudio;
+ this.hasVideo = this.inboundStreamOpts.hasVideo;
+ if (this.hasAudio) {
+ this.audioActive = this.inboundStreamOpts.audioActive;
+ }
+ if (this.hasVideo) {
+ this.videoActive = this.inboundStreamOpts.videoActive;
+ this.typeOfVideo = !this.inboundStreamOpts.typeOfVideo ? undefined : this.inboundStreamOpts.typeOfVideo;
+ this.frameRate = this.inboundStreamOpts.frameRate === -1 ? undefined : this.inboundStreamOpts.frameRate;
+ this.videoDimensions = this.inboundStreamOpts.videoDimensions;
+ }
+ if (!!this.inboundStreamOpts.filter && Object.keys(this.inboundStreamOpts.filter).length > 0) {
+ if (
+ !!this.inboundStreamOpts.filter.lastExecMethod &&
+ Object.keys(this.inboundStreamOpts.filter.lastExecMethod).length === 0
+ ) {
+ delete this.inboundStreamOpts.filter.lastExecMethod;
+ }
+ this.filter = this.inboundStreamOpts.filter;
+ }
+ } else {
+ // OutboundStreamOptions: stream belongs to a Publisher
+ this.outboundStreamOpts = options;
+
+ this.hasAudio = this.isSendAudio();
+ this.hasVideo = this.isSendVideo();
+
+ if (this.hasAudio) {
+ this.audioActive = !!this.outboundStreamOpts.publisherProperties.publishAudio;
+ }
+ if (this.hasVideo) {
+ this.videoActive = !!this.outboundStreamOpts.publisherProperties.publishVideo;
+ this.frameRate = this.outboundStreamOpts.publisherProperties.frameRate;
+ if (
+ typeof MediaStreamTrack !== 'undefined' &&
+ this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack
+ ) {
+ this.typeOfVideo = TypeOfVideo.CUSTOM;
+ } else {
+ this.typeOfVideo = this.isSendScreen() ? TypeOfVideo.SCREEN : TypeOfVideo.CAMERA;
+ }
+ }
+ // if (!!this.outboundStreamOpts.publisherProperties.filter) {
+ // this.filter = this.outboundStreamOpts.publisherProperties.filter;
+ // }
+ }
+
+ this.ee.on('mediastream-updated', () => {
+ // this.streamManager.updateMediaStream(this.mediaStream!);
+ // logger.debug('Video srcObject [' + this.mediaStream?.id + '] updated in stream [' + this.streamId + ']');
+ });
+ }
+
+ /**
+ * Recreates the media connection with the server. This entails the disposal of the previous RTCPeerConnection and the re-negotiation
+ * of a new one, that will apply the same properties.
+ *
+ * This method can be useful in those situations were there the media connection breaks and OpenVidu is not able to recover on its own
+ * for any kind of unanticipated reason (see [Automatic reconnection](/en/stable/advanced-features/automatic-reconnection/)).
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved if the reconnection operation was successful and rejected with an Error object if not
+ */
+ public reconnect(): Promise {
+ throw new Error("'reconnect' method is not ready yet.");
+ return this.reconnectStream('API');
+ }
+
+ /**
+ * Applies an audio/video filter to the stream.
+ *
+ * @param type Type of filter applied. See {@link Filter.type}
+ * @param options Parameters used to initialize the filter. See {@link Filter.options}
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved to the applied filter if success and rejected with an Error object if not
+ */
+ applyFilter(type: string, options: Object): Promise {
+ throw new Error("'applyFilter' method is not ready yet.");
+ // return new Promise(async (resolve, reject) => {
+ // if (!!this.filter) {
+ // return reject(
+ // new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'There is already a filter applied to Stream ' + this.streamId)
+ // );
+ // }
+
+ // const resolveApplyFilter = (error, triggerEvent) => {
+ // if (error) {
+ // logger.error('Error applying filter for Stream ' + this.streamId, error);
+ // if (error.code === 401) {
+ // return reject(
+ // new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to apply a filter")
+ // );
+ // } else {
+ // return reject(error);
+ // }
+ // } else {
+ // logger.info('Filter successfully applied on Stream ' + this.streamId);
+ // const oldValue: Filter = this.filter!;
+ // this.filter = new Filter(type, options);
+ // this.filter.stream = this;
+ // if (triggerEvent) {
+ // this.session.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(this.session, this, 'filter', this.filter, oldValue, 'applyFilter')
+ // ]);
+ // this.streamManager.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(this.streamManager, this, 'filter', this.filter, oldValue, 'applyFilter')
+ // ]);
+ // }
+ // return resolve(this.filter);
+ // }
+ // };
+
+ // if (type.startsWith('VB:')) {
+ // // Client filters
+
+ // if (!this.hasVideo) {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
+ // 'The Virtual Background filter requires a video track to be applied'
+ // )
+ // );
+ // }
+ // if (!this.mediaStream || this.streamManager.videos.length === 0) {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
+ // 'The StreamManager requires some video element to be attached to it in order to apply a Virtual Background filter'
+ // )
+ // );
+ // }
+
+ // let openviduToken: string;
+ // if (!!this.session.token) {
+ // openviduToken = this.session.token;
+ // } else {
+ // openviduToken = options['token'];
+ // }
+ // if (!openviduToken) {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
+ // 'Virtual Background requires the client to be connected to a Session or to have a "token" property available in "options" parameter with a valid OpenVidu token'
+ // )
+ // );
+ // }
+
+ // const tokenParams = this.session.getTokenParams(openviduToken);
+ // if (tokenParams.edition !== 'pro' && tokenParams.edition !== 'enterprise') {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
+ // 'OpenVidu Virtual Background API is available from OpenVidu Pro edition onwards'
+ // )
+ // );
+ // }
+
+ // openviduToken = encodeURIComponent(btoa(openviduToken));
+
+ // logger.info('Applying Virtual Background to stream ' + this.streamId);
+
+ // const afterScriptLoaded = async () => {
+ // try {
+ // const id = this.streamId + '_' + uuidv4();
+ // const mediaStreamClone = this.mediaStream!.clone();
+ // const videoClone = this.streamManager.videos[0].video.cloneNode(false) as HTMLVideoElement;
+ // // @ts-ignore
+ // videoClone.id = VirtualBackground.VirtualBackground.SOURCE_VIDEO_PREFIX + id;
+ // videoClone.srcObject = mediaStreamClone;
+ // videoClone.muted = true;
+ // this.virtualBackgroundSourceElements = { videoClone, mediaStreamClone };
+
+ // // @ts-ignore
+ // VirtualBackground.VirtualBackground.hideHtmlElement(videoClone, false);
+ // // @ts-ignore
+ // VirtualBackground.VirtualBackground.appendHtmlElementToHiddenContainer(videoClone, id);
+
+ // await videoClone.play();
+
+ // // @ts-ignore
+ // const VB = new VirtualBackground.VirtualBackground({
+ // id,
+ // openviduServerUrl: new URL(tokenParams.httpUri),
+ // openviduToken,
+ // inputVideo: videoClone,
+ // inputResolution: '160x96',
+ // outputFramerate: 24
+ // });
+
+ // let filteredVideo: HTMLVideoElement;
+ // switch (type) {
+ // case 'VB:blur': {
+ // filteredVideo = await VB.backgroundBlur(options);
+ // break;
+ // }
+ // case 'VB:image': {
+ // filteredVideo = await VB.backgroundImage(options);
+ // break;
+ // }
+ // default:
+ // throw new Error('Unknown Virtual Background filter: ' + type);
+ // }
+
+ // this.virtualBackgroundSinkElements = { VB, video: filteredVideo };
+
+ // videoClone.style.display = 'none';
+
+ // if (this.streamManager.remote) {
+ // this.streamManager.replaceTrackInMediaStream(
+ // (this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0],
+ // false
+ // );
+ // } else {
+ // (this.streamManager as Publisher).replaceTrackAux(
+ // (this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0],
+ // false
+ // );
+ // }
+
+ // resolveApplyFilter(undefined, false);
+ // } catch (error) {
+ // if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
+ // resolveApplyFilter(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, error.message), false);
+ // } else {
+ // resolveApplyFilter(error, false);
+ // }
+ // }
+ // };
+
+ // // @ts-ignore
+ // if (typeof VirtualBackground === 'undefined') {
+ // let script: HTMLScriptElement = document.createElement('script');
+ // script.type = 'text/javascript';
+ // script.src = tokenParams.httpUri + '/openvidu/virtual-background/openvidu-virtual-background.js?token=' + openviduToken;
+ // script.onload = async () => {
+ // try {
+ // await afterScriptLoaded();
+ // resolve(new Filter(type, options));
+ // } catch (error) {
+ // reject(error);
+ // }
+ // };
+ // document.body.appendChild(script);
+ // } else {
+ // afterScriptLoaded()
+ // .then(() => resolve(new Filter(type, options)))
+ // .catch((error) => reject(error));
+ // }
+ // } else {
+ // // Server filters
+
+ // if (!this.session.sessionConnected()) {
+ // return reject(this.session.notConnectedError());
+ // }
+
+ // logger.info('Applying server filter to stream ' + this.streamId);
+ // options = options != null ? options : {};
+ // let optionsString = options;
+ // if (typeof optionsString !== 'string') {
+ // optionsString = JSON.stringify(optionsString);
+ // }
+ // this.session.openvidu.sendRequest(
+ // 'applyFilter',
+ // { streamId: this.streamId, type, options: optionsString },
+ // (error, response) => {
+ // resolveApplyFilter(error, true);
+ // }
+ // );
+ // }
+ // });
+ }
+
+ /**
+ * Removes an audio/video filter previously applied.
+ *
+ * @returns A Promise (to which you can optionally subscribe to) that is resolved if the previously applied filter has been successfully removed and rejected with an Error object in other case
+ */
+ async removeFilter(): Promise {
+ throw new Error("'removeFilter' method is not ready yet.");
+ return await this.removeFilterAux(false);
+ }
+
+ /**
+ * Returns the internal RTCPeerConnection object associated to this stream (https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection)
+ *
+ * @returns Native RTCPeerConnection Web API object
+ */
+ getRTCPeerConnection(): RTCPeerConnection {
+ throw new Error("'getRTCPeerConnection' method is not ready yet.");
+ return this.webRtcPeer.pc;
+ }
+
+ /**
+ * Returns the internal MediaStream object associated to this stream (https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)
+ *
+ * @returns Native MediaStream Web API object
+ */
+ getMediaStream(): MediaStream {
+ // const mediaSream = new MediaStream();
+ // if (this.videoTrack) {
+ // mediaSream.addTrack(this.videoTrack.mediaStreamTrack);
+ // }
+ // if (this.audioTrack) {
+ // mediaSream.addTrack(this.audioTrack.mediaStreamTrack);
+ // }
+ // return mediaSream;
+ throw new Error("'getMediaStream' method is not ready yet.");
+
+ // return this.mediaStream!;
+ }
+
+ /* Hidden methods */
+
+ /**
+ * @hidden
+ */
+ removeFilterAux(isDisposing: boolean): Promise {
+ throw new Error("'removeFilterAux' method is not ready yet.");
+ return new Promise(async (resolve, reject) => {
+ const resolveRemoveFilter = (error, triggerEvent) => {
+ if (error) {
+ delete this.filter;
+ logger.error('Error removing filter for Stream ' + this.streamId, error);
+ if (error.code === 401) {
+ return reject(
+ new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to remove a filter")
+ );
+ } else {
+ return reject(error);
+ }
+ } else {
+ logger.info('Filter successfully removed from Stream ' + this.streamId);
+ const oldValue = this.filter!;
+ delete this.filter;
+ if (triggerEvent) {
+ this.session.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this.session, this, 'filter', this.filter!, oldValue, 'applyFilter')
+ ]);
+ this.streamManager.emitEvent('streamPropertyChanged', [
+ new StreamPropertyChangedEvent(this.streamManager, this, 'filter', this.filter!, oldValue, 'applyFilter')
+ ]);
+ }
+ return resolve();
+ }
+ };
+
+ if (!!this.filter) {
+ // There is a filter applied
+
+ if (this.filter?.type.startsWith('VB:')) {
+ // Client filters
+
+ try {
+ const mediaStreamClone = this.virtualBackgroundSourceElements!.mediaStreamClone;
+ if (!isDisposing) {
+ if (this.streamManager.remote) {
+ this.streamManager.replaceTrackInMediaStream(mediaStreamClone.getVideoTracks()[0], false);
+ } else {
+ await (this.streamManager as Publisher).replaceTrackAux(mediaStreamClone.getVideoTracks()[0], false);
+ }
+ } else {
+ mediaStreamClone.getTracks().forEach((track) => track.stop());
+ }
+
+ this.virtualBackgroundSinkElements!.VB.cleanUp();
+
+ delete this.virtualBackgroundSinkElements;
+ delete this.virtualBackgroundSourceElements;
+
+ return resolveRemoveFilter(undefined, false);
+ } catch (error) {
+ return resolveRemoveFilter(error, false);
+ }
+ } else {
+ // Server filters
+
+ if (!this.session.sessionConnected()) {
+ return reject(this.session.notConnectedError());
+ }
+
+ logger.info('Removing filter of stream ' + this.streamId);
+ this.session.openvidu.sendRequest('removeFilter', { streamId: this.streamId }, (error, response) => {
+ return resolveRemoveFilter(error, true);
+ });
+ }
+ } else {
+ // There is no filter applied
+ return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Stream ' + this.streamId + ' has no filter applied'));
+ }
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ setMediaStream(mediaStream: MediaStream): void {
+ // this.mediaStream = mediaStream;
+ }
+
+ /**
+ * @hidden
+ */
+ updateMediaStreamInVideos() {
+ throw new Error("'updateMediaStreamInVideos' method is not ready yet.");
+ // this.ee.emitEvent('mediastream-updated', []);
+ }
+
+ /**
+ * @hidden
+ */
+ getWebRtcPeer(): WebRtcPeer {
+ throw new Error("'getWebRtcPeer' method is not ready yet.");
+ return this.webRtcPeer;
+ }
+
+ /**
+ * @hidden
+ */
+ subscribeToMyRemote(value: boolean): void {
+ throw new Error("'subscribeToMyRemote' method is not ready yet.");
+ this.isSubscribeToRemote = value;
+ }
+
+ /**
+ * @hidden
+ */
+ setOutboundStreamOptions(outboundStreamOpts: OutboundStreamOptions): void {
+ this.outboundStreamOpts = outboundStreamOpts;
+ }
+
+ /**
+ * @hidden
+ */
+ // subscribe(): Promise {
+
+ // throw new Error("'subscribe' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // this.initWebRtcPeerReceive(false)
+ // .then(() => resolve())
+ // .catch((error) => reject(error));
+ // });
+ // }
+
+ /**
+ * @hidden
+ */
+ async publish(): Promise {
+ console.warn("'publish' method is not ready yet.");
+
+ const { localParticipant } = this.session.room;
+ // const videoStreamTrack = this.getMediaStream().getVideoTracks()[0];
+ // const audioStreamTrack = this.getMediaStream().getAudioTracks()[0];
+ const promises: Promise[] = [];
+
+ // TODO here should be initialized the following properties?
+ // params = {
+ // doLoopback: this.displayMyRemote() || false,
+ // hasAudio: this.isSendAudio(),
+ // hasVideo: this.isSendVideo(),
+ // audioActive: this.audioActive,
+ // videoActive: this.videoActive,
+ // typeOfVideo,
+ // frameRate: !!this.frameRate ? this.frameRate : -1,
+ // videoDimensions: JSON.stringify(this.videoDimensions),
+ // filter: this.outboundStreamOpts.publisherProperties.filter,
+ // sdpOffer: sdpOfferParam
+ // };
+ this.streamId = localParticipant.identity + '_' + uuidv4();
+
+ const publishOpt: TrackPublishOptions = {
+ name: this.streamId,
+ stream: this.streamId
+ };
+
+ if (this.temporalVideoTrack || this.temporalAudioTrack) {
+ if (this.temporalVideoTrack) {
+ // this.videoScreenTrack?.stop();
+ // this.videoScreenTrack = undefined;
+ publishOpt.source = Track.Source.Camera;
+ // localParticipant.setCameraEnabled(true);
+ const videoTrackPublication = await localParticipant.publishTrack(this.temporalVideoTrack.mediaStreamTrack, publishOpt);
+ debugger;
+ }
+
+ if (this.temporalAudioTrack) {
+ // this.audioScreenTrack?.stop();
+ // this.audioScreenTrack = undefined;
+ publishOpt.source = Track.Source.Microphone;
+ const audioTrackPublication = await localParticipant.publishTrack(this.temporalAudioTrack.mediaStreamTrack, publishOpt);
+ }
+ } else if (this.temporalVideoScreenTrack || this.temporalAudioScreenTrack) {
+ if (this.temporalVideoScreenTrack) {
+ // if (this.videoTrack) {
+ // (this.videoTrack as VideoTrack).stop();
+ // }
+ // this.videoTrack = undefined;
+ publishOpt.source = Track.Source.ScreenShare;
+ await localParticipant.publishTrack(this.temporalVideoScreenTrack.mediaStreamTrack, publishOpt);
+ }
+
+ if (this.temporalAudioScreenTrack) {
+ // if (this.audioTrack) {
+ // (this.audioTrack as AudioTrack).stop();
+ // }
+ // this.audioTrack = undefined;
+ publishOpt.source = Track.Source.ScreenShareAudio;
+ await localParticipant.publishTrack(this.temporalAudioScreenTrack.mediaStreamTrack, publishOpt);
+ }
+ }
+
+ // const [videoTrackPublication, audioTrackPublication] = await Promise.all(promises);
+ // console.log('videoTrack', videoTrackPublication);
+ // console.log('audioTrack', audioTrackPublication);
+ // if (videoTrackPublication.videoTrack) {
+ // this.videoTrack = videoTrackPublication.videoTrack;
+ // }
+ // if (audioTrackPublication.audioTrack) {
+ // this.audioTrack = audioTrackPublication.audioTrack;
+ // }
+
+ this.creationTime = new Date().getTime();
+ this.isLocalStreamPublished = true;
+ this.publishedOnce = true;
+ if (this.displayMyRemote()) {
+ // this.localMediaStreamWhenSubscribedToRemote = this.mediaStream;
+ // this.remotePeerSuccessfullyEstablished(reconnect);
+ }
+ // if (reconnect) {
+ // this.ee.emitEvent('stream-reconnected-by-publisher', []);
+ // } else {
+ this.ee.emitEvent('stream-created-by-publisher', []);
+ // }
+ this.streamManager.stream = this;
+
+ logger.info(`'Publisher' (${this.streamId}) successfully published to session`);
+
+ // if (!!this.mediaStream && this.streamManager.videos.length > 0) {
+ // // this.updateMediaStreamInVideos();
+ // }
+
+ // return new Promise((resolve, reject) => {
+ // if (this.isLocalStreamReadyToPublish) {
+ // this.initWebRtcPeerSend(false)
+ // .then(() => resolve())
+ // .catch((error) => reject(error));
+ // } else {
+ // this.ee.once('stream-ready-to-publish', () => {
+ // this.publish()
+ // .then(() => resolve())
+ // .catch((error) => reject(error));
+ // });
+ // }
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ disposeWebRtcPeer(): void {
+ throw new Error("'disposeWebRtcPeer' method is not ready yet.");
+
+ // let webrtcId;
+ // if (!!this.webRtcPeer) {
+ // this.webRtcPeer.dispose();
+ // webrtcId = this.webRtcPeer.getId();
+ // }
+ // this.stopWebRtcStats();
+ // logger.info(
+ // (!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') +
+ // 'RTCPeerConnection with id [' +
+ // webrtcId +
+ // "] from 'Stream' with id [" +
+ // this.streamId +
+ // '] is now closed'
+ // );
+ }
+
+ /**
+ * @hidden
+ */
+ async disposeMediaStream(): Promise {
+ console.warn("'disposeMediaStream' method is not ready yet.");
+
+ // if (!!this.filter && this.filter.type.startsWith('VB:')) {
+ // try {
+ // await this.removeFilterAux(true);
+ // console.debug(`Success removing Virtual Background filter for stream ${this.streamId}`);
+ // } catch (error) {
+ // console.error(`Error removing Virtual Background filter for stream ${this.streamId}`, error);
+ // }
+ // }
+
+ // if (this.mediaStream) {
+ // this.mediaStream.getAudioTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // this.mediaStream.getVideoTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // delete this.mediaStream;
+ // }
+
+ // If subscribeToRemote local MediaStream must be stopped
+ // if (this.localMediaStreamWhenSubscribedToRemote) {
+ // this.localMediaStreamWhenSubscribedToRemote.getAudioTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // this.localMediaStreamWhenSubscribedToRemote.getVideoTracks().forEach((track) => {
+ // track.stop();
+ // });
+ // delete this.localMediaStreamWhenSubscribedToRemote;
+ // }
+
+ // if (!!this.speechEvent) {
+ // if (!!this.speechEvent.stop) {
+ // this.speechEvent.stop();
+ // }
+ // delete this.speechEvent;
+ // }
+ logger.info(
+ (!!this.outboundStreamOpts ? 'Local ' : 'Remote ') + "MediaStream from 'Stream' with id [" + this.streamId + '] is now disposed'
+ );
+ }
+
+ /**
+ * @hidden
+ */
+ displayMyRemote(): boolean {
+ return this.isSubscribeToRemote;
+ // throw new Error("'displayMyRemote' method is not ready yet.");
+ }
+
+ /**
+ * @hidden
+ */
+ isSendAudio(): boolean {
+ // throw new Error("'isSendAudio' method is not ready yet.");
+ return (
+ !!this.outboundStreamOpts &&
+ this.outboundStreamOpts.publisherProperties.audioSource !== null &&
+ this.outboundStreamOpts.publisherProperties.audioSource !== false
+ );
+ }
+
+ /**
+ * @hidden
+ */
+ isSendVideo(): boolean {
+ return (
+ !!this.outboundStreamOpts &&
+ this.outboundStreamOpts.publisherProperties.videoSource !== null &&
+ this.outboundStreamOpts.publisherProperties.videoSource !== false
+ );
+ }
+
+ /**
+ * @hidden
+ */
+ isSendScreen(): boolean {
+ let screen = false;
+ if (
+ typeof MediaStreamTrack !== 'undefined' &&
+ this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack
+ ) {
+ let trackSettings: any = this.outboundStreamOpts.publisherProperties.videoSource.getSettings();
+ if (trackSettings.displaySurface) {
+ screen = ['monitor', 'window', 'browser'].includes(trackSettings.displaySurface);
+ }
+ }
+ // if (!screen && platform.isElectron()) {
+ // screen =
+ // typeof this.outboundStreamOpts.publisherProperties.videoSource === 'string' &&
+ // this.outboundStreamOpts.publisherProperties.videoSource.startsWith('screen:');
+ // }
+ if (!screen) {
+ screen = this.outboundStreamOpts.publisherProperties.videoSource === 'screen';
+ }
+ return !!this.outboundStreamOpts && screen;
+ }
+
+ /**
+ * @hidden
+ */
+ enableHarkSpeakingEvent(): void {
+ throw new Error("'enableHarkSpeakingEvent' method is not ready yet.");
+ this.setHarkListenerIfNotExists();
+ if (!this.harkSpeakingEnabled && !!this.speechEvent) {
+ this.harkSpeakingEnabled = true;
+ this.speechEvent.on('speaking', () => {
+ this.session.emitEvent('publisherStartSpeaking', [
+ new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)
+ ]);
+ this.streamManager.emitEvent('publisherStartSpeaking', [
+ new PublisherSpeakingEvent(this.streamManager, 'publisherStartSpeaking', this.connection, this.streamId)
+ ]);
+ this.harkSpeakingEnabledOnce = false; // Disable 'once' version if 'on' version was triggered
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ enableOnceHarkSpeakingEvent(): void {
+ throw new Error("'enableOnceHarkSpeakingEvent' method is not ready yet.");
+ this.setHarkListenerIfNotExists();
+ if (!this.harkSpeakingEnabledOnce && !!this.speechEvent) {
+ this.harkSpeakingEnabledOnce = true;
+ this.speechEvent.once('speaking', () => {
+ if (this.harkSpeakingEnabledOnce) {
+ // If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
+ this.session.emitEvent('publisherStartSpeaking', [
+ new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)
+ ]);
+ this.streamManager.emitEvent('publisherStartSpeaking', [
+ new PublisherSpeakingEvent(this.streamManager, 'publisherStartSpeaking', this.connection, this.streamId)
+ ]);
+ }
+ this.disableHarkSpeakingEvent(true);
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ disableHarkSpeakingEvent(disabledByOnce: boolean): void {
+ throw new Error("'disableHarkSpeakingEvent' method is not ready yet.");
+ if (!!this.speechEvent) {
+ this.harkSpeakingEnabledOnce = false;
+ if (disabledByOnce) {
+ if (this.harkSpeakingEnabled) {
+ // The 'on' version of this same event is enabled too. Do not remove the hark listener
+ return;
+ }
+ } else {
+ this.harkSpeakingEnabled = false;
+ }
+ // Shutting down the hark event
+ if (
+ this.harkVolumeChangeEnabled ||
+ this.harkVolumeChangeEnabledOnce ||
+ this.harkStoppedSpeakingEnabled ||
+ this.harkStoppedSpeakingEnabledOnce
+ ) {
+ // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
+ this.speechEvent.off('speaking');
+ } else {
+ // No other hark event is enabled. We can get entirely rid of it
+ this.speechEvent.stop();
+ delete this.speechEvent;
+ }
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ enableHarkStoppedSpeakingEvent(): void {
+ throw new Error("'enableHarkStoppedSpeakingEvent' method is not ready yet.");
+ this.setHarkListenerIfNotExists();
+ if (!this.harkStoppedSpeakingEnabled && !!this.speechEvent) {
+ this.harkStoppedSpeakingEnabled = true;
+ this.speechEvent.on('stopped_speaking', () => {
+ this.session.emitEvent('publisherStopSpeaking', [
+ new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)
+ ]);
+ this.streamManager.emitEvent('publisherStopSpeaking', [
+ new PublisherSpeakingEvent(this.streamManager, 'publisherStopSpeaking', this.connection, this.streamId)
+ ]);
+ this.harkStoppedSpeakingEnabledOnce = false; // Disable 'once' version if 'on' version was triggered
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ enableOnceHarkStoppedSpeakingEvent(): void {
+ throw new Error("'enableOnceHarkStoppedSpeakingEvent' method is not ready yet.");
+ this.setHarkListenerIfNotExists();
+ if (!this.harkStoppedSpeakingEnabledOnce && !!this.speechEvent) {
+ this.harkStoppedSpeakingEnabledOnce = true;
+ this.speechEvent.once('stopped_speaking', () => {
+ if (this.harkStoppedSpeakingEnabledOnce) {
+ // If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
+ this.session.emitEvent('publisherStopSpeaking', [
+ new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)
+ ]);
+ this.streamManager.emitEvent('publisherStopSpeaking', [
+ new PublisherSpeakingEvent(this.streamManager, 'publisherStopSpeaking', this.connection, this.streamId)
+ ]);
+ }
+ this.disableHarkStoppedSpeakingEvent(true);
+ });
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ disableHarkStoppedSpeakingEvent(disabledByOnce: boolean): void {
+ throw new Error("'disableHarkStoppedSpeakingEvent' method is not ready yet.");
+ if (!!this.speechEvent) {
+ this.harkStoppedSpeakingEnabledOnce = false;
+ if (disabledByOnce) {
+ if (this.harkStoppedSpeakingEnabled) {
+ // We are cancelling the 'once' listener for this event, but the 'on' version
+ // of this same event is enabled too. Do not remove the hark listener
+ return;
+ }
+ } else {
+ this.harkStoppedSpeakingEnabled = false;
+ }
+ // Shutting down the hark event
+ if (
+ this.harkVolumeChangeEnabled ||
+ this.harkVolumeChangeEnabledOnce ||
+ this.harkSpeakingEnabled ||
+ this.harkSpeakingEnabledOnce
+ ) {
+ // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
+ this.speechEvent.off('stopped_speaking');
+ } else {
+ // No other hark event is enabled. We can get entirely rid of it
+ this.speechEvent.stop();
+ delete this.speechEvent;
+ }
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ enableHarkVolumeChangeEvent(force: boolean): void {
+ throw new Error("'enableHarkVolumeChangeEvent' method is not ready yet.");
+ if (this.setHarkListenerIfNotExists()) {
+ if (!this.harkVolumeChangeEnabled || force) {
+ this.harkVolumeChangeEnabled = true;
+ this.speechEvent.on('volume_change', (harkEvent) => {
+ const oldValue = this.speechEvent.oldVolumeValue;
+ const value = { newValue: harkEvent, oldValue };
+ this.speechEvent.oldVolumeValue = harkEvent;
+ this.streamManager.emitEvent('streamAudioVolumeChange', [
+ new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)
+ ]);
+ });
+ }
+ } else {
+ // This way whenever the MediaStream object is available, the event listener will be automatically added
+ this.harkVolumeChangeEnabled = true;
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ enableOnceHarkVolumeChangeEvent(force: boolean): void {
+ throw new Error("'enableOnceHarkVolumeChangeEvent' method is not ready yet.");
+ if (this.setHarkListenerIfNotExists()) {
+ if (!this.harkVolumeChangeEnabledOnce || force) {
+ this.harkVolumeChangeEnabledOnce = true;
+ this.speechEvent.once('volume_change', (harkEvent) => {
+ const oldValue = this.speechEvent.oldVolumeValue;
+ const value = { newValue: harkEvent, oldValue };
+ this.speechEvent.oldVolumeValue = harkEvent;
+ this.disableHarkVolumeChangeEvent(true);
+ this.streamManager.emitEvent('streamAudioVolumeChange', [
+ new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)
+ ]);
+ });
+ }
+ } else {
+ // This way whenever the MediaStream object is available, the event listener will be automatically added
+ this.harkVolumeChangeEnabledOnce = true;
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ disableHarkVolumeChangeEvent(disabledByOnce: boolean): void {
+ throw new Error("'disableHarkVolumeChangeEvent' method is not ready yet.");
+ if (!!this.speechEvent) {
+ this.harkVolumeChangeEnabledOnce = false;
+ if (disabledByOnce) {
+ if (this.harkVolumeChangeEnabled) {
+ // We are cancelling the 'once' listener for this event, but the 'on' version
+ // of this same event is enabled too. Do not remove the hark listener
+ return;
+ }
+ } else {
+ this.harkVolumeChangeEnabled = false;
+ }
+ // Shutting down the hark event
+ if (
+ this.harkSpeakingEnabled ||
+ this.harkSpeakingEnabledOnce ||
+ this.harkStoppedSpeakingEnabled ||
+ this.harkStoppedSpeakingEnabledOnce
+ ) {
+ // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
+ this.speechEvent.off('volume_change');
+ } else {
+ // No other hark event is enabled. We can get entirely rid of it
+ this.speechEvent.stop();
+ delete this.speechEvent;
+ }
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ isLocal(): boolean {
+ // throw new Error("'isLocal' method is not ready yet.");
+ // inbound options undefined and outbound options defined
+ return !this.inboundStreamOpts && !!this.outboundStreamOpts;
+ }
+
+ /**
+ * @hidden
+ */
+ getSelectedIceCandidate(): Promise {
+ throw new Error("'getSelectedIceCandidate' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ this.webRtcStats
+ .getSelectedIceCandidateInfo()
+ .then((report) => resolve(report))
+ .catch((error) => reject(error));
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ getRemoteIceCandidateList(): RTCIceCandidate[] {
+ throw new Error("'getRemoteIceCandidateList' method is not ready yet.");
+ return this.webRtcPeer.remoteCandidatesQueue;
+ }
+
+ /**
+ * @hidden
+ */
+ getLocalIceCandidateList(): RTCIceCandidate[] {
+ throw new Error("'getLocalIceCandidateList' method is not ready yet.");
+ return this.webRtcPeer.localCandidatesQueue;
+ }
+
+ /**
+ * @hidden
+ */
+ streamIceConnectionStateBroken() {
+ throw new Error('Method not implemented');
+ if (!this.getWebRtcPeer() || !this.getRTCPeerConnection()) {
+ return false;
+ }
+ if (!!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
+ logger.warn(
+ `OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${
+ this.streamId
+ } (${this.isLocal() ? 'Publisher' : 'Subscriber'}) will force a reconnection`
+ );
+ return true;
+ } else {
+ const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
+ return iceConnectionState !== 'connected' && iceConnectionState !== 'completed';
+ }
+ }
+
+ /* Private methods */
+
+ private setHarkListenerIfNotExists(): boolean {
+ throw new Error("'setHarkListenerIfNotExists' method is not ready yet.");
+ // if (!!this.mediaStream) {
+ // if (!this.speechEvent) {
+ // const harkOptions = !!this.harkOptions
+ // ? this.harkOptions
+ // : this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
+ // harkOptions.interval = typeof harkOptions.interval === 'number' ? harkOptions.interval : 100;
+ // harkOptions.threshold = typeof harkOptions.threshold === 'number' ? harkOptions.threshold : -50;
+ // this.speechEvent = hark(this.mediaStream, harkOptions);
+ // }
+ // return true;
+ // }
+ // return false;
+ }
+
+ /**
+ * @hidden
+ */
+ setupReconnectionEventEmitter(resolve: (value: void | PromiseLike) => void, reject: (reason?: any) => void): boolean {
+ throw new Error("'setupReconnectionEventEmitter' method is not ready yet.");
+ // if (this.reconnectionEventEmitter == undefined) {
+ // // There is no ongoing reconnection
+ // this.reconnectionEventEmitter = new EventEmitter();
+ // return false;
+ // } else {
+ // // Ongoing reconnection
+ // console.warn(
+ // `Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) but an ongoing reconnection process is active. Waiting for response...`
+ // );
+ // this.reconnectionEventEmitter.once('success', () => resolve());
+ // this.reconnectionEventEmitter.once('error', (error) => reject(error));
+ // return true;
+ // }
+ }
+
+ /**
+ * @hidden
+ */
+ // initWebRtcPeerSend(reconnect: boolean): Promise {
+
+ // throw new Error("'initWebRtcPeerSend' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // if (reconnect) {
+ // if (this.setupReconnectionEventEmitter(resolve, reject)) {
+ // // Ongoing reconnection
+ // return;
+ // }
+ // } else {
+ // // MediaStream will already have hark events for reconnected streams
+ // this.initHarkEvents(); // Init hark events for the local stream
+ // }
+
+ // const finalResolve = () => {
+ // if (reconnect) {
+ // this.reconnectionEventEmitter?.emitEvent('success');
+ // delete this.reconnectionEventEmitter;
+ // }
+ // return resolve();
+ // };
+
+ // const finalReject = (error) => {
+ // if (reconnect) {
+ // this.reconnectionEventEmitter?.emitEvent('error', [error]);
+ // delete this.reconnectionEventEmitter;
+ // }
+ // return reject(error);
+ // };
+
+ // const successOfferCallback = (sdpOfferParam) => {
+ // logger.debug('Sending SDP offer to publish as ' + this.streamId, sdpOfferParam);
+
+ // const method = reconnect ? 'reconnectStream' : 'publishVideo';
+ // let params;
+ // if (reconnect) {
+ // params = {
+ // stream: this.streamId,
+ // sdpString: sdpOfferParam
+ // };
+ // } else {
+ // let typeOfVideo;
+ // if (this.isSendVideo()) {
+ // typeOfVideo =
+ // typeof MediaStreamTrack !== 'undefined' &&
+ // this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack
+ // ? TypeOfVideo.CUSTOM
+ // : this.isSendScreen()
+ // ? TypeOfVideo.SCREEN
+ // : TypeOfVideo.CAMERA;
+ // }
+ // params = {
+ // doLoopback: this.displayMyRemote() || false,
+ // hasAudio: this.isSendAudio(),
+ // hasVideo: this.isSendVideo(),
+ // audioActive: this.audioActive,
+ // videoActive: this.videoActive,
+ // typeOfVideo,
+ // frameRate: !!this.frameRate ? this.frameRate : -1,
+ // videoDimensions: JSON.stringify(this.videoDimensions),
+ // filter: this.outboundStreamOpts.publisherProperties.filter,
+ // sdpOffer: sdpOfferParam
+ // };
+ // }
+
+ // this.session.openvidu.sendRequest(method, params, (error, response) => {
+ // if (error) {
+ // if (error.code === 401) {
+ // finalReject(
+ // new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish")
+ // );
+ // } else {
+ // finalReject('Error on publishVideo: ' + JSON.stringify(error));
+ // }
+ // } else {
+ // this.webRtcPeer
+ // .processRemoteAnswer(response.sdpAnswer)
+ // .then(() => {
+ // this.streamId = response.id;
+ // this.creationTime = response.createdAt;
+ // this.isLocalStreamPublished = true;
+ // this.publishedOnce = true;
+ // if (this.displayMyRemote()) {
+ // this.localMediaStreamWhenSubscribedToRemote = this.mediaStream;
+ // this.remotePeerSuccessfullyEstablished(reconnect);
+ // }
+ // if (reconnect) {
+ // this.ee.emitEvent('stream-reconnected-by-publisher', []);
+ // } else {
+ // this.ee.emitEvent('stream-created-by-publisher', []);
+ // }
+ // this.initWebRtcStats();
+ // logger.info(
+ // "'Publisher' (" +
+ // this.streamId +
+ // ') successfully ' +
+ // (reconnect ? 'reconnected' : 'published') +
+ // ' to session'
+ // );
+
+ // finalResolve();
+ // })
+ // .catch((error) => {
+ // finalReject(error);
+ // });
+ // }
+ // });
+ // };
+
+ // const config: WebRtcPeerConfiguration = {
+ // mediaConstraints: {
+ // audio: this.hasAudio,
+ // video: this.hasVideo
+ // },
+ // simulcast: this.outboundStreamOpts.publisherProperties.videoSimulcast ?? this.session.openvidu.videoSimulcast,
+ // onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
+ // onIceConnectionStateException: this.onIceConnectionStateExceptionHandler.bind(this),
+ // iceServers: this.getIceServersConf(),
+ // mediaStream: this.mediaStream,
+ // mediaServer: this.session.openvidu.mediaServer,
+ // typeOfVideo: this.typeOfVideo ? TypeOfVideo[this.typeOfVideo] : undefined
+ // };
+
+ // if (this.session.openvidu.mediaServer !== 'mediasoup') {
+ // // Simulcast is only supported by mediasoup
+ // config.simulcast = false;
+ // }
+
+ // if (reconnect) {
+ // this.disposeWebRtcPeer();
+ // }
+ // if (this.displayMyRemote()) {
+ // this.webRtcPeer = new WebRtcPeerSendrecv(config);
+ // } else {
+ // this.webRtcPeer = new WebRtcPeerSendonly(config);
+ // }
+ // this.webRtcPeer.addIceConnectionStateChangeListener('publisher of ' + this.connection.connectionId);
+ // this.webRtcPeer
+ // .createOffer()
+ // .then((sdpOffer) => {
+ // this.webRtcPeer
+ // .processLocalOffer(sdpOffer)
+ // .then(() => {
+ // successOfferCallback(sdpOffer.sdp);
+ // })
+ // .catch((error) => {
+ // finalReject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
+ // });
+ // })
+ // .catch((error) => {
+ // finalReject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
+ // });
+ // });
+ // }
+
+ /**
+ * @hidden
+ */
+ finalResolveForSubscription(reconnect: boolean, resolve: (value: void | PromiseLike) => void) {
+ throw new Error('Method not implemented');
+ // logger.info("'Subscriber' (" + this.streamId + ') successfully ' + (reconnect ? 'reconnected' : 'subscribed'));
+ // this.remotePeerSuccessfullyEstablished(reconnect);
+ // this.initWebRtcStats();
+ // if (reconnect) {
+ // this.reconnectionEventEmitter?.emitEvent('success');
+ // delete this.reconnectionEventEmitter;
+ // }
+ // return resolve();
+ }
+
+ /**
+ * @hidden
+ */
+ finalRejectForSubscription(reconnect: boolean, error: any, reject: (reason?: any) => void) {
+ throw new Error('Method not implemented');
+ // logger.error(
+ // "Error for 'Subscriber' (" +
+ // this.streamId +
+ // ') while trying to ' +
+ // (reconnect ? 'reconnect' : 'subscribe') +
+ // ': ' +
+ // error.toString()
+ // );
+ // if (reconnect) {
+ // this.reconnectionEventEmitter?.emitEvent('error', [error]);
+ // delete this.reconnectionEventEmitter;
+ // }
+ // return reject(error);
+ }
+
+ /**
+ * @hidden
+ */
+ initWebRtcPeerReceive(reconnect: boolean): Promise {
+ throw new Error("'initWebRtcPeerReceive' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // if (reconnect) {
+ // if (this.setupReconnectionEventEmitter(resolve, reject)) {
+ // // Ongoing reconnection
+ // return;
+ // }
+ // }
+
+ // if (this.session.openvidu.mediaServer === 'mediasoup') {
+ // // Server initiates negotiation
+
+ // this.initWebRtcPeerReceiveFromServer(reconnect)
+ // .then(() => this.finalResolveForSubscription(reconnect, resolve))
+ // .catch((error) => this.finalRejectForSubscription(reconnect, error, reject));
+ // } else {
+ // // Client initiates negotiation
+
+ // this.initWebRtcPeerReceiveFromClient(reconnect)
+ // .then(() => this.finalResolveForSubscription(reconnect, resolve))
+ // .catch((error) => this.finalRejectForSubscription(reconnect, error, reject));
+ // }
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ initWebRtcPeerReceiveFromClient(reconnect: boolean): Promise {
+ throw new Error("'initWebRtcPeerReceiveFromClient' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // this.completeWebRtcPeerReceive(reconnect, false)
+ // .then((response) => {
+ // this.webRtcPeer
+ // .processRemoteAnswer(response.sdpAnswer)
+ // .then(() => resolve())
+ // .catch((error) => reject(error));
+ // })
+ // .catch((error) => reject(error));
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ initWebRtcPeerReceiveFromServer(reconnect: boolean): Promise {
+ throw new Error("'initWebRtcPeerReceiveFromServer' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // // Server initiates negotiation
+ // this.session.openvidu.sendRequest('prepareReceiveVideoFrom', { sender: this.streamId, reconnect }, (error, response) => {
+ // if (error) {
+ // return reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error)));
+ // } else {
+ // this.completeWebRtcPeerReceive(reconnect, false, response.sdpOffer)
+ // .then(() => resolve())
+ // .catch((error) => reject(error));
+ // }
+ // });
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ completeWebRtcPeerReceive(reconnect: boolean, forciblyReconnect: boolean, sdpOfferByServer?: string): Promise {
+ throw new Error("'completeWebRtcPeerReceive' method is not ready yet.");
+ // return new Promise((resolve, reject) => {
+ // logger.debug("'Session.subscribe(Stream)' called");
+
+ // const sendSdpToServer = (sdpString: string) => {
+ // logger.debug(`Sending local SDP ${!!sdpOfferByServer ? 'answer' : 'offer'} to subscribe to ${this.streamId}`, sdpString);
+
+ // const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
+ // const params = {};
+ // params[reconnect ? 'stream' : 'sender'] = this.streamId;
+ // if (!!sdpOfferByServer) {
+ // params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpString;
+ // } else {
+ // params['sdpOffer'] = sdpString;
+ // }
+ // if (reconnect) {
+ // params['forciblyReconnect'] = forciblyReconnect;
+ // }
+
+ // this.session.openvidu.sendRequest(method, params, (error, response) => {
+ // if (error) {
+ // return reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
+ // } else {
+ // return resolve(response);
+ // }
+ // });
+ // };
+
+ // const config: WebRtcPeerConfiguration = {
+ // mediaConstraints: {
+ // audio: this.hasAudio,
+ // video: this.hasVideo
+ // },
+ // simulcast: false,
+ // onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
+ // onIceConnectionStateException: this.onIceConnectionStateExceptionHandler.bind(this),
+ // iceServers: this.getIceServersConf(),
+ // mediaServer: this.session.openvidu.mediaServer,
+ // typeOfVideo: this.typeOfVideo ? TypeOfVideo[this.typeOfVideo] : undefined
+ // };
+
+ // if (reconnect) {
+ // this.disposeWebRtcPeer();
+ // }
+
+ // this.webRtcPeer = new WebRtcPeerRecvonly(config);
+ // this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
+
+ // if (!!sdpOfferByServer) {
+ // this.webRtcPeer
+ // .processRemoteOffer(sdpOfferByServer)
+ // .then(() => {
+ // this.webRtcPeer
+ // .createAnswer()
+ // .then((sdpAnswer) => {
+ // this.webRtcPeer
+ // .processLocalAnswer(sdpAnswer)
+ // .then(() => {
+ // sendSdpToServer(sdpAnswer.sdp!);
+ // })
+ // .catch((error) => {
+ // return reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
+ // });
+ // })
+ // .catch((error) => {
+ // return reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error)));
+ // });
+ // })
+ // .catch((error) => {
+ // return reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error)));
+ // });
+ // } else {
+ // this.webRtcPeer
+ // .createOffer()
+ // .then((sdpOffer) => {
+ // this.webRtcPeer
+ // .processLocalOffer(sdpOffer)
+ // .then(() => {
+ // sendSdpToServer(sdpOffer.sdp!);
+ // })
+ // .catch((error) => {
+ // return reject(new Error('(subscribe) SDP process local offer error: ' + JSON.stringify(error)));
+ // });
+ // })
+ // .catch((error) => {
+ // return reject(new Error('(subscribe) SDP create offer error: ' + JSON.stringify(error)));
+ // });
+ // }
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ remotePeerSuccessfullyEstablished(reconnect: boolean): void {
+ throw new Error("'remotePeerSuccessfullyEstablished' method is not ready yet.");
+ // if (reconnect && this.mediaStream != null) {
+ // // Now we can destroy the existing MediaStream
+ // this.disposeMediaStream();
+ // }
+
+ // this.mediaStream = new MediaStream();
+ // let receiver: RTCRtpReceiver;
+ // for (receiver of this.webRtcPeer.pc.getReceivers()) {
+ // if (!!receiver.track) {
+ // this.mediaStream.addTrack(receiver.track);
+ // }
+ // }
+ // logger.debug('Peer remote stream', this.mediaStream);
+
+ // if (!!this.mediaStream) {
+ // if (this.streamManager instanceof Subscriber) {
+ // // Apply SubscriberProperties.subscribeToAudio and SubscriberProperties.subscribeToVideo
+ // if (!!this.mediaStream.getAudioTracks()[0]) {
+ // const enabled = reconnect ? this.audioActive : !!(this.streamManager as Subscriber).properties.subscribeToAudio;
+ // this.mediaStream.getAudioTracks()[0].enabled = enabled;
+ // }
+ // if (!!this.mediaStream.getVideoTracks()[0]) {
+ // const enabled = reconnect ? this.videoActive : !!this.videoActive && !!(this.streamManager as Subscriber).properties.subscribeToVideo;
+ // this.mediaStream.getVideoTracks()[0].enabled = enabled;
+ // }
+ // }
+
+ // this.updateMediaStreamInVideos();
+ // this.initHarkEvents(); // Init hark events for the remote stream
+ // }
+ }
+
+ /**
+ * @hidden
+ */
+ // initHarkEvents(): void {
+ // throw new Error("'initHarkEvents' method is not ready yet.");
+ // if (!!this.mediaStream!.getAudioTracks()[0]) {
+ // // Hark events can only be set if audio track is available
+ // if (this.session.anySpeechEventListenerEnabled('publisherStartSpeaking', true, this.streamManager)) {
+ // this.enableOnceHarkSpeakingEvent();
+ // }
+ // if (this.session.anySpeechEventListenerEnabled('publisherStartSpeaking', false, this.streamManager)) {
+ // this.enableHarkSpeakingEvent();
+ // }
+ // if (this.session.anySpeechEventListenerEnabled('publisherStopSpeaking', true, this.streamManager)) {
+ // this.enableOnceHarkStoppedSpeakingEvent();
+ // }
+ // if (this.session.anySpeechEventListenerEnabled('publisherStopSpeaking', false, this.streamManager)) {
+ // this.enableHarkStoppedSpeakingEvent();
+ // }
+ // if (this.harkVolumeChangeEnabledOnce) {
+ // this.enableOnceHarkVolumeChangeEvent(true);
+ // }
+ // if (this.harkVolumeChangeEnabled) {
+ // this.enableHarkVolumeChangeEvent(true);
+ // }
+ // }
+ // }
+
+ private onIceConnectionStateExceptionHandler(exceptionName: ExceptionEventName, message: string, data?: any): void {
+ throw new Error("'onIceConnectionStateExceptionHandler' method is not ready yet.");
+ // switch (exceptionName) {
+ // case ExceptionEventName.ICE_CONNECTION_FAILED:
+ // this.onIceConnectionFailed();
+ // break;
+ // case ExceptionEventName.ICE_CONNECTION_DISCONNECTED:
+ // this.onIceConnectionDisconnected();
+ // break;
+ // }
+ // this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]);
+ }
+
+ private onIceConnectionFailed() {
+ throw new Error("'onIceConnectionFailed' method is not ready yet.");
+ // // Immediately reconnect, as this is a terminal error
+ // logger.log(
+ // `[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // })`
+ // );
+ // this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED);
+ }
+
+ private onIceConnectionDisconnected() {
+ throw new Error("'onIceConnectionDisconnected' method is not ready yet.");
+ // Wait to see if the ICE connection is able to reconnect
+ // logger.log(
+ // `[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${this.streamId
+ // } (${this.isLocal() ? 'Publisher' : 'Subscriber'}) if not possible`
+ // );
+ // const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
+ // this.awaitWebRtcPeerConnectionState(timeout).then((state) => {
+ // switch (state) {
+ // case 'failed':
+ // // Do nothing, as an ICE_CONNECTION_FAILED event will have already raised
+ // logger.warn(
+ // `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) is now failed after ICE_CONNECTION_DISCONNECTED`
+ // );
+ // break;
+ // case 'connected':
+ // case 'completed':
+ // logger.log(
+ // `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}`
+ // );
+ // break;
+ // case 'closed':
+ // case 'checking':
+ // case 'new':
+ // case 'disconnected':
+ // // Rest of states
+ // logger.warn(
+ // `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}`
+ // );
+ // this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED);
+ // break;
+ // }
+ // });
+ }
+
+ private async reconnectStreamAndLogResultingIceConnectionState(event: string) {
+ throw new Error("'reconnectStreamAndLogResultingIceConnectionState' method is not ready yet.");
+ // try {
+ // const finalIceStateAfterReconnection = await this.reconnectStreamAndReturnIceConnectionState(event);
+ // switch (finalIceStateAfterReconnection) {
+ // case 'connected':
+ // case 'completed':
+ // logger.log(
+ // `[${event}] Stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`
+ // );
+ // break;
+ // default:
+ // logger.error(
+ // `[${event}] Stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`
+ // );
+ // break;
+ // }
+ // } catch (error) {
+ // logger.error(
+ // `[${event}] Error reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) after ${event}: ${error}`
+ // );
+ // }
+ }
+
+ private async reconnectStreamAndReturnIceConnectionState(event: string): Promise {
+ throw new Error("'reconnectStreamAndReturnIceConnectionState' method is not ready yet.");
+ // logger.log(`[${event}] Reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'}) after event ${event}`);
+ // try {
+ // await this.reconnectStream(event);
+ // const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
+ // return this.awaitWebRtcPeerConnectionState(timeout);
+ // } catch (error) {
+ // logger.warn(
+ // `[${event}] Error reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'}). Reason: ${error}`
+ // );
+ // return this.awaitWebRtcPeerConnectionState(1);
+ // }
+ }
+
+ private async awaitWebRtcPeerConnectionState(timeout: number): Promise {
+ throw new Error("'awaitWebRtcPeerConnectionState' method is not ready yet.");
+ // let state = this.getRTCPeerConnection().iceConnectionState;
+ // const interval = 150;
+ // const intervals = Math.ceil(timeout / interval);
+ // for (let i = 0; i < intervals; i++) {
+ // state = this.getRTCPeerConnection().iceConnectionState;
+ // if (state === 'connected' || state === 'completed') {
+ // break;
+ // }
+ // // Sleep
+ // await new Promise((resolve) => setTimeout(resolve, interval));
+ // }
+ // return state;
+ }
+
+ private async reconnectStream(event: string) {
+ throw new Error("'reconnectStream' method is not ready yet.");
+ // const isWsConnected = await this.isWebsocketConnected(event, 3000);
+ // if (isWsConnected) {
+ // // There is connection to openvidu-server. The RTCPeerConnection is the only one broken
+ // logger.log(
+ // `[${event}] Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) and the websocket is opened`
+ // );
+ // if (this.isLocal()) {
+ // return this.initWebRtcPeerSend(true);
+ // } else {
+ // return this.initWebRtcPeerReceive(true);
+ // }
+ // } else {
+ // // There is no connection to openvidu-server. Nothing can be done. The automatic reconnection
+ // // feature should handle a possible reconnection of RTCPeerConnection in case network comes back
+ // const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
+ // }) but the websocket wasn't opened`;
+ // logger.error(errorMsg);
+ // throw Error(errorMsg);
+ // }
+ }
+
+ private isWebsocketConnected(event: string, msResponseTimeout: number): Promise {
+ throw new Error("'isWebsocketConnected' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ const wsReadyState = this.session.openvidu.getWsReadyState();
+ if (wsReadyState === 1) {
+ const responseTimeout = setTimeout(() => {
+ console.warn(`[${event}] Websocket timeout of ${msResponseTimeout}ms`);
+ return resolve(false);
+ }, msResponseTimeout);
+ this.session.openvidu.sendRequest('echo', {}, (error, response) => {
+ clearTimeout(responseTimeout);
+ if (!!error) {
+ console.warn(`[${event}] Websocket 'echo' returned error: ${error}`);
+ return resolve(false);
+ } else {
+ return resolve(true);
+ }
+ });
+ } else {
+ console.warn(`[${event}] Websocket readyState is ${wsReadyState}`);
+ return resolve(false);
+ }
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ initWebRtcStats(): void {
+ throw new Error("'initWebRtcStats' method is not ready yet.");
+ this.webRtcStats = new WebRtcStats(this);
+ this.webRtcStats.initWebRtcStats();
+
+ //TODO: send common webrtc stats from client to openvidu-server
+ /*if (this.session.openvidu.webrtcStatsInterval > 0) {
+ setInterval(() => {
+ this.gatherStatsForPeer().then(jsonStats => {
+ const body = {
+ sessionId: this.session.sessionId,
+ participantPrivateId: this.connection.rpcSessionId,
+ stats: jsonStats
+ }
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', this.session.openvidu.httpUri + '/elasticsearch/webrtc-stats', true);
+ xhr.setRequestHeader('Content-Type', 'application/json');
+ xhr.send(JSON.stringify(body));
+ })
+ }, this.session.openvidu.webrtcStatsInterval * 1000);
+ }*/
+ }
+
+ private stopWebRtcStats(): void {
+ throw new Error("'stopWebRtcStats' method is not ready yet.");
+ if (!!this.webRtcStats && this.webRtcStats.isEnabled()) {
+ this.webRtcStats.stopWebRtcStats();
+ }
+ }
+
+ private getIceServersConf(): RTCIceServer[] | undefined {
+ throw new Error("'getIceServersConf' method is not ready yet.");
+ let returnValue;
+ if (!!this.session.openvidu.advancedConfiguration.iceServers) {
+ returnValue =
+ this.session.openvidu.advancedConfiguration.iceServers === 'freeice'
+ ? undefined
+ : this.session.openvidu.advancedConfiguration.iceServers;
+ } else if (this.session.openvidu.iceServers) {
+ returnValue = this.session.openvidu.iceServers;
+ } else {
+ returnValue = undefined;
+ }
+ return returnValue;
+ }
+
+ private gatherStatsForPeer(): Promise {
+ throw new Error("'gatherStatsForPeer' method is not ready yet.");
+ return new Promise((resolve, reject) => {
+ if (this.isLocal()) {
+ // Publisher stream stats
+
+ this.getRTCPeerConnection()
+ .getSenders()
+ .forEach((sender) =>
+ sender.getStats().then((response) => {
+ response.forEach((report) => {
+ if (this.isReportWanted(report)) {
+ const finalReport = {};
+
+ finalReport['type'] = report.type;
+ finalReport['timestamp'] = report.timestamp;
+ finalReport['id'] = report.id;
+
+ // Common to Chrome, Firefox and Safari
+ if (report.type === 'outbound-rtp') {
+ finalReport['ssrc'] = report.ssrc;
+ finalReport['firCount'] = report.firCount;
+ finalReport['pliCount'] = report.pliCount;
+ finalReport['nackCount'] = report.nackCount;
+ finalReport['qpSum'] = report.qpSum;
+
+ // Set media type
+ if (!!report.kind) {
+ finalReport['mediaType'] = report.kind;
+ } else if (!!report.mediaType) {
+ finalReport['mediaType'] = report.mediaType;
+ } else {
+ // Safari does not have 'mediaType' defined for inbound-rtp. Must be inferred from 'id' field
+ finalReport['mediaType'] = report.id.indexOf('VideoStream') !== -1 ? 'video' : 'audio';
+ }
+
+ if (finalReport['mediaType'] === 'video') {
+ finalReport['framesEncoded'] = report.framesEncoded;
+ }
+
+ finalReport['packetsSent'] = report.packetsSent;
+ finalReport['bytesSent'] = report.bytesSent;
+ }
+
+ // Only for Chrome and Safari
+ if (report.type === 'candidate-pair' && report.totalRoundTripTime !== undefined) {
+ // This is the final selected candidate pair
+ finalReport['availableOutgoingBitrate'] = report.availableOutgoingBitrate;
+ finalReport['rtt'] = report.currentRoundTripTime;
+ finalReport['averageRtt'] = report.totalRoundTripTime / report.responsesReceived;
+ }
+
+ // Only for Firefox >= 66.0
+ if (report.type === 'remote-inbound-rtp' || report.type === 'remote-outbound-rtp') {
+ }
+
+ logger.log(finalReport);
+ }
+ });
+ })
+ );
+ } else {
+ // Subscriber stream stats
+
+ this.getRTCPeerConnection()
+ .getReceivers()
+ .forEach((receiver) =>
+ receiver.getStats().then((response) => {
+ response.forEach((report) => {
+ if (this.isReportWanted(report)) {
+ const finalReport = {};
+
+ finalReport['type'] = report.type;
+ finalReport['timestamp'] = report.timestamp;
+ finalReport['id'] = report.id;
+
+ // Common to Chrome, Firefox and Safari
+ if (report.type === 'inbound-rtp') {
+ finalReport['ssrc'] = report.ssrc;
+ finalReport['firCount'] = report.firCount;
+ finalReport['pliCount'] = report.pliCount;
+ finalReport['nackCount'] = report.nackCount;
+ finalReport['qpSum'] = report.qpSum;
+
+ // Set media type
+ if (!!report.kind) {
+ finalReport['mediaType'] = report.kind;
+ } else if (!!report.mediaType) {
+ finalReport['mediaType'] = report.mediaType;
+ } else {
+ // Safari does not have 'mediaType' defined for inbound-rtp. Must be inferred from 'id' field
+ finalReport['mediaType'] = report.id.indexOf('VideoStream') !== -1 ? 'video' : 'audio';
+ }
+
+ if (finalReport['mediaType'] === 'video') {
+ finalReport['framesDecoded'] = report.framesDecoded;
+ }
+
+ finalReport['packetsReceived'] = report.packetsReceived;
+ finalReport['packetsLost'] = report.packetsLost;
+ finalReport['jitter'] = report.jitter;
+ finalReport['bytesReceived'] = report.bytesReceived;
+ }
+
+ // Only for Chrome and Safari
+ if (report.type === 'candidate-pair' && report.totalRoundTripTime !== undefined) {
+ // This is the final selected candidate pair
+ finalReport['availableIncomingBitrate'] = report.availableIncomingBitrate;
+ finalReport['rtt'] = report.currentRoundTripTime;
+ finalReport['averageRtt'] = report.totalRoundTripTime / report.responsesReceived;
+ }
+
+ // Only for Firefox >= 66.0
+ if (report.type === 'remote-inbound-rtp' || report.type === 'remote-outbound-rtp') {
+ }
+ logger.log(finalReport);
+ }
+ });
+ })
+ );
+ }
+ });
+ }
+
+ private isReportWanted(report: any): boolean {
+ throw new Error("'isReportWanted' method is not ready yet.");
+ return (
+ (report.type === 'inbound-rtp' && !this.isLocal()) ||
+ (report.type === 'outbound-rtp' && this.isLocal()) ||
+ (report.type === 'candidate-pair' && report.nominated && report.bytesSent > 0)
+ );
+ }
+}
diff --git a/openvidu-browser/old/OldStreamManager.ts b/openvidu-browser/old/OldStreamManager.ts
new file mode 100644
index 00000000000..28f219afdb7
--- /dev/null
+++ b/openvidu-browser/old/OldStreamManager.ts
@@ -0,0 +1,681 @@
+/*
+ * (C) Copyright 2017-2022 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 '../src/OpenVidu/Stream';
+import { Subscriber } from '../src/OpenVidu/Subscriber';
+import { EventDispatcher } from '../src/OpenVidu/EventDispatcher';
+import { StreamManagerVideo } from '../src/OpenViduInternal/Interfaces/Public/StreamManagerVideo';
+import { StreamManagerEventMap } from '../src/OpenViduInternal/Events/EventMap/StreamManagerEventMap';
+import { StreamManagerEvent } from '../src/OpenViduInternal/Events/StreamManagerEvent';
+import { VideoElementEvent } from '../src/OpenViduInternal/Events/VideoElementEvent';
+import { ExceptionEvent, ExceptionEventName } from '../src/OpenViduInternal/Events/ExceptionEvent';
+import { VideoInsertMode } from '../src/OpenViduInternal/Enums/VideoInsertMode';
+import { OpenViduLogger } from '../src/OpenViduInternal/Logger/OpenViduLogger';
+import { PlatformUtils } from '../src/OpenViduInternal/Utils/Platform';
+import { LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack, RemoteVideoTrack } from 'livekit-client';
+
+/**
+ * @hidden
+ */
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+
+/**
+ * @hidden
+ */
+let platform: PlatformUtils;
+
+/**
+ * Interface in charge of displaying the media streams in the HTML DOM. This wraps any {@link Publisher} and {@link Subscriber} object.
+ * You can insert as many video players fo the same Stream as you want by calling {@link StreamManager.addVideoElement} or
+ * {@link 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
+ *
+ * See available event listeners at {@link StreamManagerEventMap}.
+ */
+export abstract class StreamManager extends EventDispatcher {
+ /**
+ * 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 {@link Publisher}
+ * - `true` for {@link Subscriber}
+ */
+ remote: boolean;
+
+ /**
+ * The DOM HTMLElement assigned as target element when creating the video for the Publisher/Subscriber. This property is only defined if:
+ * - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
+ * - {@link Subscriber} has been initialized by calling method {@link 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:
+ * - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
+ * - {@link Subscriber} has been initialized by calling method {@link Session.subscribe} with a valid `targetElement` parameter
+ */
+ id: string;
+
+ /**
+ * @hidden
+ */
+ protected firstVideoElement?: StreamManagerVideo;
+ /**
+ * @hidden
+ */
+ protected element: HTMLElement;
+ /**
+ * @hidden
+ */
+ protected canPlayListener: EventListener;
+ /**
+ * @hidden
+ */
+ protected streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
+ /**
+ * @hidden
+ */
+ private lazyLaunchVideoElementCreatedEvent = false;
+
+ /**
+ * @hidden
+ */
+ constructor(stream: Stream, targetElement?: HTMLElement | string) {
+ console.warn("'StreamManager' class is not ready yet");
+ super();
+
+ // platform = PlatformUtils.getInstance();
+ this.stream = stream;
+ this.stream.streamManager = this;
+ this.remote = !this.stream.isLocal();
+
+ if (!!targetElement) {
+ // TODO. si el susuario proporciona un targetElement habria que crear un video dentro del componente con ese targetElement
+ let targetElementAux: HTMLElement | null = null;
+ if (typeof targetElement === 'string') {
+ targetElementAux = document.getElementById(targetElement);
+ } else if (targetElement instanceof HTMLElement) {
+ targetElementAux = targetElement;
+ }
+
+ if (!!targetElementAux) {
+ this.firstVideoElement = {
+ targetElement: targetElementAux,
+ video: document.createElement('video'),
+ id: '',
+ canplayListenerAdded: false
+ };
+ // if (
+ // platform.isSafariBrowser() ||
+ // (platform.isIPhoneOrIPad() &&
+ // (platform.isChromeMobileBrowser() ||
+ // platform.isEdgeMobileBrowser() ||
+ // platform.isOperaMobileBrowser() ||
+ // platform.isFirefoxMobileBrowser()))
+ // ) {
+ // this.firstVideoElement.video.playsInline = true;
+ // }
+ this.targetElement = targetElementAux;
+ this.element = targetElementAux;
+ }
+ }
+
+ this.canPlayListener = () => {
+ this.deactivateStreamPlayingEventExceptionTimeout();
+ this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
+ };
+ }
+
+ /**
+ * See {@link EventDispatcher.on}
+ */
+ on(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
+ console.warn("STREAM MANAGER 'on' method is not ready yet");
+ super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
+
+ if (type === 'videoElementCreated') {
+ // throw new Error("'videoElementCreated' event is not ready yet");
+ // TODO it is necessary the lazyLaunchVideoElementCreatedEvent?
+ // this.lazyLaunchVideoElementCreatedEvent = true;
+ if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
+ this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
+ this.lazyLaunchVideoElementCreatedEvent = false;
+ }
+ }
+ if (type === 'streamPlaying') {
+ // throw new Error("'streamPlaying' event is not ready yet");
+
+ 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, 'streamPlaying', undefined)]);
+ }
+ }
+ if (this.stream.hasAudio) {
+ if (type === 'publisherStartSpeaking') {
+ throw new Error("'publisherStartSpeaking' event is not ready yet");
+ // this.stream.enableHarkSpeakingEvent();
+ }
+ if (type === 'publisherStopSpeaking') {
+ throw new Error("'publisherStopSpeaking' event is not ready yet");
+ // this.stream.enableHarkStoppedSpeakingEvent();
+ }
+ if (type === 'streamAudioVolumeChange') {
+ throw new Error("'streamAudioVolumeChange' event is not ready yet");
+ // this.stream.enableHarkVolumeChangeEvent(false);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * See {@link EventDispatcher.once}
+ */
+ once(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
+ super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
+
+ if (type === 'videoElementCreated') {
+ if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
+ this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
+ }
+ }
+ if (type === 'streamPlaying') {
+ 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, 'streamPlaying', undefined)]);
+ }
+ }
+ // if (this.stream.hasAudio) {
+ // if (type === 'publisherStartSpeaking') {
+ // this.stream.enableOnceHarkSpeakingEvent();
+ // }
+ // if (type === 'publisherStopSpeaking') {
+ // this.stream.enableOnceHarkStoppedSpeakingEvent();
+ // }
+ // if (type === 'streamAudioVolumeChange') {
+ // this.stream.enableOnceHarkVolumeChangeEvent(false);
+ // }
+ // }
+ return this;
+ }
+
+ /**
+ * See {@link EventDispatcher.off}
+ */
+ off(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
+ super.offAux(type, handler);
+
+ if (type === 'publisherStartSpeaking') {
+ throw new Error("'off publisherStartSpeaking' method is not ready yet");
+
+ // Both StreamManager and Session can have "publisherStartSpeaking" event listeners
+ const remainingStartSpeakingEventListeners =
+ this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
+ if (remainingStartSpeakingEventListeners === 0) {
+ // this.stream.disableHarkSpeakingEvent(false);
+ }
+ }
+ if (type === 'publisherStopSpeaking') {
+ throw new Error("'off publisherStopSpeaking' method is not ready yet");
+
+ // Both StreamManager and Session can have "publisherStopSpeaking" event listeners
+ const remainingStopSpeakingEventListeners =
+ this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
+ if (remainingStopSpeakingEventListeners === 0) {
+ // this.stream.disableHarkStoppedSpeakingEvent(false);
+ }
+ }
+ if (type === 'streamAudioVolumeChange') {
+ throw new Error("'off streamAudioVolumeChange' method is not ready yet");
+
+ // Only StreamManager can have "streamAudioVolumeChange" event listeners
+ const remainingVolumeEventListeners = this.ee.getListeners(type).length;
+ if (remainingVolumeEventListeners === 0) {
+ // this.stream.disableHarkVolumeChangeEvent(false);
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Makes `video` element parameter display this {@link stream}. This is useful when you are
+ * [managing the video elements on your own](/en/stable/cheatsheet/manage-videos/#you-take-care-of-the-video-players)
+ *
+ * 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 {
+ console.warn('addVideoElement not ready yet in StreamManagerAdapter');
+ this.initializeVideoProperties(video);
+
+ // TODO: livekit attach method must be called here
+ if (this.stream.temporalVideoTrack || this.stream.temporalAudioTrack) {
+
+ if(this.stream.temporalVideoTrack) {
+ this.stream.temporalVideoTrack.attach(video);
+ }
+ if(this.stream.temporalAudioTrack) {
+ this.stream.temporalAudioTrack.attach(video);
+ }
+
+
+ } else if (this.stream.temporalVideoScreenTrack || this.stream.temporalAudioScreenTrack) {
+
+ if (this.stream.temporalVideoScreenTrack) {
+ this.stream.temporalVideoScreenTrack.attach(video);
+ }
+ if (this.stream.temporalAudioScreenTrack) {
+ this.stream.temporalAudioScreenTrack.attach(video);
+ }
+ }
+
+ // if (!this.remote && this.stream.displayMyRemote()) {
+ // if (video.srcObject !== this.stream.getMediaStream()) {
+ // video.srcObject = this.stream.getMediaStream();
+ // }
+ // }
+
+ // 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;
+
+ 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,
+ canplayListenerAdded: false
+ });
+
+ logger.info('New video element associated to ', this);
+
+ return returnNumber;
+ }
+
+ /**
+ * Creates a new video element displaying this {@link stream}. This allows you to have multiple video elements displaying the same media stream.
+ *
+ * #### Events dispatched
+ *
+ * The Publisher/Subscriber object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM. See {@link VideoElementEvent}
+ *
+ * @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher/Subscriber will be inserted
+ * @param insertMode How the video element will be inserted accordingly to `targetElemet`
+ *
+ * @returns The created HTMLVideoElement
+ */
+ createVideoElement(targetElement?: string | HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement {
+ throw new Error("'createVideoElement' method is not ready yet");
+ // let targEl;
+ // if (typeof targetElement === 'string') {
+ // targEl = document.getElementById(targetElement);
+ // 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 = this.createVideo();
+ // 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,
+ // canplayListenerAdded: false
+ // };
+ // this.pushNewStreamManagerVideo(v);
+
+ // this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]);
+ // this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement;
+
+ // return video;
+ }
+
+ /**
+ * Updates the current configuration for the {@link PublisherSpeakingEvent} feature and the [StreamManagerEvent.streamAudioVolumeChange](/en/stable/api/openvidu-browser/classes/StreamManagerEvent.html) feature for this specific
+ * StreamManager audio stream, overriding the global options set with {@link OpenVidu.setAdvancedConfiguration}. This way you can customize the audio events options
+ * for each specific StreamManager and change them dynamically.
+ *
+ * @param publisherSpeakingEventsOptions New options to be applied to this StreamManager's audio stream. It is an object which includes the following optional properties:
+ * - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
+ * - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
+ */
+ updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number; threshold?: number }): void {
+ throw new Error("'updatePublisherSpeakingEventsOptions' method is not ready yet");
+ // const currentHarkOptions = !!this.stream.harkOptions
+ // ? this.stream.harkOptions
+ // : this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
+ // const newInterval =
+ // typeof publisherSpeakingEventsOptions.interval === 'number'
+ // ? publisherSpeakingEventsOptions.interval
+ // : typeof currentHarkOptions.interval === 'number'
+ // ? currentHarkOptions.interval
+ // : 100;
+ // const newThreshold =
+ // typeof publisherSpeakingEventsOptions.threshold === 'number'
+ // ? publisherSpeakingEventsOptions.threshold
+ // : typeof currentHarkOptions.threshold === 'number'
+ // ? currentHarkOptions.threshold
+ // : -50;
+ // this.stream.harkOptions = {
+ // interval: newInterval,
+ // threshold: newThreshold
+ // };
+ // if (!!this.stream.speechEvent) {
+ // this.stream.speechEvent.setInterval(newInterval);
+ // this.stream.speechEvent.setThreshold(newThreshold);
+ // }
+ }
+
+ /* Hidden methods */
+
+ /**
+ * @hidden
+ */
+ initializeVideoProperties(video: HTMLVideoElement): void {
+ // throw new Error("'initializeVideoProperties' method is not ready yet");
+ // if (!(!this.remote && this.stream.displayMyRemote())) {
+ // // Avoid setting the MediaStream into the srcObject if remote subscription before publishing
+ // if (video.srcObject !== this.stream.getMediaStream()) {
+ // // If srcObject already set don't do it again
+ // video.srcObject = this.stream.getMediaStream();
+ // }
+ // }
+ video.autoplay = true;
+ video.controls = false;
+
+ // if (
+ // platform.isSafariBrowser() ||
+ // (platform.isIPhoneOrIPad() &&
+ // (platform.isChromeMobileBrowser() ||
+ // platform.isEdgeMobileBrowser() ||
+ // platform.isOperaMobileBrowser() ||
+ // platform.isFirefoxMobileBrowser()))
+ // ) {
+ video.playsInline = true;
+ // }
+
+ if (!video.id) {
+ video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId;
+ // DEPRECATED property: assign once the property id if the user provided a valid targetElement
+ if (!this.id && !!this.targetElement) {
+ this.id = video.id;
+ }
+ }
+
+ // if (this.remote && this.isMirroredVideo(video)) {
+ // // Subscriber video associated to a previously mirrored video element
+ // this.removeMirrorVideo(video);
+ // } else
+ if (!this.remote && !this.stream.displayMyRemote()) {
+ // Publisher video
+ video.muted = true;
+ }
+ // if (this.isMirroredVideo(video) && !this.stream.outboundStreamOpts.publisherProperties.mirror) {
+ // // If the video was already rotated and now is set to not mirror
+ // this.removeMirrorVideo(video);
+ // } else if (this.stream.outboundStreamOpts.publisherProperties.mirror && !this.stream.isSendScreen()) {
+ // // If the video is now set to mirror and is not screen share
+ // this.mirrorVideo(video);
+ // }
+ // }
+ }
+
+ /**
+ * @hidden
+ */
+ removeAllVideos(): void {
+ console.warn("'removeAllVideos' method is not ready yet");
+ 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.forEach((streamManagerVideo) => {
+ // // Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
+ // if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
+ // streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
+ // }
+ // streamManagerVideo.canplayListenerAdded = false;
+ // if (!!streamManagerVideo.targetElement) {
+ // // Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
+ // // and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
+ // streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
+ // this.ee.emitEvent('videoElementDestroyed', [
+ // new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')
+ // ]);
+ // }
+ // // Remove srcObject from the video
+ // this.removeSrcObject(streamManagerVideo);
+ // // Remove from collection of videos every video managed by OpenVidu Browser
+ // this.videos.filter((v) => !v.targetElement);
+ // });
+ }
+
+ /**
+ * @hidden
+ */
+ disassociateVideo(video: HTMLVideoElement): boolean {
+ // throw new Error("'disassociateVideo' method is not ready yet");
+ let disassociated = false;
+ for (let i = 0; i < this.videos.length; i++) {
+ if (this.videos[i].video === video) {
+ this.videos[i].video.removeEventListener('canplay', this.canPlayListener);
+ this.videos.splice(i, 1);
+ disassociated = true;
+ logger.info('Video element disassociated from ', this);
+ break;
+ }
+ }
+ return disassociated;
+ }
+
+ /**
+ * @hidden
+ */
+ addPlayEventToFirstVideo() {
+ console.warn("'addPlayEventToFirstVideo' method is not ready yet");
+ if (!!this.videos[0] && !!this.videos[0].video && !this.videos[0].canplayListenerAdded) {
+ this.activateStreamPlayingEventExceptionTimeout();
+ this.videos[0].video.addEventListener('canplay', this.canPlayListener);
+ this.videos[0].canplayListenerAdded = true;
+ }
+ }
+
+ /**
+ * @hidden
+ */
+ updateMediaStream(mediaStream: MediaStream) {
+ throw new Error("'updateMediaStream' method is not ready yet");
+ this.videos.forEach((streamManagerVideo) => {
+ streamManagerVideo.video.srcObject = mediaStream;
+ if (platform.isIonicIos()) {
+ // iOS Ionic. LIMITATION: must reinsert the video in the DOM for
+ // the media stream to be updated
+ const vParent = streamManagerVideo.video.parentElement;
+ const newVideo = streamManagerVideo.video;
+ vParent!!.replaceChild(newVideo, streamManagerVideo.video);
+ streamManagerVideo.video = newVideo;
+ }
+ });
+ }
+
+ /**
+ * @hidden
+ */
+ emitEvent(type: string, eventArray: any[]): void {
+ this.ee.emitEvent(type, eventArray);
+ }
+
+ /**
+ * @hidden
+ */
+ createVideo(): HTMLVideoElement {
+ throw new Error("'createVideo' method is not ready yet");
+ return document.createElement('video');
+ }
+
+ /**
+ * @hidden
+ */
+ removeSrcObject(streamManagerVideo: StreamManagerVideo) {
+ throw new Error("'removeSrcObject' method is not ready yet");
+ streamManagerVideo.video.srcObject = null;
+ this.deactivateStreamPlayingEventExceptionTimeout();
+ }
+
+ /**
+ * @hidden
+ */
+ abstract replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void;
+
+ /* Private methods */
+
+ protected pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) {
+ // throw new Error("'pushNewStreamManagerVideo' method is not ready yet");
+ this.videos.push(streamManagerVideo);
+ this.addPlayEventToFirstVideo();
+ if (this.stream.session.streamManagers.indexOf(this) === -1) {
+ this.stream.session.streamManagers.push(this);
+ this.id;
+ }
+ }
+
+ private mirrorVideo(video: HTMLVideoElement): void {
+ throw new Error("'mirrorVideo' method is not ready yet");
+ if (!platform.isIonicIos()) {
+ video.style.transform = 'rotateY(180deg)';
+ video.style.webkitTransform = 'rotateY(180deg)';
+ }
+ }
+
+ private removeMirrorVideo(video: HTMLVideoElement): void {
+ throw new Error("'removeMirrorVideo' method is not ready yet");
+ video.style.transform = 'unset';
+ video.style.webkitTransform = 'unset';
+ }
+
+ private isMirroredVideo(video: HTMLVideoElement): boolean {
+ throw new Error("'isMirroredVideo' method is not ready yet");
+ return video.style.transform === 'rotateY(180deg)' || video.style.webkitTransform === 'rotateY(180deg)';
+ }
+
+ // TODO Necesario?
+ private activateStreamPlayingEventExceptionTimeout() {
+ if (!this.remote) {
+ // ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers
+ return;
+ }
+ if (this.streamPlayingEventExceptionTimeout != null) {
+ // The timeout is already activated
+ return;
+ }
+ // Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
+ const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
+ this.streamPlayingEventExceptionTimeout = setTimeout(() => {
+ const msg =
+ 'StreamManager of Stream ' +
+ this.stream.streamId +
+ ' (' +
+ (this.remote ? 'Subscriber' : 'Publisher') +
+ ') did not trigger "streamPlaying" event in ' +
+ msTimeout +
+ ' ms';
+ logger.warn(msg);
+ this.stream.session.emitEvent('exception', [
+ new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (this) as Subscriber, msg)
+ ]);
+ delete this.streamPlayingEventExceptionTimeout;
+ }, msTimeout);
+ }
+
+ //TODO Necesario?
+ private deactivateStreamPlayingEventExceptionTimeout() {
+ // throw new Error("'deactivateStreamPlayingEventExceptionTimeout' method is not ready yet");
+ clearTimeout(this.streamPlayingEventExceptionTimeout as any);
+ delete this.streamPlayingEventExceptionTimeout;
+ }
+}
diff --git a/openvidu-browser/openvidu-browser.code-workspace b/openvidu-browser/openvidu-browser.code-workspace
new file mode 100644
index 00000000000..7e4a0117786
--- /dev/null
+++ b/openvidu-browser/openvidu-browser.code-workspace
@@ -0,0 +1,11 @@
+{
+ "folders": [
+ {
+ "path": "."
+ },
+ {
+ "path": "../server-client-adapter"
+ }
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/openvidu-browser/src/OpenVidu/Connection.ts b/openvidu-browser/src/OpenVidu/Connection.ts
index 4e17d35ef2d..32ba4532b98 100644
--- a/openvidu-browser/src/OpenVidu/Connection.ts
+++ b/openvidu-browser/src/OpenVidu/Connection.ts
@@ -1,226 +1,107 @@
-/*
- * (C) Copyright 2017-2022 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 { Session } from './Session';
+import { LocalParticipant, RemoteParticipant, RemoteTrackPublication, Track } from 'livekit-client';
import { Stream } from './Stream';
-import { LocalConnectionOptions } from '../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
-import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
-import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
-import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
-import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
-
-/**
- * @hidden
- */
-const logger: OpenViduLogger = OpenViduLogger.getInstance();
+import { Session } from './Session';
+import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
+import { v4 as uuidv4 } from 'uuid';
/**
- * Represents each one of the user's connection to the session (the local one and other user's connections).
- * Therefore each {@link Session} and {@link Stream} object has an attribute of type Connection
+ * Livekit Participant
*/
export class Connection {
- /**
- * Unique identifier of the connection
- */
- connectionId: string;
-
- /**
- * Time when this connection was created in OpenVidu Server (UTC milliseconds)
- */
- creationTime: number;
-
- /**
- * Data associated to this connection (and therefore to certain user). This is an important field:
- * it allows you to broadcast all the information you want for each user (a username, for example)
- */
- data: string;
-
- /**
- * Role of the connection.
- * - `SUBSCRIBER`: can subscribe to published Streams of other users by calling {@link Session.subscribe}
- * - `PUBLISHER`: SUBSCRIBER permissions + can publish their own Streams by calling {@link Session.publish}
- * - `MODERATOR`: SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection by call {@link Session.forceUnpublish} and {@link Session.forceDisconnect}
- *
- * **Only defined for the local connection. In remote connections will be `undefined`**
- */
- role: string;
-
- /**
- * Whether the streams published by this Connection will be recorded or not. This only affects [INDIVIDUAL recording](/en/stable/advanced-features/recording/#individual-recording-selection) PRO
- *
- * **Only defined for the local connection. In remote connections will be `undefined`**
- */
- record: boolean;
+ participant: LocalParticipant | RemoteParticipant;
/**
* @hidden
*/
- stream?: Stream;
+ stream: Stream;
/**
* @hidden
*/
- localOptions: LocalConnectionOptions | undefined;
+ session: Session;
- /**
- * @hidden
- */
- remoteOptions: RemoteConnectionOptions | undefined;
-
- /**
- * @hidden
- */
- disposed = false;
-
- /**
- * @hidden
- */
- rpcSessionId: string;
+ get connectionId(): string {
+ return `CON_${this.participant.sid}`;
+ }
- /**
- * @hidden
- */
- constructor(protected session: Session, connectionOptions: LocalConnectionOptions | RemoteConnectionOptions) {
- console.warn("'Connection' class is not ready yet.");
- let msg = "'Connection' created ";
- if (!!(connectionOptions).role) {
- // Connection is local
- this.localOptions = connectionOptions;
- this.connectionId = this.localOptions.id;
- this.creationTime = this.localOptions.createdAt;
- this.data = this.localOptions.metadata;
- // this.rpcSessionId = this.localOptions.sessionId;
- this.role = this.localOptions.role;
- this.record = this.localOptions.record;
- msg += '(local)';
- } else {
- // Connection is remote
- this.remoteOptions = connectionOptions;
- this.connectionId = this.remoteOptions.id;
- this.creationTime = this.remoteOptions.createdAt;
- if (this.remoteOptions.metadata) {
- this.data = this.remoteOptions.metadata;
- }
- if (this.remoteOptions.streams) {
- this.initRemoteStreams(this.remoteOptions.streams);
- }
- msg += "(remote) with 'connectionId' [" + this.remoteOptions.id + ']';
- }
- logger.info(msg);
+ get creationTime(): number {
+ return this.participant.joinedAt?.getTime() ?? -1;
}
- /* Hidden methods */
+ get data(): string {
+ if (!!this.participant.metadata) {
+ return JSON.stringify({});
+ }
- /**
- * @hidden
- */
- sendIceCandidate(candidate: RTCIceCandidate): void {
- throw new Error("'sendIceCandidate' method is not ready yet.");
-
- if (!this.disposed) {
- logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' + this.connectionId, candidate);
-
- this.session.openvidu.sendRequest(
- 'onIceCandidate',
- {
- endpointName: this.connectionId,
- candidate: candidate.candidate,
- sdpMid: candidate.sdpMid,
- sdpMLineIndex: candidate.sdpMLineIndex
- },
- (error, response) => {
- if (error) {
- logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
- this.session.emitEvent('exception', [
- new ExceptionEvent(
- this.session,
- ExceptionEventName.ICE_CANDIDATE_ERROR,
- this.session,
- 'There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side',
- error
- )
- ]);
- }
- }
- );
- } else {
- logger.warn(`Connection ${this.connectionId} disposed when trying to send an ICE candidate. ICE candidate not sent`);
+ const metadata = this.participant.metadata;
+ if (typeof metadata !== 'string') {
+ return JSON.stringify(metadata);
}
+ return metadata;
}
- /**
- * @hidden
- */
- initRemoteStreams(options: StreamOptionsServer[]): void {
-
- console.warn("'initRemoteStreams' method is not ready yet.");
-
- // This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
- // this.stream should also be replaced by a collection of streams to support multiple streams per Connection
- options.forEach((opts) => {
- const streamOptions: InboundStreamOptions = {
- id: opts.id,
- createdAt: opts.createdAt,
- connection: this,
- hasAudio: opts.hasAudio,
- hasVideo: opts.hasVideo,
- audioActive: opts.audioActive,
- videoActive: opts.videoActive,
- typeOfVideo: opts.typeOfVideo,
- frameRate: opts.frameRate,
- videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined,
- filter: !!opts.filter ? opts.filter : undefined
- };
- const stream = new Stream(this.session, streamOptions);
-
- this.addStream(stream);
- });
-
- logger.info(
- "Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ',
- this.stream!.inboundStreamOpts
- );
+ get role(): string {
+ return this.participant.permissions?.canPublish ? 'PUBLISHER' : 'SUBSCRIBER';
}
- /**
- * @hidden
- */
- addStream(stream: Stream): void {
- stream.connection = this;
- this.stream = stream;
+ get record(): boolean {
+ return false;
}
- /**
- * @hidden
- */
- removeStream(): void {
+ constructor(session: Session, participant: LocalParticipant | RemoteParticipant) {
+ let msg = "'Connection' created ";
+ this.participant = participant;
+ // if (this.participant instanceof LocalParticipant) {
+ this.session = session;
+ // }
+
+ if (this.participant instanceof RemoteParticipant) {
+ msg += "(remote) with 'connectionId' [" + this.connectionId + ']';
+ let videoStreamTrack!: RemoteTrackPublication;
+ let audioStreamTrack!: RemoteTrackPublication;
+
+ this.participant.tracks.forEach((track) => {
+ if (track.kind === 'video') {
+ videoStreamTrack = track;
+ } else if (track.kind === 'audio') {
+ audioStreamTrack = track;
+ }
+ });
+ if (videoStreamTrack || audioStreamTrack) {
+ const videoConstraints = videoStreamTrack.dimensions;
+ const videoDimensions: { width: number; height: number } = videoConstraints
+ ? { width: videoConstraints.width, height: videoConstraints.height }
+ : { width: 0, height: 0 };
+
+ const createdAt = participant['participantInfo']
+ ? Number(BigInt(participant['participantInfo'].joinedAt)) * 1000
+ : new Date().getTime();
+
+ const streamOptions: InboundStreamOptions = {
+ id: `STR_${uuidv4()}`,
+ createdAt,
+ connection: this,
+ hasAudio: this.participant.isMicrophoneEnabled,
+ hasVideo: this.participant.isCameraEnabled,
+ audioActive: this.participant.isMicrophoneEnabled && !audioStreamTrack.audioTrack?.isMuted,
+ videoActive: this.participant.isCameraEnabled && !videoStreamTrack.videoTrack?.isMuted,
+ typeOfVideo: videoStreamTrack.source === Track.Source.ScreenShare ? TypeOfVideo.SCREEN : TypeOfVideo.CAMERA,
+ frameRate: Number(videoStreamTrack.videoTrack?.mediaStreamTrack.getConstraints().frameRate?.toString()) || 30,
+ videoDimensions: videoDimensions,
+ filter: undefined
+ };
+ const stream = new Stream(this.session, streamOptions);
+ stream.connection = this;
+ this.stream = stream;
+ }
+ }
- // throw new Error("'removeStream' method is not ready yet.");
- delete this.stream;
+ console.log(msg);
}
- /**
- * @hidden
- */
- dispose(): void {
-
- // throw new Error("'dispose' method is not ready yet.");
- this.disposed = true;
- this.removeStream();
+ updateStreamId(streamId: string) {
+ this.stream.streamId = streamId;
+ this.stream.inboundStreamOpts.id = streamId;
}
}
diff --git a/openvidu-browser/src/OpenVidu/Filter.ts b/openvidu-browser/src/OpenVidu/Filter.ts
index 38f293a52a6..feb94a483f8 100644
--- a/openvidu-browser/src/OpenVidu/Filter.ts
+++ b/openvidu-browser/src/OpenVidu/Filter.ts
@@ -90,116 +90,116 @@ export class Filter {
execMethod(method: string, params: Object): Promise {
throw new Error("'execMethod' method is not ready yet");
- return new Promise((resolve, reject) => {
- logger.info('Executing filter method to stream ' + this.stream.streamId);
+ // return new Promise((resolve, reject) => {
+ // logger.info('Executing filter method to stream ' + this.stream.streamId);
- let finalParams;
+ // let finalParams;
- const successExecMethod = (triggerEvent) => {
- logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
- const oldValue = (Object).assign({}, this.stream.filter);
- this.stream.filter!.lastExecMethod = { method, params: finalParams };
- if (triggerEvent) {
- this.stream.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(
- this.stream.session,
- this.stream,
- 'filter',
- this.stream.filter!,
- oldValue,
- 'execFilterMethod'
- )
- ]);
- this.stream.streamManager.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(
- this.stream.streamManager,
- this.stream,
- 'filter',
- this.stream.filter!,
- oldValue,
- 'execFilterMethod'
- )
- ]);
- }
- return resolve();
- };
+ // const successExecMethod = (triggerEvent) => {
+ // logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
+ // const oldValue = (Object).assign({}, this.stream.filter);
+ // this.stream.filter!.lastExecMethod = { method, params: finalParams };
+ // if (triggerEvent) {
+ // this.stream.session.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(
+ // this.stream.session,
+ // this.stream,
+ // 'filter',
+ // this.stream.filter!,
+ // oldValue,
+ // 'execFilterMethod'
+ // )
+ // ]);
+ // this.stream.streamManager.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(
+ // this.stream.streamManager,
+ // this.stream,
+ // 'filter',
+ // this.stream.filter!,
+ // oldValue,
+ // 'execFilterMethod'
+ // )
+ // ]);
+ // }
+ // return resolve();
+ // };
- if (this.type.startsWith('VB:')) {
- if (typeof params === 'string') {
- try {
- params = JSON.parse(params);
- } catch (error) {
- return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'Wrong params syntax: ' + error));
- }
- }
+ // if (this.type.startsWith('VB:')) {
+ // if (typeof params === 'string') {
+ // try {
+ // params = JSON.parse(params);
+ // } catch (error) {
+ // return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'Wrong params syntax: ' + error));
+ // }
+ // }
- finalParams = params;
+ // finalParams = params;
- if (method === 'update') {
- if (!this.stream.virtualBackgroundSinkElements?.VB) {
- return reject(
- new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')
- );
- } else {
- this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
- .then(() => successExecMethod(false))
- .catch((error) => {
- if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
- return reject(new OpenViduError(error.name, error.message));
- } else {
- return reject(
- new OpenViduError(
- OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
- 'Error updating values on Virtual Background filter: ' + error
- )
- );
- }
- });
- }
- } else {
- return reject(
- new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)
- );
- }
- } else {
- let stringParams;
- if (typeof params !== 'string') {
- try {
- stringParams = JSON.stringify(params);
- } catch (error) {
- const errorMsg = "'params' property must be a JSON formatted object";
- logger.error(errorMsg);
- return reject(errorMsg);
- }
- } else {
- stringParams = params;
- }
+ // if (method === 'update') {
+ // if (!this.stream.virtualBackgroundSinkElements?.VB) {
+ // return reject(
+ // new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')
+ // );
+ // } else {
+ // this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
+ // .then(() => successExecMethod(false))
+ // .catch((error) => {
+ // if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
+ // return reject(new OpenViduError(error.name, error.message));
+ // } else {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
+ // 'Error updating values on Virtual Background filter: ' + error
+ // )
+ // );
+ // }
+ // });
+ // }
+ // } else {
+ // return reject(
+ // new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)
+ // );
+ // }
+ // } else {
+ // let stringParams;
+ // if (typeof params !== 'string') {
+ // try {
+ // stringParams = JSON.stringify(params);
+ // } catch (error) {
+ // const errorMsg = "'params' property must be a JSON formatted object";
+ // logger.error(errorMsg);
+ // return reject(errorMsg);
+ // }
+ // } else {
+ // stringParams = params;
+ // }
- finalParams = stringParams;
+ // finalParams = stringParams;
- this.stream.session.openvidu.sendRequest(
- 'execFilterMethod',
- { streamId: this.stream.streamId, method, params: stringParams },
- (error, response) => {
- if (error) {
- logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
- if (error.code === 401) {
- return reject(
- new OpenViduError(
- OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
- "You don't have permissions to execute a filter method"
- )
- );
- } else {
- return reject(error);
- }
- } else {
- return successExecMethod(true);
- }
- }
- );
- }
- });
+ // this.stream.session.openvidu.sendRequest(
+ // 'execFilterMethod',
+ // { streamId: this.stream.streamId, method, params: stringParams },
+ // (error, response) => {
+ // if (error) {
+ // logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
+ // if (error.code === 401) {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
+ // "You don't have permissions to execute a filter method"
+ // )
+ // );
+ // } else {
+ // return reject(error);
+ // }
+ // } else {
+ // return successExecMethod(true);
+ // }
+ // }
+ // );
+ // }
+ // });
}
/**
@@ -213,37 +213,37 @@ export class Filter {
addEventListener(eventType: string, handler: (event: FilterEvent) => void): Promise {
throw new Error("'addEventListener' method is not ready yet");
- return new Promise((resolve, reject) => {
- logger.info('Adding filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
- this.stream.session.openvidu.sendRequest(
- 'addFilterEventListener',
- { streamId: this.stream.streamId, eventType },
- (error, response) => {
- if (error) {
- logger.error(
- 'Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
- error
- );
- if (error.code === 401) {
- return reject(
- new OpenViduError(
- OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
- "You don't have permissions to add a filter event listener"
- )
- );
- } else {
- return reject(error);
- }
- } else {
- this.handlers.set(eventType, handler);
- logger.info(
- 'Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId
- );
- return resolve();
- }
- }
- );
- });
+ // return new Promise((resolve, reject) => {
+ // logger.info('Adding filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
+ // this.stream.session.openvidu.sendRequest(
+ // 'addFilterEventListener',
+ // { streamId: this.stream.streamId, eventType },
+ // (error, response) => {
+ // if (error) {
+ // logger.error(
+ // 'Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
+ // error
+ // );
+ // if (error.code === 401) {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
+ // "You don't have permissions to add a filter event listener"
+ // )
+ // );
+ // } else {
+ // return reject(error);
+ // }
+ // } else {
+ // this.handlers.set(eventType, handler);
+ // logger.info(
+ // 'Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId
+ // );
+ // return resolve();
+ // }
+ // }
+ // );
+ // });
}
/**
@@ -256,36 +256,36 @@ export class Filter {
removeEventListener(eventType: string): Promise {
throw new Error("'removeEventListener' method is not ready yet");
- return new Promise((resolve, reject) => {
- logger.info('Removing filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
- this.stream.session.openvidu.sendRequest(
- 'removeFilterEventListener',
- { streamId: this.stream.streamId, eventType },
- (error, response) => {
- if (error) {
- logger.error(
- 'Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
- error
- );
- if (error.code === 401) {
- return reject(
- new OpenViduError(
- OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
- "You don't have permissions to add a filter event listener"
- )
- );
- } else {
- return reject(error);
- }
- } else {
- this.handlers.delete(eventType);
- logger.info(
- 'Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId
- );
- return resolve();
- }
- }
- );
- });
+ // return new Promise((resolve, reject) => {
+ // logger.info('Removing filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
+ // this.stream.session.openvidu.sendRequest(
+ // 'removeFilterEventListener',
+ // { streamId: this.stream.streamId, eventType },
+ // (error, response) => {
+ // if (error) {
+ // logger.error(
+ // 'Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
+ // error
+ // );
+ // if (error.code === 401) {
+ // return reject(
+ // new OpenViduError(
+ // OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
+ // "You don't have permissions to add a filter event listener"
+ // )
+ // );
+ // } else {
+ // return reject(error);
+ // }
+ // } else {
+ // this.handlers.delete(eventType);
+ // logger.info(
+ // 'Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId
+ // );
+ // return resolve();
+ // }
+ // }
+ // );
+ // });
}
}
diff --git a/openvidu-browser/src/OpenVidu/LocalRecorder.ts b/openvidu-browser/src/OpenVidu/LocalRecorder.ts
index 0cc609d7961..c4dde271187 100644
--- a/openvidu-browser/src/OpenVidu/LocalRecorder.ts
+++ b/openvidu-browser/src/OpenVidu/LocalRecorder.ts
@@ -82,71 +82,71 @@ export class LocalRecorder {
record(options?: any): Promise {
throw new Error("'record' is not ready yet");
- return new Promise((resolve, reject) => {
- try {
- if (typeof options === 'string' || options instanceof String) {
- return reject(
- `When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`
- );
- }
- if (typeof MediaRecorder === 'undefined') {
- logger.error(
- 'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
- );
- throw Error(
- 'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
- );
- }
- if (this.state !== LocalRecorderState.READY) {
- throw Error(
- "'LocalRecord.record()' needs 'LocalRecord.state' to be 'READY' (current value: '" +
- this.state +
- "'). Call 'LocalRecorder.clean()' or init a new LocalRecorder before"
- );
- }
- logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
-
- if (!options) {
- options = { mimeType: 'video/webm' };
- } else if (!options.mimeType) {
- options.mimeType = 'video/webm';
- }
+ // return new Promise((resolve, reject) => {
+ // try {
+ // if (typeof options === 'string' || options instanceof String) {
+ // return reject(
+ // `When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`
+ // );
+ // }
+ // if (typeof MediaRecorder === 'undefined') {
+ // logger.error(
+ // 'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
+ // );
+ // throw Error(
+ // 'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
+ // );
+ // }
+ // if (this.state !== LocalRecorderState.READY) {
+ // throw Error(
+ // "'LocalRecord.record()' needs 'LocalRecord.state' to be 'READY' (current value: '" +
+ // this.state +
+ // "'). Call 'LocalRecorder.clean()' or init a new LocalRecorder before"
+ // );
+ // }
+ // logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
+
+ // if (!options) {
+ // options = { mimeType: 'video/webm' };
+ // } else if (!options.mimeType) {
+ // options.mimeType = 'video/webm';
+ // }
+
+ // this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
+ // this.mediaRecorder.start();
+ // } catch (err) {
+ // return reject(err);
+ // }
- this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
- this.mediaRecorder.start();
- } catch (err) {
- return reject(err);
- }
+ // this.mediaRecorder.ondataavailable = (e) => {
+ // if (e.data.size > 0) {
+ // this.chunks.push(e.data);
+ // }
+ // };
- this.mediaRecorder.ondataavailable = (e) => {
- if (e.data.size > 0) {
- this.chunks.push(e.data);
- }
- };
+ // this.mediaRecorder.onerror = (e) => {
+ // logger.error('MediaRecorder error: ', e);
+ // };
- this.mediaRecorder.onerror = (e) => {
- logger.error('MediaRecorder error: ', e);
- };
+ // this.mediaRecorder.onstart = () => {
+ // logger.log('MediaRecorder started (state=' + this.mediaRecorder.state + ')');
+ // };
- this.mediaRecorder.onstart = () => {
- logger.log('MediaRecorder started (state=' + this.mediaRecorder.state + ')');
- };
+ // this.mediaRecorder.onstop = () => {
+ // this.onStopDefault();
+ // };
- this.mediaRecorder.onstop = () => {
- this.onStopDefault();
- };
+ // this.mediaRecorder.onpause = () => {
+ // logger.log('MediaRecorder paused (state=' + this.mediaRecorder.state + ')');
+ // };
- this.mediaRecorder.onpause = () => {
- logger.log('MediaRecorder paused (state=' + this.mediaRecorder.state + ')');
- };
+ // this.mediaRecorder.onresume = () => {
+ // logger.log('MediaRecorder resumed (state=' + this.mediaRecorder.state + ')');
+ // };
- this.mediaRecorder.onresume = () => {
- logger.log('MediaRecorder resumed (state=' + this.mediaRecorder.state + ')');
- };
-
- this.state = LocalRecorderState.RECORDING;
- return resolve();
- });
+ // this.state = LocalRecorderState.RECORDING;
+ // return resolve();
+ // });
}
/**
diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts
index 5d58f0ff5fa..34c44745410 100644
--- a/openvidu-browser/src/OpenVidu/OpenVidu.ts
+++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts
@@ -1,109 +1,52 @@
-/*
- * (C) Copyright 2017-2022 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 { LocalRecorder } from './LocalRecorder';
-import { Publisher } from './Publisher';
-import { Session } from './Session';
-import { Stream } from './Stream';
-import { SessionDisconnectedEvent } from '../OpenViduInternal/Events/SessionDisconnectedEvent';
-import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
+import { AudioCaptureOptions, CreateLocalTracksOptions, LocalTrack, Room, VideoCaptureOptions, createLocalTracks } from 'livekit-client';
import { Device } from '../OpenViduInternal/Interfaces/Public/Device';
-import { OpenViduAdvancedConfiguration } from '../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
-import { CustomMediaStreamConstraints } from '../OpenViduInternal/Interfaces/Private/CustomMediaStreamConstraints';
-import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
-import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
-import { StreamPropertyChangedEventReason, ChangedPropertyType } from '../OpenViduInternal/Events/Types/Types';
-
-import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
+import { Publisher } from './Publisher';
+import { Session } from './Session';
import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
-import { OpenViduLoggerConfiguration } from '../OpenViduInternal/Logger/OpenViduLoggerConfiguration';
-/**
- * @hidden
- */
-import EventEmitter = require('wolfy87-eventemitter');
-/**
- * @hidden
- */
-import RpcBuilder = require('../OpenViduInternal/KurentoUtils/kurento-jsonrpc');
-import { AudioCaptureOptions, CreateLocalTracksOptions, LocalTrack, Room, VideoCaptureOptions, createLocalTracks } from 'livekit-client';
+import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
+import { CustomMediaStreamConstraints } from '../OpenViduInternal/Interfaces/Private/CustomMediaStreamConstraints';
+import { OpenViduAdvancedConfiguration } from '../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
+import { Stream } from './Stream';
/**
* @hidden
*/
const packageJson = require('../../package.json');
+
/**
* @hidden
*/
-declare var cordova: any;
+let platform: PlatformUtils = PlatformUtils.getInstance();
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
-/**
- * @hidden
- */
-let platform: PlatformUtils;
-
/**
* Entrypoint of OpenVidu Browser library.
* Use it to initialize objects of type {@link Session}, {@link Publisher} and {@link LocalRecorder}
*/
export class OpenVidu {
- private jsonRpcClient: any;
- private masterNodeHasCrashed = false;
-
- /**
- * @hidden
- */
- session: Session;
-
/**
* @hidden
*/
- publishers: Publisher[] = [];
- /**
- * @hidden
- */
- wsUri: string;
- /**
- * @hidden
- */
- httpUri: string;
- /**
- * @hidden
- */
- secret = '';
+ libraryVersion: string;
/**
* @hidden
*/
- recorder = false;
+ session: Session;
/**
* @hidden
*/
- stt = false;
+ publishers: Publisher[] = [];
/**
* @hidden
*/
- iceServers: RTCIceServer[];
+ advancedConfiguration: OpenViduAdvancedConfiguration = {};
/**
* @hidden
*/
@@ -112,46 +55,6 @@ export class OpenVidu {
* @hidden
*/
finalUserId: string;
- /**
- * @hidden
- */
- mediaServer: string;
- /**
- * @hidden
- */
- videoSimulcast: boolean;
- /**
- * @hidden
- */
- life: number = -1;
- /**
- * @hidden
- */
- advancedConfiguration: OpenViduAdvancedConfiguration = {};
- /**
- * @hidden
- */
- webrtcStatsInterval: number = -1;
- /**
- * @hidden
- */
- sendBrowserLogs: OpenViduLoggerConfiguration = OpenViduLoggerConfiguration.disabled;
- /**
- * @hidden
- */
- isAtLeastPro: boolean = false;
- /**
- * @hidden
- */
- isEnterprise: boolean = false;
- /**
- * @hidden
- */
- libraryVersion: string;
- /**
- * @hidden
- */
- ee = new EventEmitter();
constructor() {
console.warn("'OpenVidu' class is not ready yet.");
@@ -165,37 +68,19 @@ export class OpenVidu {
'font-weight: bold; font-size: 15px;'
);
- platform = PlatformUtils.getInstance();
this.libraryVersion = packageJson.version;
logger.info('OpenVidu initialized');
logger.info('Platform detected: ' + platform.getDescription());
logger.info('openvidu-browser version: ' + this.libraryVersion);
-
- // if (platform.isMobileDevice() || platform.isReactNative()) {
- // // Listen to orientationchange only on mobile devices
- // this.onOrientationChanged(() => {
- // this.publishers.forEach((publisher) => {
- // if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
- // this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
- // }
- // });
- // });
- // }
}
/**
* Returns new session
*/
initSession(): Session {
- this.session = new Session(this);
- return this.session;
+ return (this.session = new Session(this));
}
- /**
- *
- * !WARNING: This method now requires a Promise to be returned.
- *
- */
async initPublisher(targetElement: string | HTMLElement | undefined): Promise;
async initPublisher(targetElement: string | HTMLElement | undefined, properties: PublisherProperties): Promise;
async initPublisher(
@@ -207,9 +92,8 @@ export class OpenVidu {
properties: PublisherProperties,
completionHandler: (error: Error | undefined) => void
): Promise;
-
/**
- * Returns a new Promise
+ * Returns a new publisher
*
* #### Events dispatched
*
@@ -229,7 +113,6 @@ export class OpenVidu {
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
*/
async initPublisher(targetElement: string | HTMLElement | undefined, param2?, param3?): Promise {
-
let ovPublisherProperties: PublisherProperties = {};
// Check if param2 exists and is not a function
@@ -263,52 +146,7 @@ export class OpenVidu {
return publisher;
}
- // let properties: PublisherProperties;
-
- // if (!!param2 && typeof param2 !== 'function') {
- // // Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
-
- // properties = param2;
-
- // properties = {
- // audioSource: typeof properties.audioSource !== 'undefined' ? properties.audioSource : undefined,
- // frameRate:
- // typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
- // ? undefined
- // : typeof properties.frameRate !== 'undefined'
- // ? properties.frameRate
- // : undefined,
- // insertMode:
- // typeof properties.insertMode !== 'undefined'
- // ? typeof properties.insertMode === 'string'
- // ? VideoInsertMode[properties.insertMode]
- // : properties.insertMode
- // : VideoInsertMode.APPEND,
- // mirror: typeof properties.mirror !== 'undefined' ? properties.mirror : true,
- // publishAudio: typeof properties.publishAudio !== 'undefined' ? properties.publishAudio : true,
- // publishVideo: typeof properties.publishVideo !== 'undefined' ? properties.publishVideo : true,
- // resolution:
- // typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack
- // ? undefined
- // : typeof properties.resolution !== 'undefined'
- // ? properties.resolution
- // : '640x480',
- // videoSource: typeof properties.videoSource !== 'undefined' ? properties.videoSource : undefined,
- // videoSimulcast: properties.videoSimulcast,
- // filter: properties.filter
- // };
- // } else {
- // // Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
-
- // properties = {
- // insertMode: VideoInsertMode.APPEND,
- // mirror: true,
- // publishAudio: true,
- // publishVideo: true,
- // resolution: '640x480'
- // };
- // }
-
+ // TODO ...
}
/**
@@ -325,80 +163,6 @@ export class OpenVidu {
} else {
return this.initPublisher(targetElement);
}
- // throw new Error("'initPublisherAsync' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // let publisher: Publisher;
-
- // const callback = (error: Error) => {
- // if (!!error) {
- // return reject(error);
- // } else {
- // return resolve(publisher);
- // }
- // };
-
- // if (!!properties) {
- // return this.initPublisher(targetElement, properties);
- // } else {
- // return this.initPublisher(targetElement);
- // }
- // });
- }
-
- /**
- * Returns a new local recorder for recording streams straight away from the browser
- * @param stream Stream to record
- */
- initLocalRecorder(stream: Stream): LocalRecorder {
- throw new Error("'initLocalRecorder' method is not ready yet.");
-
- return new LocalRecorder(stream);
- }
-
- /**
- * Checks if the browser supports OpenVidu
- * @returns 1 if the browser supports OpenVidu, 0 otherwise
- */
- checkSystemRequirements(): boolean {
- // throw new Error("'checkSystemRequirements' method is not ready yet.");
- // Specific iOS platform support (iPhone, iPad)
- if (platform.isIPhoneOrIPad()) {
- return (
- platform.isIOSWithSafari() ||
- platform.isChromeMobileBrowser() ||
- platform.isFirefoxMobileBrowser() ||
- platform.isOperaMobileBrowser() ||
- platform.isEdgeMobileBrowser() ||
- platform.isIonicIos() // Ionic apps for iOS
- );
- }
-
- // General platform support for web clients (Desktop, Mobile)
- return (
- platform.isChromeBrowser() ||
- platform.isChromeMobileBrowser() ||
- platform.isFirefoxBrowser() ||
- platform.isFirefoxMobileBrowser() ||
- platform.isOperaBrowser() ||
- platform.isOperaMobileBrowser() ||
- platform.isEdgeBrowser() ||
- platform.isEdgeMobileBrowser() ||
- platform.isSamsungBrowser() ||
- platform.isSafariBrowser() ||
- platform.isAndroidBrowser() || // Android WebView & Ionic apps for Android
- platform.isElectron() ||
- platform.isNodeJs() ||
- // TODO: remove when updating platform detection library
- platform.isMotorolaEdgeDevice()
- );
- }
-
- /**
- * Checks if the browser supports screen-sharing. Desktop Chrome, Firefox and Opera support screen-sharing
- * @returns 1 if the browser supports screen-sharing, 0 otherwise
- */
- checkScreenSharingCapabilities(): boolean {
- return platform.canScreenShare();
}
/**
@@ -418,106 +182,6 @@ export class OpenVidu {
});
console.log('getDevices', deviceList);
return deviceList;
- // throw new Error("'getDevices' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // navigator.mediaDevices
- // .enumerateDevices()
- // .then((deviceInfos) => {
- // const devices: Device[] = [];
-
- // // Ionic Android devices
- // if (platform.isIonicAndroid() && typeof cordova != 'undefined' && cordova?.plugins?.EnumerateDevicesPlugin) {
- // cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => {
- // let pluginAudioDevices: Device[] = [];
- // let videoDevices: Device[] = [];
- // let audioDevices: Device[] = [];
- // pluginAudioDevices = pluginDevices.filter((device: Device) => device.kind === 'audioinput');
- // videoDevices = deviceInfos.filter((device: MediaDeviceInfo) => device.kind === 'videoinput') as any;
- // audioDevices = deviceInfos.filter((device: MediaDeviceInfo) => device.kind === 'audioinput') as any;
- // videoDevices.forEach((deviceInfo, index) => {
- // if (!deviceInfo.label) {
- // let label = '';
- // if (index === 0) {
- // label = 'Front Camera';
- // } else if (index === 1) {
- // label = 'Back Camera';
- // } else {
- // label = 'Unknown Camera';
- // }
- // devices.push({
- // kind: deviceInfo.kind,
- // deviceId: deviceInfo.deviceId,
- // label: label
- // });
- // } else {
- // devices.push({
- // kind: deviceInfo.kind,
- // deviceId: deviceInfo.deviceId,
- // label: deviceInfo.label
- // });
- // }
- // });
- // audioDevices.forEach((deviceInfo, index) => {
- // if (!deviceInfo.label) {
- // let label = '';
- // switch (index) {
- // case 0: // Default Microphone
- // label = 'Default';
- // break;
- // case 1: // Microphone + Speakerphone
- // const defaultMatch = pluginAudioDevices.filter((d) => d.label.includes('Built'))[0];
- // label = defaultMatch ? defaultMatch.label : 'Built-in Microphone';
- // break;
- // case 2: // Headset Microphone
- // const wiredMatch = pluginAudioDevices.filter((d) => d.label.includes('Wired'))[0];
- // if (wiredMatch) {
- // label = wiredMatch.label;
- // } else {
- // label = 'Headset earpiece';
- // }
- // break;
- // case 3:
- // const wirelessMatch = pluginAudioDevices.filter((d) => d.label.includes('Bluetooth'))[0];
- // label = wirelessMatch ? wirelessMatch.label : 'Wireless';
- // break;
- // default:
- // label = 'Unknown Microphone';
- // break;
- // }
- // devices.push({
- // kind: deviceInfo.kind,
- // deviceId: deviceInfo.deviceId,
- // label: label
- // });
- // } else {
- // devices.push({
- // kind: deviceInfo.kind,
- // deviceId: deviceInfo.deviceId,
- // label: deviceInfo.label
- // });
- // }
- // });
- // return resolve(devices);
- // });
- // } else {
- // // Rest of platforms
- // deviceInfos.forEach((deviceInfo) => {
- // if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
- // devices.push({
- // kind: deviceInfo.kind,
- // deviceId: deviceInfo.deviceId,
- // label: deviceInfo.label
- // });
- // }
- // });
- // return resolve(devices);
- // }
- // })
- // .catch((error) => {
- // logger.error('Error getting devices', error);
- // return reject(error);
- // });
- // });
}
/**
@@ -711,147 +375,26 @@ export class OpenVidu {
throw new Error("'enableProdMode' method is not ready yet.");
logger.enableProdMode();
}
- /* tslint:enable:no-empty */
-
- /**
- * Set OpenVidu advanced configuration options. `configuration` is an object of type {@link OpenViduAdvancedConfiguration}. Call this method to override previous values at any moment.
- */
- setAdvancedConfiguration(configuration: OpenViduAdvancedConfiguration): void {
- //TODO: apply this configuration in the room
- this.advancedConfiguration = configuration;
- }
-
- /* Hidden methods */
-
- /**
- * @hidden
- */
- onOrientationChanged(handler): void {
- throw new Error("'onOrientationChanged' method is not ready yet.");
- (globalThis as any).addEventListener('orientationchange', handler);
- }
/**
* @hidden
*/
- sendNewVideoDimensionsIfRequired(
- publisher: Publisher,
- reason: StreamPropertyChangedEventReason,
- WAIT_INTERVAL: number,
- MAX_ATTEMPTS: number
- ) {
- // throw new Error("'sendNewVideoDimensionsIfRequired' method is not ready yet.");
- let attempts = 0;
- const oldWidth = publisher?.stream?.videoDimensions?.width || 0;
- const oldHeight = publisher?.stream?.videoDimensions?.height || 0;
-
- const repeatUntilChangeOrMaxAttempts: NodeJS.Timeout = setInterval(() => {
- attempts++;
- if (attempts > MAX_ATTEMPTS) {
- clearTimeout(repeatUntilChangeOrMaxAttempts);
- }
- publisher.getVideoDimensions().then((newDimensions) => {
- if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
- clearTimeout(repeatUntilChangeOrMaxAttempts);
- this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
- }
- });
- }, WAIT_INTERVAL);
- }
-
- /**
- * @hidden
- */
- sendVideoDimensionsChangedEvent(
- publisher: Publisher,
- reason: StreamPropertyChangedEventReason,
- oldWidth: number,
- oldHeight: number,
- newWidth: number,
- newHeight: number
- ) {
- // throw new Error("'sendVideoDimensionsChangedEvent' method is not ready yet.");
- publisher.stream.videoDimensions = {
- width: newWidth || 0,
- height: newHeight || 0
- };
- // this.sendRequest(
- // 'streamPropertyChanged',
- // {
- // streamId: publisher.stream.streamId,
- // property: 'videoDimensions',
- // newValue: JSON.stringify(publisher.stream.videoDimensions),
- // reason
- // },
- // (error, response) => {
- // if (error) {
- // logger.error("Error sending 'streamPropertyChanged' event", error);
+ addAlreadyProvidedTracks(myConstraints: CustomMediaStreamConstraints, mediaStream: MediaStream, stream?: Stream): MediaStream {
+ throw new Error("'addAlreadyProvidedTracks' method is not ready yet.");
+ // if (!!myConstraints.videoTrack) {
+ // mediaStream.addTrack(myConstraints.videoTrack);
+ // if (!!stream) {
+ // if (!!myConstraints.constraints.video) {
+ // stream.lastVideoTrackConstraints = myConstraints.constraints.video;
// } else {
- this.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(
- this.session,
- publisher.stream,
- 'videoDimensions',
- publisher.stream.videoDimensions,
- { width: oldWidth, height: oldHeight },
- reason
- )
- ]);
- publisher.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(
- publisher,
- publisher.stream,
- 'videoDimensions',
- publisher.stream.videoDimensions,
- { width: oldWidth, height: oldHeight },
- reason
- )
- ]);
- // this.session.sendVideoData(publisher);
+ // stream.lastVideoTrackConstraints = myConstraints.videoTrack.getConstraints();
+ // }
+ // }
// }
+ // if (!!myConstraints.audioTrack) {
+ // mediaStream.addTrack(myConstraints.audioTrack);
// }
- // );
- }
-
- /**
- * @hidden
- */
- sendTrackChangedEvent(publisher: Publisher, oldLabel: string, newLabel: string, propertyType: ChangedPropertyType) {
- throw new Error("'sendTrackChangedEvent' method is not ready yet.");
- const oldValue = { label: oldLabel };
- const newValue = { label: newLabel };
- const reason = 'trackReplaced';
-
- if (publisher.stream.isLocalStreamPublished) {
- this.sendRequest(
- 'streamPropertyChanged',
- {
- streamId: publisher.stream.streamId,
- property: propertyType,
- newValue: newValue,
- reason
- },
- (error, response) => {
- if (error) {
- logger.error("Error sending 'streamPropertyChanged' event", error);
- } else {
- this.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
- ]);
- publisher.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
- ]);
- }
- }
- );
- } else {
- this.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this.session, publisher.stream, propertyType, newValue, oldValue, reason)
- ]);
- publisher.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(publisher, publisher.stream, propertyType, newValue, oldValue, reason)
- ]);
- }
+ // return mediaStream;
}
/**
@@ -943,185 +486,17 @@ export class OpenVidu {
}
/**
- * @hidden
- */
- // startWs(onConnectSucces: (error: Error) => void): void {
- // throw new Error("'startWs' method is not ready yet.");
- // const config = {
- // heartbeat: 5000,
- // ws: {
- // uri: this.wsUri + '?sessionId=' + this.session.sessionId,
- // onconnected: onConnectSucces,
- // ondisconnect: this.disconnectCallback.bind(this),
- // onreconnecting: this.reconnectingCallback.bind(this),
- // onreconnected: this.reconnectedCallback.bind(this),
- // ismasternodecrashed: this.isMasterNodeCrashed.bind(this)
- // },
- // rpc: {
- // requestTimeout: 10000,
- // heartbeatRequestTimeout: 5000,
- // participantJoined: this.session.onParticipantJoined.bind(this.session),
- // participantPublished: this.session.onParticipantPublished.bind(this.session),
- // participantUnpublished: this.session.onParticipantUnpublished.bind(this.session),
- // participantLeft: this.session.onParticipantLeft.bind(this.session),
- // participantEvicted: this.session.onParticipantEvicted.bind(this.session),
- // recordingStarted: this.session.onRecordingStarted.bind(this.session),
- // recordingStopped: this.session.onRecordingStopped.bind(this.session),
- // broadcastStarted: this.session.onBroadcastStarted.bind(this.session),
- // broadcastStopped: this.session.onBroadcastStopped.bind(this.session),
- // sendMessage: this.session.onNewMessage.bind(this.session),
- // streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session),
- // connectionPropertyChanged: this.session.onConnectionPropertyChanged.bind(this.session),
- // networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session),
- // filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
- // iceCandidate: this.session.recvIceCandidate.bind(this.session),
- // mediaError: this.session.onMediaError.bind(this.session),
- // masterNodeCrashedNotification: this.onMasterNodeCrashedNotification.bind(this),
- // forciblyReconnectSubscriber: this.session.onForciblyReconnectSubscriber.bind(this.session),
- // speechToTextMessage: this.session.onSpeechToTextMessage.bind(this.session),
- // speechToTextDisconnected: this.session.onSpeechToTextDisconnected.bind(this.session)
- // }
- // };
- // this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
- // }
-
- /**
- * @hidden
- */
- onMasterNodeCrashedNotification(response): void {
- throw new Error("'onMasterNodeCrashedNotification' method is not ready yet.");
- console.error('Master Node has crashed');
- this.masterNodeHasCrashed = true;
- this.session.onLostConnection('nodeCrashed');
- this.jsonRpcClient.close(4103, 'Master Node has crashed');
- }
-
- /**
- * @hidden
- */
- getWsReadyState(): number {
- throw new Error("'getWsReadyState' method is not ready yet.");
- return this.jsonRpcClient.getReadyState();
- }
-
- /**
- * @hidden
- */
- closeWs(): void {
- throw new Error("'closeWs' method is not ready yet.");
- this.jsonRpcClient.close(4102, 'Connection closed by client');
- }
-
- /**
- * @hidden
- */
- sendRequest(method: string, params: any, callback?): void {
- throw new Error("'sendRequest' method is not ready yet.");
- if (params && params instanceof Function) {
- callback = params;
- params = {};
- }
- logger.debug('Sending request: {method:"' + method + '", params: ' + JSON.stringify(params) + '}');
- this.jsonRpcClient?.send(method, params, callback);
- }
-
- /**
- * @hidden
- */
- getWsUri(): string {
- throw new Error("'getWsUri' method is not ready yet.");
- return this.wsUri;
- }
-
- /**
- * @hidden
- */
- getSecret(): string {
- throw new Error("'getSecret' method is not ready yet.");
- return this.secret;
- }
-
- /**
- * @hidden
- */
- getRecorder(): boolean {
- throw new Error("'getRecorder' method is not ready yet.");
- return this.recorder;
- }
-
- /**
- * @hidden
- */
- getStt(): boolean {
- throw new Error("'getStt' method is not ready yet.");
- return this.stt;
- }
-
- /**
- * @hidden
- */
- generateAudioDeviceError(error, constraints: MediaStreamConstraints): OpenViduError {
- throw new Error("'generateAudioDeviceError' method is not ready yet.");
- if (error.name === 'Error') {
- // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
- error.name = error.constructor.name;
- }
- let errorName, errorMessage: string;
- switch (error.name.toLowerCase()) {
- case 'notfounderror':
- errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
- errorMessage = error.toString();
- return new OpenViduError(errorName, errorMessage);
- case 'notallowederror':
- errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED;
- errorMessage = error.toString();
- return new OpenViduError(errorName, errorMessage);
- case 'overconstrainederror':
- if (error.constraint.toLowerCase() === 'deviceid') {
- errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
- errorMessage =
- "Audio input device with deviceId '" +
- ((constraints.audio).deviceId!!).exact +
- "' not found";
- } else {
- errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
- errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
- }
- return new OpenViduError(errorName, errorMessage);
- case 'notreadableerror':
- errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
- errorMessage = error.toString();
- return new OpenViduError(errorName, errorMessage);
- default:
- return new OpenViduError(OpenViduErrorName.INPUT_AUDIO_DEVICE_GENERIC_ERROR, error.toString());
- }
- }
-
- /**
- * @hidden
+ * Checks if the browser supports screen-sharing. Desktop Chrome, Firefox and Opera support screen-sharing
+ * @returns 1 if the browser supports screen-sharing, 0 otherwise
*/
- addAlreadyProvidedTracks(myConstraints: CustomMediaStreamConstraints, mediaStream: MediaStream, stream?: Stream): MediaStream {
- // throw new Error("'addAlreadyProvidedTracks' method is not ready yet.");
- if (!!myConstraints.videoTrack) {
- mediaStream.addTrack(myConstraints.videoTrack);
- if (!!stream) {
- if (!!myConstraints.constraints.video) {
- stream.lastVideoTrackConstraints = myConstraints.constraints.video;
- } else {
- stream.lastVideoTrackConstraints = myConstraints.videoTrack.getConstraints();
- }
- }
- }
- if (!!myConstraints.audioTrack) {
- mediaStream.addTrack(myConstraints.audioTrack);
- }
- return mediaStream;
+ checkScreenSharingCapabilities(): boolean {
+ return platform.canScreenShare();
}
/**
* @hidden
*/
- protected configureDeviceIdOrScreensharing(
+ private configureDeviceIdOrScreensharing(
myConstraints: CustomMediaStreamConstraints,
publisherProperties: PublisherProperties,
resolve,
@@ -1188,28 +563,28 @@ export class OpenVidu {
logger.error(error);
return reject(error);
} else {
- const extensionId = this.advancedConfiguration
- .screenShareChromeExtension!.split('/')
- .pop()!!
- .trim();
- screenSharing.getChromeExtensionStatus(extensionId, (status) => {
- if (status === 'installed-disabled') {
- const error = new OpenViduError(
- OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
- 'You must enable the screen extension'
- );
- logger.error(error);
- return reject(error);
- }
- if (status === 'not-installed') {
- const error = new OpenViduError(
- OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED,
- this.advancedConfiguration.screenShareChromeExtension
- );
- logger.error(error);
- return reject(error);
- }
- });
+ // const extensionId = this.advancedConfiguration
+ // .screenShareChromeExtension!.split('/')
+ // .pop()!!
+ // .trim();
+ // screenSharing.getChromeExtensionStatus(extensionId, (status) => {
+ // if (status === 'installed-disabled') {
+ // const error = new OpenViduError(
+ // OpenViduErrorName.SCREEN_EXTENSION_DISABLED,
+ // 'You must enable the screen extension'
+ // );
+ // logger.error(error);
+ // return reject(error);
+ // }
+ // if (status === 'not-installed') {
+ // const error = new OpenViduError(
+ // OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED,
+ // this.advancedConfiguration.screenShareChromeExtension
+ // );
+ // logger.error(error);
+ // return reject(error);
+ // }
+ // });
return;
}
} else {
@@ -1278,7 +653,7 @@ export class OpenVidu {
/**
* @hidden
*/
- protected setVideoSource(myConstraints: CustomMediaStreamConstraints, videoSource: string) {
+ private setVideoSource(myConstraints: CustomMediaStreamConstraints, videoSource: string) {
// throw new Error("'setVideoSource' method is not ready yet.");
if (!myConstraints.constraints!.video) {
myConstraints.constraints!.video = {};
@@ -1286,136 +661,6 @@ export class OpenVidu {
(myConstraints.constraints!.video)['deviceId'] = { exact: videoSource };
}
- /* Private methods */
-
- private disconnectCallback(): void {
- throw new Error("'disconnectCallback' method is not ready yet.");
-
- logger.warn('Websocket connection lost');
- if (this.isRoomAvailable()) {
- this.session.onLostConnection('networkDisconnect');
- } else {
- alert('Connection error. Please reload page.');
- }
- }
-
- private reconnectingCallback(): void {
- logger.warn('Websocket connection lost (reconnecting)');
- if (!this.isRoomAvailable()) {
- alert('Connection error. Please reload page.');
- } else {
- this.session.emitEvent('reconnecting', []);
- }
- }
-
- private reconnectWebsocketThroughRpcConnectMethod(rpcSessionId) {
- throw new Error("'reconnectWebsocketThroughRpcConnectMethod' method is not ready yet.");
- // This RPC method allows checking:
- // Single Master: if success, connection recovered
- // if error, no Master Node crashed and life will be -1. onLostConnection with reason networkDisconnect will be triggered
- // Multi Master: if success, connection recovered
- // if error and Master Node crashed notification was already received, nothing must be done
- // if error and Master Node NOT crashed, sessionStatus method must be sent:
- // if life is equal, networkDisconnect
- // if life is greater, nodeCrashed
- this.sendRequest('connect', { sessionId: rpcSessionId, reconnect: true }, (error, response) => {
- if (!!error) {
- if (this.isMasterNodeCrashed()) {
- logger.warn('Master Node has crashed!');
- } else {
- logger.error(error);
-
- const notifyLostConnection = (reason, errorMsg) => {
- logger.warn(errorMsg);
- this.session.onLostConnection(reason);
- this.jsonRpcClient.close(4101, 'Reconnection fault: ' + errorMsg);
- };
-
- const rpcSessionStatus = () => {
- if (this.life === -1) {
- // Single Master
- notifyLostConnection(
- 'networkDisconnect',
- 'WS successfully reconnected but the user was already evicted due to timeout'
- );
- } else {
- // Multi Master
- // This RPC method is only required to find out the reason of the disconnection:
- // whether the client lost its network connection or a Master Node crashed
- this.sendRequest('sessionStatus', { sessionId: this.session.sessionId }, (error, response) => {
- if (error != null) {
- console.error('Error checking session status', error);
- } else {
- if (this.life === response.life) {
- // If the life stored in the client matches the life stored in the server, it means that the client lost its network connection
- notifyLostConnection(
- 'networkDisconnect',
- 'WS successfully reconnected but the user was already evicted due to timeout'
- );
- } else {
- // If the life stored in the client is below the life stored in the server, it means that the Master Node has crashed
- notifyLostConnection(
- 'nodeCrashed',
- 'WS successfully reconnected to OpenVidu Server but your Master Node crashed'
- );
- }
- }
- });
- }
- };
-
- if (error.code === 40007 && error.message === 'reconnection error') {
- // Kurento error: invalid RPC sessionId. This means that the kurento-jsonrpc-server of openvidu-server where kurento-jsonrpc-client
- // is trying to reconnect does not know about this sessionId. This can mean two things:
- // 1) openvidu-browser managed to reconnect after a while, but openvidu-server already evicted the user for not receiving ping.
- // 2) openvidu-server process is a different one because of a node crash.
- // Send a "sessionStatus" method to check the reason
- console.error('Invalid RPC sessionId. Client network disconnection or Master Node crash');
- rpcSessionStatus();
- } else {
- rpcSessionStatus();
- }
- }
- } else {
- this.jsonRpcClient.resetPing();
- this.session.onRecoveredConnection();
- }
- });
- }
-
- private reconnectedCallback(): void {
- throw new Error("'reconnectedCallback' method is not ready yet.");
- logger.warn('Websocket reconnected');
- if (this.isRoomAvailable()) {
- if (!!this.session.connection) {
- this.reconnectWebsocketThroughRpcConnectMethod(this.session.connection.rpcSessionId);
- } else {
- logger.warn('There was no previous connection when running reconnection callback');
- // Make Session object dispatch 'sessionDisconnected' event
- const sessionDisconnectEvent = new SessionDisconnectedEvent(this.session, 'networkDisconnect');
- this.session.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]);
- sessionDisconnectEvent.callDefaultBehavior();
- }
- } else {
- alert('Connection error. Please reload page.');
- }
- }
-
- private isMasterNodeCrashed() {
- throw new Error("'isMasterNodeCrashed' method is not ready yet.");
- return this.masterNodeHasCrashed;
- }
-
- private isRoomAvailable(): boolean {
- throw new Error("'isRoomAvailable' method is not ready yet.");
- if (this.session !== undefined && this.session instanceof Session) {
- return true;
- } else {
- logger.warn('Session instance not found');
- return false;
- }
- }
-
private isScreenShare(videoSource: string) {
// throw new Error("'isScreenShare' method is not ready yet.");
return videoSource === 'screen' || videoSource === 'window' || (platform.isElectron() && videoSource.startsWith('screen:'));
diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts
index 4ead7594b0f..1b6015a76a6 100644
--- a/openvidu-browser/src/OpenVidu/Publisher.ts
+++ b/openvidu-browser/src/OpenVidu/Publisher.ts
@@ -1,59 +1,37 @@
-/*
- * (C) Copyright 2017-2022 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 { OpenVidu } from './OpenVidu';
-import { Session } from './Session';
-import { Stream } from './Stream';
-import { StreamManager } from './StreamManager';
-import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
-import { PublisherEventMap } from '../OpenViduInternal/Events/EventMap/PublisherEventMap';
-import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
-import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
-import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
-import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
-import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
-import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
-import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
-import { StreamEventReason } from '../OpenViduInternal/Events/Types/Types';
import {
AudioCaptureOptions,
- LocalAudioTrack,
LocalTrack,
- LocalTrackPublication,
LocalVideoTrack,
- Room,
ScreenShareCaptureOptions,
- Track,
+ TrackPublishOptions,
VideoCaptureOptions,
createLocalAudioTrack,
createLocalScreenTracks,
createLocalVideoTrack
} from 'livekit-client';
+import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
+import { StreamEventReason } from '../OpenViduInternal/Events/Types/Types';
+import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
+import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
+import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
+import { OpenVidu } from './OpenVidu';
+import { Session } from './Session';
+import { Stream } from './Stream';
+import { StreamManager } from './StreamManager';
+import { CustomMediaStreamConstraints } from '../OpenViduInternal/Interfaces/Private/CustomMediaStreamConstraints';
+import { v4 as uuidv4 } from 'uuid';
+import { EventMap } from '../OpenViduInternal/Events/EventMap/EventMap';
/**
* @hidden
*/
-const logger: OpenViduLogger = OpenViduLogger.getInstance();
-
+const platform: PlatformUtils = PlatformUtils.getInstance();
/**
* @hidden
*/
-let platform: PlatformUtils;
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
+// It could represent the local participant from livekit
/**
* Packs local media streams. Participants can publish it to a session. Initialized with {@link OpenVidu.initPublisher} method.
*
@@ -61,41 +39,40 @@ let platform: PlatformUtils;
*/
export class Publisher extends StreamManager {
/**
- * Whether the Publisher has been granted access to the requested input devices or not
- */
- accessAllowed = false;
-
- /**
- * Whether you have called {@link Publisher.subscribeToRemote} with value `true` or `false` (*false* by default)
+ * @hidden
*/
- isSubscribedToRemote = false;
-
+ openvidu: OpenVidu;
/**
* The {@link Session} to which the Publisher belongs
*/
session: Session; // Initialized by Session.publish(Publisher)
- private accessDenied = false;
- protected properties: PublisherProperties;
- private permissionDialogTimeout: NodeJS.Timer;
+ private properties: PublisherProperties;
+
+ // TODO: Is this needed?
+ accessAllowed: boolean;
+ // TODO: Is this needed?
+ accessDenied: boolean;
/**
* @hidden
*/
- openvidu: OpenVidu;
-
+ videoOptions: VideoCaptureOptions | undefined;
/**
* @hidden
*/
- screenShareResizeInterval: NodeJS.Timer;
-
- videoOptions?: VideoCaptureOptions;
- audioOptions?: AudioCaptureOptions;
-
+ audioOptions: AudioCaptureOptions | undefined;
+ /**
+ * @hidden
+ */
+ videoPublishOptions: TrackPublishOptions | undefined;
+ /**
+ * @hidden
+ */
+ audioPublishOptions: TrackPublishOptions | undefined;
/**
* @hidden
*/
constructor(targetElement: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) {
- // TODO: initialize with real values
const session = openvidu.session ? openvidu.session : new Session(openvidu);
const options = {
publisherProperties: properties,
@@ -104,13 +81,14 @@ export class Publisher extends StreamManager {
const stream = new Stream(session, options);
super(stream, targetElement);
-
- platform = PlatformUtils.getInstance();
this.stream = stream;
this.properties = properties;
this.openvidu = openvidu;
+ this.session = session;
+ this.stream.connection = session.connection
this.stream.ee.on('local-stream-destroyed', (reason: StreamEventReason) => {
- this.stream.isLocalStreamPublished = false;
+ // TODO: Uncomment this
+ // this.stream.isLocalStreamPublished = false;
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
this.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();
@@ -120,32 +98,44 @@ export class Publisher extends StreamManager {
/**
* @hidden
*/
- get videoReference(): HTMLVideoElement {
- let element!: HTMLVideoElement;
- this.stream.session.room.localParticipant.videoTracks.forEach((track) => {
- element = track.track?.attachedElements[0] as HTMLVideoElement;
- });
+ async initialize(): Promise {
+ const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
+ const { constraints, audioTrack, videoTrack } = myConstraints;
- if (!element) {
+ const { width, height } = this.getResolution(constraints);
+
+ const videoOptions = this.getVideoOptions(constraints, width, height);
+ const audioOptions = this.getAudioOptions(constraints, audioTrack);
+
+ if (this.isMediaTracksProvided(myConstraints)) {
+ // No need to call getUserMedia at all. MediaStreamTracks already provided
+ // TODO
+ throw new Error('MediaStreamTracks already provided. It is not implemented yet');
+ } else {
+ // MediaStreamTracks not provided. Must call getUserMedia
+ const outboundStreamOptions = {
+ mediaConstraints: constraints,
+ publisherProperties: this.properties
+ };
+ let constraintsAux: MediaStreamConstraints = {};
+ let startTime = Date.now();
+ this.stream.outboundStreamOpts = outboundStreamOptions;
+ const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
+ constraintsAux.audio = this.stream.isSendingScreen() ? false : definedAudioConstraint;
+ constraintsAux.video = constraints.video;
- return this.stream.temporalVideoTrack?.attachedElements[0] as HTMLVideoElement;
+ try {
+ if (this.shouldUseScreenCapture()) {
+ await this.handleScreenCapture();
+ } else {
+ await this.handleCameraAndMicrophone(videoOptions, audioOptions);
+ }
+ } catch (error) {
+ console.error('Error getting user media', error);
+ }
}
- return element;
}
- /**
- * @hidden
- */
- // setOptions(videoOptions?: VideoCaptureOptions, audioOptions?: AudioCaptureOptions): void {
- // // if(Boolean(videoOptions)) {
- // this.videoOptions = videoOptions;
- // // }
- // // if(Boolean(audioOptions)) {
- // this.audioOptions = audioOptions;
- // // }
- // // console.warn('setOptions not implemented in Publisher');
- // }
-
/**
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
*
@@ -164,41 +154,15 @@ export class Publisher extends StreamManager {
* @param enabled `true` to publish the audio stream, `false` to unpublish it
*/
publishAudio(enabled: boolean): void {
- this.session.room.localParticipant.setMicrophoneEnabled(enabled);
- this.stream.audioActive = enabled;
-
- return;
- throw new Error("'publishAudio' method is not ready yet");
if (this.stream.audioActive !== enabled) {
- const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
- ? this.stream.localMediaStreamWhenSubscribedToRemote!
- : this.stream.getMediaStream();
- affectedMediaStream.getAudioTracks().forEach((track) => {
- track.enabled = enabled;
+ this.session.localParticipant.setMicrophoneEnabled(enabled).then(() => {
+ // this.session.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
+ // ]);
+ // this.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
+ // ]);
});
- if (!!this.session && !!this.stream.streamId) {
- this.session.openvidu.sendRequest(
- 'streamPropertyChanged',
- {
- streamId: this.stream.streamId,
- property: 'audioActive',
- newValue: enabled,
- reason: 'publishAudio'
- },
- (error, response) => {
- if (error) {
- logger.error("Error sending 'streamPropertyChanged' event", error);
- } else {
- this.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
- ]);
- this.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
- ]);
- }
- }
- );
- }
this.stream.audioActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
}
@@ -231,163 +195,19 @@ export class Publisher extends StreamManager {
* will be used instead.
*/
async publishVideo(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise {
- console.warn("'publishVideo' method is not ready yet");
- await this.session.room.localParticipant.setCameraEnabled(enabled);
- this.stream.videoActive = enabled;
- return;
-
- return new Promise(async (resolve, reject) => {
- if (this.stream.videoActive !== enabled) {
- const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
- ? this.stream.localMediaStreamWhenSubscribedToRemote!
- : this.stream.getMediaStream();
- let mustRestartMediaStream = false;
- affectedMediaStream.getVideoTracks().forEach((track) => {
- track.enabled = enabled;
- if (!enabled && resource === true) {
- track.stop();
- } else if (enabled && track.readyState === 'ended') {
- // Resource was freed
- mustRestartMediaStream = true;
- }
- });
-
- // There is a Virtual Background filter applied that must be removed in case the hardware must be freed
- // if (!enabled && resource === true && !!this.stream.filter && this.stream.filter.type.startsWith('VB:')) {
- // this.stream.lastVBFilter = this.stream.filter; // Save the filter to be re-applied in case of unmute
- // await this.stream.removeFilterAux(true);
- // }
-
- if (mustRestartMediaStream) {
- const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
- affectedMediaStream.removeTrack(oldVideoTrack);
-
- const replaceVideoTrack = async (tr: MediaStreamTrack) => {
- affectedMediaStream.addTrack(tr);
- if (this.stream.isLocalStreamPublished) {
- await this.replaceTrackInRtcRtpSender(tr);
- }
- if (!!this.stream.lastVBFilter) {
- setTimeout(async () => {
- let options = this.stream.lastVBFilter!.options;
- const lastExecMethod = this.stream.lastVBFilter!.lastExecMethod;
- if (!!lastExecMethod && lastExecMethod.method === 'update') {
- options = Object.assign({}, options, lastExecMethod.params);
- }
- await this.stream.applyFilter(this.stream.lastVBFilter!.type, options);
- delete this.stream.lastVBFilter;
- }, 1);
- }
- };
-
- if (!!resource && resource instanceof MediaStreamTrack) {
- await replaceVideoTrack(resource);
- } else {
- try {
- const mediaStream = await navigator.mediaDevices.getUserMedia({
- audio: false,
- video: this.stream.lastVideoTrackConstraints
- });
- await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
- } catch (error) {
- return reject(error);
- }
- }
- }
-
- if (!!this.session && !!this.stream.streamId) {
- this.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
- ]);
- this.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
- ]);
- }
- this.stream.videoActive = enabled;
- logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
- return resolve();
- }
- });
- }
-
- /**
- * Call this method before {@link Session.publish} if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
- */
- subscribeToRemote(value?: boolean): void {
- throw new Error("'subscribeToRemote' method is not ready yet");
- // value = value !== undefined ? value : true;
- // this.isSubscribedToRemote = value;
- // this.stream.subscribeToMyRemote(value);
- }
-
- /**
- * See {@link EventDispatcher.on}
- */
- on(type: K, handler: (event: PublisherEventMap[K]) => void): this {
- console.warn("'on' method is not ready yet");
- super.on(type, handler);
-
- if (type === 'streamCreated') {
- // throw new Error("'streamCreated' event is not ready yet");
- // TODO: Comprobar este evento
-
- if (!!this.stream && this.stream.isLocalStreamPublished) {
- this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
- } else {
- this.stream.ee.on('stream-created-by-publisher', () => {
- this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
- });
- }
- }
- if (type === 'accessAllowed') {
- if (this.accessAllowed) {
- this.emitEvent('accessAllowed', []);
- }
- }
- if (type === 'accessDenied') {
- if (this.accessDenied) {
- this.emitEvent('accessDenied', []);
- }
- }
- return this;
- }
-
- /**
- * See {@link EventDispatcher.once}
- */
- once(type: K, handler: (event: PublisherEventMap[K]) => void): this {
- throw new Error("'once' method is not ready yet");
- super.once(type, handler);
-
- if (type === 'streamCreated') {
- if (!!this.stream && this.stream.isLocalStreamPublished) {
- this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
- } else {
- this.stream.ee.once('stream-created-by-publisher', () => {
- this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
- });
- }
- }
- if (type === 'accessAllowed') {
- if (this.accessAllowed) {
- this.emitEvent('accessAllowed', []);
- }
- }
- if (type === 'accessDenied') {
- if (this.accessDenied) {
- this.emitEvent('accessDenied', []);
- }
+ if (this.stream.videoActive !== enabled) {
+ console.warn("'publishVideo' method is not ready yet");
+ await this.session.localParticipant.setCameraEnabled(enabled);
+ // this.session.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
+ // ]);
+ // this.emitEvent('streamPropertyChanged', [
+ // new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
+ // ]);
+
+ this.stream.videoActive = enabled;
+ logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
}
- return this;
- }
-
- /**
- * See {@link EventDispatcher.off}
- */
- off(type: K, handler?: (event: PublisherEventMap[K]) => void): this {
- // throw new Error("'off' method is not ready yet");
- super.off(type, handler);
- return this;
}
/**
@@ -407,674 +227,160 @@ export class Publisher extends StreamManager {
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the track was successfully replaced and rejected with an Error object in other case
*/
async replaceTrack(track: MediaStreamTrack): Promise {
- const localTracks = this.session.room.localParticipant.getTracks();
- const trackKind = track.kind;
+ throw new Error('Method not implemented.');
- debugger;
- console.log(this.videoReference);
- if (localTracks.some((t) => t.isMuted)) {
- console.error('Cannot replace track while muted');
- return;
- }
- const kind: MediaDeviceKind = track.kind === 'audio' ? 'audioinput' : 'videoinput';
- const deviceId = track.getSettings().deviceId;
- if (!!kind && !!deviceId) {
- await this.session.room.switchActiveDevice(kind, deviceId);
- const trackMatched = localTracks.find((t) => t.kind === trackKind);
- if (trackMatched) {
- const videoTrack: LocalVideoTrack = trackMatched.track as LocalVideoTrack;
- videoTrack.attach(this.videoReference);
- this.stream.temporalVideoTrack = videoTrack;
- } else {
- console.error(`The participant does not have a ${trackKind} track`);
- }
+ // const localTracks = this.session.localParticipant.getTracks();
+ // const trackKind = track.kind;
- track.stop();
- // this.replaceTrackAux(track, true);
- } else {
- throw new Error('Invalid MediaStreamTrack provided');
- }
+ // if (localTracks.some((t) => t.isMuted)) {
+ // console.error('Cannot replace track while muted');
+ // return;
+ // }
+ // const kind: MediaDeviceKind = track.kind === 'audio' ? 'audioinput' : 'videoinput';
+ // const deviceId = track.getSettings().deviceId;
+ // if (!!kind && !!deviceId) {
+ // await this.session.switchActiveDevice(kind, deviceId);
+ // const trackMatched = localTracks.find((t) => t.kind === trackKind);
+ // if (trackMatched) {
+ // const videoTrack: LocalVideoTrack = trackMatched.track as LocalVideoTrack;
+ // videoTrack.attach(this.videoReference);
+ // this.stream.temporalVideoTrack = videoTrack;
+ // } else {
+ // console.error(`The participant does not have a ${trackKind} track`);
+ // }
+
+ // // this.replaceTrackAux(track, true);
+ // } else {
+ // throw new Error('Invalid MediaStreamTrack provided');
+ // }
}
- /* Hidden methods */
+ on(type: K, handler: (event: EventMap[K]) => void): this {
+ throw new Error('Method not implemented.');
+ }
+ once(type: K, handler: (event: EventMap[K]) => void): this {
+ throw new Error('Method not implemented.');
+ }
+ off(type: K, handler?: ((event: EventMap[K]) => void) | undefined): this {
+ throw new Error('Method not implemented.');
+ }
/**
* @hidden
*/
- async initialize(): Promise {
- const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
-
- let videoOptions: VideoCaptureOptions | undefined = undefined;
- let audioOptions: AudioCaptureOptions | undefined = undefined;
+ replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
+ throw new Error('Method not implemented.');
+ }
- let width: number;
- let height: number;
- const { constraints, audioTrack, videoTrack } = myConstraints;
- const { audio: audioSource, video: videoSource } = constraints;
+ private getResolution(constraints: MediaStreamConstraints): { width: number; height: number } {
try {
- width = (typeof constraints.video !== 'boolean' && Number(constraints?.video?.width?.toString())) || 640;
- height = (typeof constraints.video !== 'boolean' && Number(constraints?.video?.height?.toString())) || 480;
-
- if (typeof videoSource !== 'boolean' || videoSource) {
- videoOptions = {
- deviceId: videoSource?.['deviceId'].exact || videoSource?.['deviceId'] || undefined,
- facingMode: 'user',
- resolution: {
- width,
- height,
- frameRate: videoSource?.['frameRate'] || 30
- }
- };
- }
- if (typeof audioSource !== 'boolean' || audioSource) {
- audioOptions = {
- deviceId: audioTrack?.getSettings().deviceId
- };
- }
+ const videoSource = constraints.video;
+ const width = (typeof videoSource !== 'boolean' && Number(videoSource?.width?.toString())) || 640;
+ const height = (typeof videoSource !== 'boolean' && Number(videoSource?.height?.toString())) || 480;
+ return { width, height };
} catch (error) {
console.warn('Invalid resolution provided in PublisherProperties. Setting default value (640x480)');
- width = 640;
- height = 480;
+ return { width: 640, height: 480 };
}
+ }
- if (
- (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
- (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
- (!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
- ) {
- // No need to call getUserMedia at all. MediaStreamTracks already provided
- // TODO
- throw new Error('MediaStreamTracks already provided. It is not implemented yet');
- } else {
- const outboundStreamOptions = {
- mediaConstraints: constraints,
- publisherProperties: this.properties
- };
- let constraintsAux: MediaStreamConstraints = {};
- let startTime;
-
- this.stream.setOutboundStreamOptions(outboundStreamOptions);
- const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
- constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
- constraintsAux.video = constraints.video;
- startTime = Date.now();
-
- // this.setPermissionDialogTimer(timeForDialogEvent);
- try {
- if (this.stream.isSendScreen() /*&& navigator.mediaDevices['getDisplayMedia']*/ && !platform.isElectron()) {
- // throw new Error('SCREEN getDisplayMedia not implemented yet');
-
- const screenTrackOptions: ScreenShareCaptureOptions = {
- audio: this.properties.audioSource === 'screen',
- video: true,
- systemAudio: 'include'
- };
-
- const myScreenTracks = await createLocalScreenTracks(screenTrackOptions);
- this.accessAllowed = true;
- this.accessDenied = false;
- const screenVideoTrack = myScreenTracks.find((t) => t.kind === 'video') as LocalVideoTrack;
- screenVideoTrack.mediaStreamTrack.contentHint = 'detail';
- console.log('screenVideoTrack', screenVideoTrack);
-
- console.log('screenVideoTrack', screenVideoTrack.mediaStreamTrack.contentHint);
- const screenAdudioTrack = myScreenTracks.find((t) => t.kind === 'audio') as LocalAudioTrack;
-
- // Generate a MediaStream with the screen track and the audio track
- // const mediaStream = new MediaStream();
- if (screenVideoTrack) {
- // mediaStream.addTrack(screenVideoTrack.mediaStreamTrack);
- this.stream.temporalVideoScreenTrack = screenVideoTrack;
- this.stream.temporalVideoTrack = undefined;
- }
- if (screenAdudioTrack) {
- // mediaStream.addTrack(screenVideoTrack.mediaStreamTrack);
- this.stream.temporalAudioScreenTrack = screenAdudioTrack;
- this.stream.temporalAudioTrack = undefined;
- }
- // this.stream.setMediaStream(mediaStream);
-
- // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
- // await getMediaSuccess(mediaStream, definedAudioConstraint);
-
- this.stream.isLocalStreamReadyToPublish = true;
- this.stream.ee.emitEvent('stream-ready-to-publish', []);
- } else {
- this.stream.lastVideoTrackConstraints = constraintsAux.video;
- // const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
- const [myVideoTrack, myAudioTrack] = await Promise.all([
- createLocalVideoTrack(videoOptions),
- createLocalAudioTrack(audioOptions)
- ]);
-
- // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
- // await getMediaSuccess(mediaStream, definedAudioConstraint);
-
- this.accessAllowed = true;
- this.accessDenied = false;
- this.stream.temporalVideoTrack = myVideoTrack;
- this.stream.temporalAudioTrack = myAudioTrack;
- this.stream.temporalVideoScreenTrack = undefined;
- this.stream.temporalAudioScreenTrack = undefined;
-
- // Generate a MediaStream with the screen track and the audio track
- // const mediaStream = new MediaStream();
- // mediaStream.addTrack(myVideoTrack.mediaStreamTrack);
- // mediaStream.addTrack(myAudioTrack.mediaStreamTrack);
- // this.stream.setMediaStream(mediaStream);
- // console.log('setting media stream', mediaStream);
-
- this.stream.isLocalStreamReadyToPublish = true;
- this.stream.ee.emitEvent('stream-ready-to-publish', []);
+ private getVideoOptions(constraints: MediaStreamConstraints, width: number, height: number): VideoCaptureOptions | undefined {
+ const videoSource = constraints.video;
+
+ if (typeof videoSource !== 'boolean' || videoSource) {
+ return {
+ deviceId: videoSource?.['deviceId']?.exact || videoSource?.['deviceId'] || undefined,
+ facingMode: 'user',
+ resolution: {
+ width,
+ height,
+ frameRate: videoSource?.['frameRate'] || 30
}
- } catch (error) {
- console.error('Error getting user media', error);
- // await getMediaError(error);
- }
+ };
}
- // throw new Error("'initialize' method is not ready yet");
- // return new Promise(async (resolve, reject) => {
- // let constraints: MediaStreamConstraints = {};
- // let constraintsAux: MediaStreamConstraints = {};
- // const timeForDialogEvent = 2000;
- // let startTime;
-
- // const errorCallback = (openViduError: OpenViduError) => {
- // this.accessDenied = true;
- // this.accessAllowed = false;
- // logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
- // return reject(openViduError);
- // };
-
- // const successCallback = (mediaStream: MediaStream) => {
- // this.accessAllowed = true;
- // this.accessDenied = false;
-
- // if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
- // mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
- // mediaStream.addTrack(this.properties.audioSource);
- // }
-
- // if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
- // mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
- // mediaStream.addTrack(this.properties.videoSource);
- // }
-
- // // Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
- // if (!!mediaStream.getAudioTracks()[0]) {
- // const enabled =
- // this.stream.audioActive !== undefined && this.stream.audioActive !== null
- // ? this.stream.audioActive
- // : !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
- // mediaStream.getAudioTracks()[0].enabled = enabled;
- // }
- // if (!!mediaStream.getVideoTracks()[0]) {
- // const enabled =
- // this.stream.videoActive !== undefined && this.stream.videoActive !== null
- // ? this.stream.videoActive
- // : !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
- // mediaStream.getVideoTracks()[0].enabled = enabled;
- // }
-
- // // Set Content Hint on all MediaStreamTracks
- // for (const track of mediaStream.getAudioTracks()) {
- // if (!track.contentHint?.length) {
- // // contentHint for audio: "", "speech", "speech-recognition", "music".
- // // https://w3c.github.io/mst-content-hint/#audio-content-hints
- // track.contentHint = '';
- // logger.info(`Audio track Content Hint set: '${track.contentHint}'`);
- // }
- // }
- // for (const track of mediaStream.getVideoTracks()) {
- // if (!track.contentHint?.length) {
- // // contentHint for video: "", "motion", "detail", "text".
- // // https://w3c.github.io/mst-content-hint/#video-content-hints
- // switch (this.stream.typeOfVideo) {
- // case TypeOfVideo.SCREEN:
- // track.contentHint = 'detail';
- // break;
- // case TypeOfVideo.CUSTOM:
- // logger.warn('CUSTOM type video track was provided without Content Hint!');
- // track.contentHint = 'motion';
- // break;
- // case TypeOfVideo.CAMERA:
- // case TypeOfVideo.IPCAM:
- // default:
- // track.contentHint = 'motion';
- // break;
- // }
- // logger.info(`Video track Content Hint set: '${track.contentHint}'`);
- // }
- // }
-
- // this.initializeVideoReference(mediaStream);
-
- // if (!this.stream.displayMyRemote()) {
- // // When we are subscribed to our remote we don't still set the MediaStream object in the video elements to
- // // avoid early 'streamPlaying' event
- // this.stream.updateMediaStreamInVideos();
- // }
- // delete this.firstVideoElement;
-
- // if (this.stream.isSendVideo()) {
- // // Has video track
- // this.getVideoDimensions().then((dimensions) => {
- // this.stream.videoDimensions = {
- // width: dimensions.width,
- // height: dimensions.height
- // };
-
- // if (this.stream.isSendScreen()) {
-
- // if(this.stream.isSendAudio() && mediaStream.getAudioTracks().length === 0){
- // // If sending audio is enabled and there are no audio tracks in the mediaStream, disable audio for screen sharing.
- // this.stream.audioActive = false;
- // this.stream.hasAudio = false;
- // this.stream.outboundStreamOpts.publisherProperties.publishAudio = false;
- // this.stream.outboundStreamOpts.publisherProperties.audioSource = false;
- // }
-
- // // Set interval to listen for screen resize events
- // this.screenShareResizeInterval = setInterval(() => {
- // const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
- // const newWidth = settings.width;
- // const newHeight = settings.height;
- // const widthChanged = newWidth != null && newWidth !== this.stream.videoDimensions.width;
- // const heightChanged = newHeight != null && newHeight !== this.stream.videoDimensions.height;
- // if (this.stream.isLocalStreamPublished && (widthChanged || heightChanged)) {
- // this.openvidu.sendVideoDimensionsChangedEvent(
- // this,
- // 'screenResized',
- // this.stream.videoDimensions.width,
- // this.stream.videoDimensions.height,
- // newWidth || 0,
- // newHeight || 0
- // );
- // }
- // }, 650);
- // }
-
- // this.stream.isLocalStreamReadyToPublish = true;
- // this.stream.ee.emitEvent('stream-ready-to-publish', []);
- // });
- // } else {
- // // Only audio track (no videoDimensions)
- // this.stream.isLocalStreamReadyToPublish = true;
- // this.stream.ee.emitEvent('stream-ready-to-publish', []);
- // }
-
- // return resolve();
- // };
-
- // const getMediaSuccess = async (mediaStream: MediaStream, definedAudioConstraint) => {
- // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
- // if (this.stream.isSendScreen() && this.properties.audioSource !== 'screen' && this.stream.isSendAudio()) {
- // // When getting desktop as user media audio constraint must be false. Now we can ask for it if required
- // constraintsAux.audio = definedAudioConstraint;
- // constraintsAux.video = false;
- // startTime = Date.now();
- // this.setPermissionDialogTimer(timeForDialogEvent);
-
- // try {
- // const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
- // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
- // mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
- // successCallback(mediaStream);
- // } catch (error) {
- // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
- // mediaStream.getAudioTracks().forEach((track) => {
- // track.stop();
- // });
- // mediaStream.getVideoTracks().forEach((track) => {
- // track.stop();
- // });
- // errorCallback(this.openvidu.generateAudioDeviceError(error, constraints));
- // return;
- // }
- // } else {
- // successCallback(mediaStream);
- // }
- // };
-
- // const getMediaError = async (error) => {
- // logger.error(`getMediaError: ${error.toString()}`);
- // this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
- // if (error.name === 'Error') {
- // // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
- // error.name = error.constructor.name;
- // }
- // let errorName, errorMessage;
- // switch (error.name.toLowerCase()) {
- // case 'notfounderror':
- // try {
- // const mediaStream = await navigator.mediaDevices.getUserMedia({
- // audio: false,
- // video: constraints.video
- // });
- // mediaStream.getVideoTracks().forEach((track) => {
- // track.stop();
- // });
- // errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
- // errorMessage = error.toString();
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // } catch (error) {
- // errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
- // errorMessage = error.toString();
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // }
-
- // break;
- // case 'notallowederror':
- // errorName = this.stream.isSendScreen()
- // ? OpenViduErrorName.SCREEN_CAPTURE_DENIED
- // : OpenViduErrorName.DEVICE_ACCESS_DENIED;
- // errorMessage = error.toString();
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // break;
- // case 'overconstrainederror':
- // try {
- // const mediaStream = await navigator.mediaDevices.getUserMedia({
- // audio: false,
- // video: constraints.video
- // });
- // mediaStream.getVideoTracks().forEach((track) => {
- // track.stop();
- // });
- // if (error.constraint.toLowerCase() === 'deviceid') {
- // errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
- // errorMessage =
- // "Audio input device with deviceId '" +
- // ((constraints.audio).deviceId!!).exact +
- // "' not found";
- // } else {
- // errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
- // errorMessage =
- // "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
- // }
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // } catch (error) {
- // if (error.constraint.toLowerCase() === 'deviceid') {
- // errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
- // errorMessage =
- // "Video input device with deviceId '" +
- // ((constraints.video).deviceId!!).exact +
- // "' not found";
- // } else {
- // errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
- // errorMessage =
- // "Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
- // }
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // }
-
- // break;
- // case 'aborterror':
- // case 'notreadableerror':
- // errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
- // errorMessage = error.toString();
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // break;
- // default:
- // errorName = OpenViduErrorName.GENERIC_ERROR;
- // errorMessage = error.toString();
- // errorCallback(new OpenViduError(errorName, errorMessage));
- // break;
- // }
- // };
-
- // try {
- // const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
- // if (
- // (!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
- // (!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
- // (!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
- // ) {
- // // No need to call getUserMedia at all. MediaStreamTracks already provided
- // successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
- // } else {
- // constraints = myConstraints.constraints;
-
- // const outboundStreamOptions = {
- // mediaConstraints: constraints,
- // publisherProperties: this.properties
- // };
- // this.stream.setOutboundStreamOptions(outboundStreamOptions);
-
- // const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
- // constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
- // constraintsAux.video = constraints.video;
- // startTime = Date.now();
- // this.setPermissionDialogTimer(timeForDialogEvent);
-
- // try {
- // if (this.stream.isSendScreen() && navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
- // const mediaStream = await navigator.mediaDevices['getDisplayMedia']({ video: true, audio: this.properties.audioSource === 'screen' });
- // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
- // await getMediaSuccess(mediaStream, definedAudioConstraint);
- // } else {
- // this.stream.lastVideoTrackConstraints = constraintsAux.video;
- // const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
- // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
- // await getMediaSuccess(mediaStream, definedAudioConstraint);
- // }
- // } catch (error) {
- // await getMediaError(error);
- // }
- // }
- // } catch (error) {
- // errorCallback(error);
- // }
- // });
- }
-
- /**
- * @hidden
- */
- async replaceTrackAux(track: MediaStreamTrack, updateLastConstraints: boolean): Promise {
- // throw new Error("'replaceTrackAux' method is not ready yet");
- // Set field "enabled" of the new track to the previous value
- const trackOriginalEnabledValue: boolean = track.enabled;
- if (track.kind === 'video') {
- track.enabled = this.stream.videoActive;
- } else if (track.kind === 'audio') {
- track.enabled = this.stream.audioActive;
- }
- try {
- if (this.stream.isLocalStreamPublished) {
- // Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
- // If it has not been published yet, replacing it on the MediaStream object is enough
- this.replaceTrackInMediaStream(track, updateLastConstraints);
- // return await this.replaceTrackInRtcRtpSender(track);
- } else {
- // Publisher not published. Simply replace the track on the local MediaStream
- return this.replaceTrackInMediaStream(track, updateLastConstraints);
- }
- } catch (error) {
- track.enabled = trackOriginalEnabledValue;
- throw error;
- }
+ return undefined;
}
- /**
- * @hidden
- *
- * To obtain the videoDimensions we wait for the video reference to have enough metadata
- * and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
- * use the HTMLVideoElement properties videoWidth and videoHeight
- */
- getVideoDimensions(): Promise<{ width: number; height: number }> {
- throw new Error("'getVideoDimensions' method is not ready yet");
- return new Promise((resolve, reject) => {
- // Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
- const requiresDomInsertion: boolean =
- (platform.isIonicIos() || platform.isIOSWithSafari()) && this.videoReference.readyState < 1;
+ private getAudioOptions(
+ constraints: MediaStreamConstraints,
+ audioTrack: MediaStreamTrack | undefined
+ ): AudioCaptureOptions | undefined {
+ const audioSource = constraints.audio;
- let loadedmetadataListener;
- const resolveDimensions = () => {
- let width: number;
- let height: number;
- if (typeof this.stream.getMediaStream().getVideoTracks()[0].getSettings === 'function') {
- const settings = this.stream.getMediaStream().getVideoTracks()[0].getSettings();
- width = settings.width || this.videoReference.videoWidth;
- height = settings.height || this.videoReference.videoHeight;
- } else {
- logger.warn('MediaStreamTrack does not have getSettings method on ' + platform.getDescription());
- width = this.videoReference.videoWidth;
- height = this.videoReference.videoHeight;
- }
-
- if (loadedmetadataListener != null) {
- this.videoReference.removeEventListener('loadedmetadata', loadedmetadataListener);
- }
- if (requiresDomInsertion) {
- document.body.removeChild(this.videoReference);
- }
-
- return resolve({ width, height });
+ if (typeof audioSource !== 'boolean' || audioSource) {
+ return {
+ deviceId: audioTrack?.getSettings().deviceId
};
+ }
- if (this.videoReference.readyState >= 1) {
- // The video already has metadata available
- // No need of loadedmetadata event
- resolveDimensions();
- } else {
- // The video does not have metadata available yet
- // Must listen to loadedmetadata event
- loadedmetadataListener = () => {
- if (!this.videoReference.videoWidth) {
- let interval = setInterval(() => {
- if (!!this.videoReference.videoWidth) {
- clearInterval(interval);
- resolveDimensions();
- }
- }, 40);
- } else {
- resolveDimensions();
- }
- };
- this.videoReference.addEventListener('loadedmetadata', loadedmetadataListener);
- if (requiresDomInsertion) {
- document.body.appendChild(this.videoReference);
- }
- }
- });
+ return undefined;
}
-
- /**
- * @hidden
- */
- reestablishStreamPlayingEvent() {
- // throw new Error("'reestablishStreamPlayingEvent' method is not ready yet");
- if (this.ee.getListeners('streamPlaying').length > 0) {
- this.addPlayEventToFirstVideo();
- }
+ private isMediaTracksProvided(constraints: CustomMediaStreamConstraints): boolean {
+ return (
+ (!!constraints.videoTrack && !!constraints.audioTrack) ||
+ (!!constraints.audioTrack && constraints.constraints?.video === false) ||
+ (!!constraints.videoTrack && constraints.constraints?.audio === false)
+ );
}
- /**
- * @hidden
- */
- initializeVideoReference(mediaStream: MediaStream) {
- throw new Error("'initializeVideoReference' method is not ready yet");
- // this.videoReference = document.createElement('video');
- // this.videoReference.style.display = 'none';
- // this.videoReference.muted = true;
- // this.videoReference.autoplay = true;
- // this.videoReference.controls = false;
- // if (
- // platform.isSafariBrowser() ||
- // (platform.isIPhoneOrIPad() &&
- // (platform.isChromeMobileBrowser() ||
- // platform.isEdgeMobileBrowser() ||
- // platform.isOperaMobileBrowser() ||
- // platform.isFirefoxMobileBrowser()))
- // ) {
- // this.videoReference.playsInline = true;
- // }
- // this.stream.setMediaStream(mediaStream);
- // if (!!this.firstVideoElement) {
- // this.createVideoElement(this.firstVideoElement.targetElement, this.properties.insertMode);
- // }
- // this.videoReference.srcObject = this.stream.getMediaStream();
+ private shouldUseScreenCapture(): boolean {
+ return this.stream.isSendingScreen() && !platform.isElectron();
}
- /**
- * @hidden
- */
- replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
- // throw new Error("'replaceTrackInMediaStream' method is not ready yet");
- const mediaStream: MediaStream = this.stream.displayMyRemote()
- ? this.stream.localMediaStreamWhenSubscribedToRemote!
- : this.stream.getMediaStream();
- let removedTrack: MediaStreamTrack;
- if (track.kind === 'video') {
- removedTrack = mediaStream.getVideoTracks()[0];
- if (updateLastConstraints) {
- this.stream.lastVideoTrackConstraints = track.getConstraints();
- }
- } else {
- removedTrack = mediaStream.getAudioTracks()[0];
- }
- removedTrack.enabled = false;
- removedTrack.stop();
- mediaStream.removeTrack(removedTrack);
- mediaStream.addTrack(track);
- const trackInfo = {
- oldLabel: removedTrack?.label || '',
- newLabel: track?.label || ''
+ private async handleScreenCapture(): Promise {
+ const screenTrackOptions: ScreenShareCaptureOptions = {
+ audio: this.properties.audioSource === 'screen',
+ video: true,
+ systemAudio: 'include'
};
- if (track.kind === 'video' && updateLastConstraints) {
- this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
- this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'videoTrack');
- // if (this.stream.isLocalStreamPublished) {
- // this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
- // }
- } else if (track.kind === 'audio' && updateLastConstraints) {
- this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'audioTrack');
- }
- // if (track.kind === 'audio') {
- // this.stream.disableHarkSpeakingEvent(false);
- // this.stream.disableHarkStoppedSpeakingEvent(false);
- // this.stream.disableHarkVolumeChangeEvent(false);
- // this.stream.initHarkEvents();
- // }
+ const myScreenTracks = await createLocalScreenTracks(screenTrackOptions);
+ this.accessAllowed = true;
+ this.accessDenied = false;
+ this.updateStreamWithScreenTracks(myScreenTracks);
+ this.stream.isLocalStreamReadyToPublish = true;
+ this.stream.ee.emitEvent('stream-ready-to-publish', []);
}
- /* Private methods */
-
- private setPermissionDialogTimer(waitTime: number): void {
- throw new Error("'setPermissionDialogTimer' method is not ready yet");
- this.permissionDialogTimeout = setTimeout(() => {
- this.emitEvent('accessDialogOpened', []);
- }, waitTime);
+ private updateStreamWithScreenTracks(myScreenTracks: LocalTrack[]): void {
+ // const screenVideoTrack = myScreenTracks.find((t) => t.kind === 'video') as LocalVideoTrack;
+ // if (screenVideoTrack) {
+ // screenVideoTrack.mediaStreamTrack.contentHint = 'detail';
+ // this.stream.temporalVideoScreenTrack = screenVideoTrack;
+ // this.stream.temporalVideoTrack = undefined;
+ // }
+ // const screenAudioTrack = myScreenTracks.find((t) => t.kind === 'audio') as LocalAudioTrack;
+ // if (screenAudioTrack) {
+ // this.stream.temporalAudioScreenTrack = screenAudioTrack;
+ // this.stream.temporalAudioTrack = undefined;
+ // }
}
- private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
- throw new Error("'clearPermissionDialogTimer' method is not ready yet");
- clearTimeout(this.permissionDialogTimeout);
- if (Date.now() - startTime > waitTime) {
- // Permission dialog was shown and now is closed
- this.emitEvent('accessDialogClosed', []);
+ private async handleCameraAndMicrophone(videoOptions?: VideoCaptureOptions, audioOptions?: AudioCaptureOptions): Promise {
+ // const [myVideoTrack, myAudioTrack] = await Promise.all([createLocalVideoTrack(videoOptions), createLocalAudioTrack(audioOptions)]);
+ // this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
+ // await getMediaSuccess(mediaStream, definedAudioConstraint);
+ this.accessAllowed = true;
+ this.accessDenied = false;
+ const publishOptions: TrackPublishOptions = {
+ stream: this.stream.streamId
+ };
+ if (videoOptions) {
+ publishOptions.name = `ov_video_${uuidv4()}`;
+ this.videoOptions = videoOptions;
+ this.videoPublishOptions = publishOptions;
+ // await this.session.localParticipant.setCameraEnabled(true, videoOptions, publishOptions);
}
- }
-
- private async replaceTrackInRtcRtpSender(track: MediaStreamTrack): Promise {
- throw new Error("'replaceTrackInRtcRtpSender' method is not ready yet");
- const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
- let sender: RTCRtpSender | undefined;
- if (track.kind === 'video') {
- sender = senders.find((s) => !!s.track && s.track.kind === 'video');
- if (!sender) {
- throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
- }
- } else if (track.kind === 'audio') {
- sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
- if (!sender) {
- throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
- }
- } else {
- throw new Error('Unknown track kind ' + track.kind);
+ if (audioOptions) {
+ publishOptions.name = `ov_audio_${uuidv4()}`;
+ this.audioOptions = audioOptions;
+ this.audioPublishOptions = publishOptions;
+ // await this.session.localParticipant.setMicrophoneEnabled(true, audioOptions, publishOptions);
}
- await (sender as RTCRtpSender).replaceTrack(track);
+ // this.stream.temporalVideoTrack = myVideoTrack;
+ // this.stream.temporalAudioTrack = myAudioTrack;
+ // this.stream.temporalVideoScreenTrack = undefined;
+ // this.stream.temporalAudioScreenTrack = undefined;
+ this.stream.isLocalStreamReadyToPublish = true;
}
}
diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts
index f3bae1f315e..bbc34383228 100644
--- a/openvidu-browser/src/OpenVidu/Session.ts
+++ b/openvidu-browser/src/OpenVidu/Session.ts
@@ -1,240 +1,149 @@
-/*
- * (C) Copyright 2017-2022 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 './Connection';
-import { Filter } from './Filter';
-import { OpenVidu } from './OpenVidu';
-import { Publisher } from './Publisher';
-import { Stream } from './Stream';
-import { StreamManager } from './StreamManager';
-import { Subscriber } from './Subscriber';
-import { Capabilities } from '../OpenViduInternal/Interfaces/Public/Capabilities';
-import { EventDispatcher } from './EventDispatcher';
-import { SignalOptions } from '../OpenViduInternal/Interfaces/Public/SignalOptions';
-import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
-import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
-import { LocalConnectionOptions } from '../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
-import { SessionOptions } from '../OpenViduInternal/Interfaces/Private/SessionOptions';
-import { SessionEventMap } from '../OpenViduInternal/Events/EventMap/SessionEventMap';
-import { ConnectionEvent } from '../OpenViduInternal/Events/ConnectionEvent';
-import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent';
-import { RecordingEvent } from '../OpenViduInternal/Events/RecordingEvent';
-import { SessionDisconnectedEvent } from '../OpenViduInternal/Events/SessionDisconnectedEvent';
-import { SignalEvent } from '../OpenViduInternal/Events/SignalEvent';
-import { SpeechToTextEvent } from '../OpenViduInternal/Events/SpeechToTextEvent';
-import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
-import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
-import { ConnectionPropertyChangedEvent } from '../OpenViduInternal/Events/ConnectionPropertyChangedEvent';
-import { NetworkQualityLevelChangedEvent } from '../OpenViduInternal/Events/NetworkQualityLevelChangedEvent';
-import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
-import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
-import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
-import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
-import {
- StreamPropertyChangedEventReason,
- ChangedPropertyType,
- RecordingEventReason,
- ConnectionEventReason,
- StreamEventReason
-} from '../OpenViduInternal/Events/Types/Types';
-/**
- * @hidden
- */
-import semverMajor = require('semver/functions/major');
-/**
- * @hidden
- */
-import semverMinor = require('semver/functions/minor');
-import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
import {
ConnectionQuality,
ConnectionState,
- LocalTrack,
- LocalTrackPublication,
+ DataPacket_Kind,
+ DataPublishOptions,
+ LocalParticipant,
Participant,
- RemoteAudioTrack,
RemoteParticipant,
RemoteTrack,
RemoteTrackPublication,
- RemoteVideoTrack,
Room,
+ RoomConnectOptions,
RoomEvent,
Track
} from 'livekit-client';
-import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
+import { Connection } from './Connection';
+import { EventDispatcher } from './EventDispatcher';
+import { SessionDisconnectedEvent } from '../OpenViduInternal/Events/SessionDisconnectedEvent';
+import { EventMap } from '../OpenViduInternal/Events/EventMap/EventMap';
+import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
+import { Capabilities } from '../OpenViduInternal/Interfaces/Public/Capabilities';
+import { OpenVidu } from './OpenVidu';
+import { StreamManager } from './StreamManager';
+import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
+import { ConnectionEvent } from '../OpenViduInternal/Events/ConnectionEvent';
+import { Stream } from './Stream';
+import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
+import { Subscriber } from './Subscriber';
+import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
+import { Publisher } from './Publisher';
+import { SignalOptions } from '../OpenViduInternal/Interfaces/Public/SignalOptions';
+import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
+import { SessionEventMap } from '../OpenViduInternal/Events/EventMap/SessionEventMap';
import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
-import { SubscriptionError } from 'livekit-client/dist/src/proto/livekit_models_pb';
+import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
+import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
/**
* @hidden
*/
-const logger: OpenViduLogger = OpenViduLogger.getInstance();
-
+let platform: PlatformUtils = PlatformUtils.getInstance();
/**
* @hidden
*/
-let platform: PlatformUtils;
+const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
- * Represents a video call. It can also be seen as a videoconference room where multiple users can connect.
- * Participants who publish their videos to a session can be seen by the rest of users connected to that specific session.
- * Initialized with {@link OpenVidu.initSession} method.
- *
- * See available event listeners at {@link SessionEventMap}.
+ * Livekit Room
*/
export class Session extends EventDispatcher {
- /**
- * Local connection to the Session. This object is defined only after {@link Session.connect} has been successfully executed, and can be retrieved subscribing to `connectionCreated` event
- */
- connection: Connection;
-
- /**
- * Collection of all StreamManagers of this Session ({@link Publisher} and {@link Subscriber})
- */
- streamManagers: StreamManager[] = [];
-
- /**
- * Object defining the methods that the client is able to call. These are defined by the {@link Connection.role}.
- * This object is only defined after {@link Session.connect} has been successfully resolved
- */
- capabilities: Capabilities;
-
- // This map is only used to avoid race condition between 'joinRoom' response and 'onParticipantPublished' notification
/**
* @hidden
*/
- remoteStreamsCreated: Map = new Map();
+ room: Room;
- /**
- * @hidden
- */
openvidu: OpenVidu;
+
/**
- * @hidden
- */
- // options: SessionOptions;
- /**
- * @hidden
+ * Unique identifier of the Session
*/
- token: string;
+ get sessionId(): string {
+ return this.room.name;
+ }
/**
- * @hidden
+ * Local connection to the Session. This object is defined only after {@link Session.connect} has been successfully executed, and can be retrieved subscribing to `connectionCreated` event
*/
- room: Room;
+ connection: Connection;
- private streamCreatedInterval: NodeJS.Timeout | undefined = undefined;
- private streamDestroyedInterval: NodeJS.Timeout | undefined = undefined;
- private ovRemoteConnections: Map = new Map();
- private temporalPublications: Map = new Map();
+ remoteConnections: Map = new Map();
+ // get remoteConnections(): Map {
+ // //TODO
+ // if (!this.room) return new Map();
+
+ // const participants = this.room.participants;
+ // participants.forEach((participant: RemoteParticipant) => {
+ // const opt: RemoteConnectionOptions = {
+ // id: participant.sid,
+ // createdAt: participant.joinedAt?.getTime() ?? -1, // miliseconds
+ // metadata: participant.metadata || '',
+ // streams: []
+ // };
+ // this.ovRemoteConnections.set(participant.sid, new Connection(this, opt));
+ // });
+ // return this.ovRemoteConnections;
+ // }
- private networkQualityLevelValue: number = 5;
+ // get streamManager(): StreamManager[] {}
- /**
- * @hidden
- */
- constructor(openvidu: OpenVidu) {
- super();
- this.room = new Room();
- platform = PlatformUtils.getInstance();
- this.openvidu = openvidu;
+ get capabilities(): Capabilities {
+ return {
+ subscribe: true,
+ publish: this.openvidu.role !== 'SUBSCRIBER',
+ forceUnpublish: this.openvidu.role === 'MODERATOR',
+ forceDisconnect: this.openvidu.role === 'MODERATOR'
+ };
}
/**
- * Unique identifier of the Session
+ * @hidden
*/
- get sessionId(): string {
- return this.room.name;
+ get localParticipant(): LocalParticipant {
+ return this.room.localParticipant;
}
- get remoteConnections(): Map {
- //TODO
- if (!this.room) return new Map();
+ private token: string;
+ private networkQualityLevelValue: number = 5;
+ private temporalPublications: Map = new Map();
+ // private streamCreatedInterval: NodeJS.Timeout | undefined = undefined;
+ private participantEventsIntervals: Map = new Map();
- const participants = this.room.participants;
- const connections = new Map();
- return this.ovRemoteConnections;
+ constructor(openvidu: OpenVidu) {
+ super();
+ this.openvidu = openvidu;
+ this.room = new Room();
}
connect(token: string): Promise;
connect(token: string, metadata: any): Promise;
-
- /**
- * Connects to the session using `token`. Parameter `metadata` allows you to pass extra data to share with other users when
- * they receive `streamCreated` event. The structure of `metadata` string is up to you (maybe some standardized format
- * as JSON or XML is a good idea).
- *
- * This metadata is not considered secure, as it is generated in the client side. To pass secure data, add it as a parameter in the
- * token generation operation (through the API REST, openvidu-java-client or openvidu-node-client).
- *
- * Only after the returned Promise is successfully resolved {@link Session.connection} object will be available and properly defined.
- *
- * #### Events dispatched
- *
- * The {@link Session} object of the local participant will first dispatch one or more `connectionCreated` events upon successful termination of this method:
- * - First one for your own local Connection object, so you can retrieve {@link Session.connection} property.
- * - Then one for each remote Connection previously connected to the Session, if any. Any other remote user connecting to the Session after you have
- * successfully connected will also dispatch a `connectionCreated` event when they do so.
- *
- * The {@link Session} object of the local participant will also dispatch a `streamCreated` event for each remote active {@link Publisher} that was already streaming
- * when connecting, just after dispatching all remote `connectionCreated` events.
- *
- * The {@link Session} object of every other participant connected to the session will dispatch a `connectionCreated` event.
- *
- * See {@link ConnectionEvent} and {@link StreamEvent} to learn more.
- *
- * @returns A Promise to which you must subscribe that is resolved if the the connection to the Session was successful and rejected with an Error object if not
- *
- */
async connect(token: string, metadata?: any): Promise {
try {
- //'ws://localhost:7880/';
const url = this.getLivekitUrlFromMetadata(token);
- this.token = token;
console.log('connecting to', url, token, metadata);
if (this.room) {
this.room.on(RoomEvent.Connected, () => {
- //TODO Generate this info properly with the room and server-client-adapter info
- const opts: LocalConnectionOptions = {
- id: this.room?.localParticipant.sid,
- finalUserId: this.room?.localParticipant.identity,
- createdAt: Number(BigInt(this.room?.['roomInfo']?.creationTime)) * 1000, // miliseconds
- metadata: this.room?.localParticipant.metadata || '',
- value: [],
- session: this.room?.['roomInfo']?.sid,
- sessionId: this.room?.['roomInfo']?.name,
- role: 'PUBLISHER',
- record: false,
- coturnIp: '',
- coturnPort: 0,
- turnUsername: '',
- turnCredential: '',
- version: '3.0.0',
- mediaServer: '',
- videoSimulcast: true,
- life: 0
- };
- this.processSessionConnected(token, opts);
+ console.log('connected to room');
console.log('SESSION CONNECTED!!!!!!!!', this.room);
+
+ this.connection = new Connection(this, this.room.localParticipant);
+ this.openvidu.role = this.room.localParticipant?.permissions?.canPublish ? 'PUBLISHER' : 'SUBSCRIBER';
+ this.openvidu.finalUserId = this.room.localParticipant?.identity;
+ // this.openvidu.videoSimulcast
+ this.token = token;
+ this.processSessionConnected();
});
- return this.room.connect(url, token);
+ const options: RoomConnectOptions = {
+ autoSubscribe: true,
+ maxRetries: 3,
+ peerConnectionTimeout: 15000,
+ websocketTimeout: 15000
+ };
+ return this.room.connect(url, token, options);
}
- console.error('Room not defined. Cannot connect');
+
+ throw new Error('Room not defined. Cannot connect');
} catch (error) {
console.error(
`Error connecting to ${platform.getName()} (version ${platform.getVersion()}) for ${platform.getFamily()}:`,
@@ -245,80 +154,19 @@ export class Session extends EventDispatcher {
}
}
- private processSessionConnected(token: string, options: LocalConnectionOptions) {
- this.processJoinRoomResponse(options, token);
-
- // Initialize local Connection object with values returned by openvidu-server
- this.connection = new Connection(this, options);
-
- // Initialize remote Connections with value returned by openvidu-server
- // const events = {
- // connections: new Array(),
- // streams: new Array()
- // };
- // const existingParticipants: RemoteConnectionOptions[] = response.value;
- // existingParticipants.forEach((remoteConnectionOptions: RemoteConnectionOptions) => {
- // const connection = new Connection(this, remoteConnectionOptions);
- // this.remoteConnections.set(connection.connectionId, connection);
- // events.connections.push(connection);
- // if (!!connection.stream) {
- // this.remoteStreamsCreated.set(connection.stream.streamId, true);
- // events.streams.push(connection.stream);
- // }
- // });
-
- // // Own 'connectionCreated' event
- // this.ee.emitEvent('connectionCreated', [
- // new ConnectionEvent(false, this, 'connectionCreated', this.connection, '')
- // ]);
-
- // // One 'connectionCreated' event for each existing connection in the session
- // events.connections.forEach((connection) => {
- // this.ee.emitEvent('connectionCreated', [
- // new ConnectionEvent(false, this, 'connectionCreated', connection, '')
- // ]);
- // });
- // // One 'streamCreated' event for each active stream in the session
- // events.streams.forEach((stream) => {
- // this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', stream, '')]);
- // });
-
- // if (!!response.recordingId && !!response.recordingName) {
- // this.ee.emitEvent('recordingStarted', [
- // new RecordingEvent(this, 'recordingStarted', response.recordingId, response.recordingName)
- // ]);
- // }
+ disconnect(): void {
+ if (this.connection && this.room) {
+ this.room.disconnect();
+ const sessionDisconnectEvent = new SessionDisconnectedEvent(this, 'disconnect');
+ this.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]);
+ sessionDisconnectEvent.callDefaultBehavior();
+ } else {
+ console.warn('You were not connected to the session ' + this.sessionId);
+ }
}
- /**
- * Leaves the session, destroying all streams and deleting the user as a participant.
- *
- * #### Events dispatched
- *
- * The {@link Session} object of the local participant will dispatch a `sessionDisconnected` event.
- * This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
- * and also deletes any HTML video element associated to each Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
- * For every video removed, each Subscriber object will dispatch a `videoElementDestroyed` event.
- * Call `event.preventDefault()` upon event `sessionDisconnected` to avoid this behavior and take care of disposing and cleaning all the Subscriber objects yourself.
- * See {@link SessionDisconnectedEvent} and {@link VideoElementEvent} to learn more.
- *
- * The {@link Publisher} object of the local participant will dispatch a `streamDestroyed` event if there is a {@link Publisher} object publishing to the session.
- * This event will automatically stop all media tracks and delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
- * For every video removed, the Publisher object will dispatch a `videoElementDestroyed` event.
- * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session (to do so it is a mandatory requirement to call `Session.unpublish()`
- * or/and `Session.disconnect()` in the previous session). See {@link StreamEvent} and {@link VideoElementEvent} to learn more.
- *
- * The {@link Session} object of every other participant connected to the session will dispatch a `streamDestroyed` event if the disconnected participant was publishing.
- * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
- * and also deletes any HTML video element associated to that Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
- * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
- * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object yourself.
- * See {@link StreamEvent} and {@link VideoElementEvent} to learn more.
- *
- * The {@link Session} object of every other participant connected to the session will dispatch a `connectionDestroyed` event in any case. See {@link ConnectionEvent} to learn more.
- */
- disconnect(): void {
- this.leave(false, 'disconnect');
+ sessionConnected() {
+ return this.room.state === ConnectionState.Connected;
}
subscribe(stream: Stream, targetElement: string | HTMLElement | undefined): Subscriber;
@@ -356,26 +204,23 @@ export class Session extends EventDispatcher {
param3?: ((error: Error | undefined) => void) | SubscriberProperties,
param4?: (error: Error | undefined) => void
): Subscriber {
- console.warn("'subscribe' method is not ready yet.");
- console.log('subscribe', stream, targetElement, param3, param4);
let properties: SubscriberProperties = {};
+ const defaultProperties = {
+ insertMode: VideoInsertMode.APPEND,
+ subscribeToAudio: true,
+ subscribeToVideo: true
+ };
+
if (!!param3 && typeof param3 !== 'function') {
properties = {
- insertMode:
- typeof param3.insertMode !== 'undefined'
- ? typeof param3.insertMode === 'string'
- ? VideoInsertMode[param3.insertMode]
- : properties.insertMode
- : VideoInsertMode.APPEND,
- subscribeToAudio: typeof param3.subscribeToAudio !== 'undefined' ? param3.subscribeToAudio : true,
- subscribeToVideo: typeof param3.subscribeToVideo !== 'undefined' ? param3.subscribeToVideo : true
+ insertMode: typeof param3.insertMode === 'string' ? VideoInsertMode[param3.insertMode] : defaultProperties.insertMode,
+ subscribeToAudio:
+ typeof param3.subscribeToAudio !== 'undefined' ? param3.subscribeToAudio : defaultProperties.subscribeToAudio,
+ subscribeToVideo:
+ typeof param3.subscribeToVideo !== 'undefined' ? param3.subscribeToVideo : defaultProperties.subscribeToVideo
};
} else {
- properties = {
- insertMode: VideoInsertMode.APPEND,
- subscribeToAudio: true,
- subscribeToVideo: true
- };
+ properties = { ...defaultProperties };
}
let completionHandler: (error: Error | undefined) => void = () => {};
@@ -391,28 +236,13 @@ export class Session extends EventDispatcher {
// }
throw this.notConnectedError();
}
-
logger.info('Subscribing to ' + stream.connection.connectionId);
-
- // stream
- // .subscribe()
- // .then(() => {
- // logger.info('Subscribed correctly to ' + stream.connection.connectionId);
- // if (completionHandler !== undefined) {
- // completionHandler(undefined);
- // }
- // })
- // .catch((error) => {
- // if (completionHandler !== undefined) {
- // completionHandler(error);
- // }
- // });
completionHandler(undefined);
const subscriber = new Subscriber(stream, targetElement, properties);
if (!!subscriber.targetElement) {
- stream.streamManager.createVideoElement(subscriber.targetElement, properties.insertMode);
+ throw new Error('It should create video element but it is not implemented yet');
+ // stream.streamManager.createVideoElement(subscriber.targetElement, properties.insertMode);
}
-
return subscriber;
}
@@ -423,459 +253,144 @@ export class Session extends EventDispatcher {
subscribeAsync(stream: Stream, targetElement: string | HTMLElement, properties: SubscriberProperties): Promise;
subscribeAsync(stream: Stream, targetElement: string | HTMLElement, properties?: SubscriberProperties): Promise {
- throw new Error("'subscribeAsync' method is not ready yet.");
- return new Promise((resolve, reject) => {
- if (!this.sessionConnected()) {
- return reject(this.notConnectedError());
- }
-
- let subscriber: Subscriber;
-
- const callback = (error: Error) => {
- if (!!error) {
- return reject(error);
- } else {
- return resolve(subscriber);
- }
- };
-
+ try {
if (!!properties) {
- subscriber = this.subscribe(stream, targetElement, properties, callback);
+ return Promise.resolve(this.subscribe(stream, targetElement, properties));
} else {
- subscriber = this.subscribe(stream, targetElement, callback);
+ return Promise.resolve(this.subscribe(stream, targetElement));
}
- });
+ } catch (error) {
+ return Promise.reject(error);
+ }
}
- /**
- * Unsubscribes from `subscriber`, automatically removing its associated HTML video elements.
- *
- * #### Events dispatched
- *
- * The {@link Subscriber} object will dispatch a `videoElementDestroyed` event for each video associated to it that was removed from DOM.
- * Only videos [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)) will be automatically removed
- *
- * See {@link VideoElementEvent} to learn more
- */
unsubscribe(subscriber: Subscriber): Promise {
throw new Error("'unsubscribe' method is not ready yet.");
- return new Promise((resolve, reject) => {
- if (!this.sessionConnected()) {
- return reject(this.notConnectedError());
- } else {
- const connectionId = subscriber.stream.connection.connectionId;
-
- logger.info('Unsubscribing from ' + connectionId);
-
- this.openvidu.sendRequest(
- 'unsubscribeFromVideo',
- { sender: subscriber.stream.connection.connectionId },
- (error, response) => {
- if (error) {
- logger.error('Error unsubscribing from ' + connectionId);
- return reject(error);
- } else {
- logger.info('Unsubscribed correctly from ' + connectionId);
- subscriber.stream.streamManager.removeAllVideos();
- subscriber.stream.disposeWebRtcPeer();
- subscriber.stream.disposeMediaStream();
- return resolve();
- }
- }
- );
- }
- });
}
- /**
- * Publishes to the Session the Publisher object
- *
- * #### Events dispatched
- *
- * The local {@link Publisher} object will dispatch a `streamCreated` event upon successful termination of this method. See {@link StreamEvent} to learn more.
- *
- * The local {@link Publisher} object will dispatch a `streamPlaying` once the media stream starts playing. See {@link StreamManagerEvent} to learn more.
- *
- * The {@link Session} object of every other participant connected to the session will dispatch a `streamCreated` event so they can subscribe to it. See {@link StreamEvent} to learn more.
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the publisher was successfully published and rejected with an Error object if not
- */
async publish(publisher: Publisher): Promise {
- console.warn('publish not ready yet in Session', this.connection);
-
if (!this.sessionConnected()) {
return Promise.reject(this.notConnectedError());
}
- publisher.session = this;
+ // publisher.session = this;
publisher.stream.session = this;
- try {
- if (publisher.stream.publishedOnce) {
- await publisher.initialize();
- publisher.reestablishStreamPlayingEvent();
- }
- this.connection.addStream(publisher.stream);
- await publisher.stream.publish();
- } catch (error) {
- console.error('error publishing stream', error);
- return Promise.reject(error);
- }
+ this.connection.stream = publisher.stream;
+ await publisher.stream.publish(publisher);
+ }
- console.log('publish', publisher);
+ async unpublish(publisher: Publisher): Promise {
+ throw new Error("'unpublish' method is not ready yet.");
+ }
- return Promise.resolve();
+ forceDisconnect(connection: Connection): Promise {
+ throw new Error("'forceDisconnect' method is not ready yet.");
}
- /**
- * Unpublishes from the Session the Publisher object.
- *
- * #### Events dispatched
- *
- * The {@link Publisher} object of the local participant will dispatch a `streamDestroyed` event.
- * This event will automatically stop all media tracks and delete any HTML video element associated to this Publisher
- * (only those videos [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
- * For every video removed, the Publisher object will dispatch a `videoElementDestroyed` event.
- * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session.
- *
- * The {@link Session} object of every other participant connected to the session will dispatch a `streamDestroyed` event.
- * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) and
- * delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
- * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
- * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object on your own.
- *
- * See {@link StreamEvent} and {@link VideoElementEvent} to learn more.
- */
- async unpublish(publisher: Publisher): Promise {
- // throw new Error("'unpublish' method is not ready yet.");
- // return new Promise((resolve, reject) => {
+ forceUnpublish(stream: Stream): Promise {
+ throw new Error("'forceUnpublish' method is not ready yet.");
+ }
+
+ signal(signal: SignalOptions): Promise {
if (!this.sessionConnected()) {
- throw this.notConnectedError();
+ return Promise.reject(this.notConnectedError());
}
- const stream = publisher.stream;
-
- if (!stream.connection) {
- throw new Error('The associated Connection object of this Publisher is null');
- } else if (stream.connection !== this.connection) {
- throw new Error(
- 'The associated Connection object of this Publisher is not your local Connection. ' +
- "Only moderators can force unpublish on remote Streams via 'forceUnpublish' method"
- );
- } else {
- logger.info('Unpublishing local media (' + stream.connection.connectionId + ')');
-
- // this.openvidu.sendRequest('unpublishVideo', (error, response) => {
- // if (error) {
- // return reject(error);
- // } else {
+ const signalMessage: { data: string; type: string; to: string[] } = {
+ to: [],
+ data: signal.data ? signal.data : '',
+ type: ''
+ };
- let tracks: LocalTrack[] = [];
- this.room.localParticipant.tracks.forEach((track) => {
- if(track.track) {
- tracks.push(track.track);
+ if (signal.to && signal.to.length > 0) {
+ const connectionIds: string[] = [];
+ signal.to.forEach((connection) => {
+ if (!!connection.connectionId) {
+ connectionIds.push(connection.connectionId);
}
});
+ signalMessage['to'] = connectionIds;
+ }
- await this.room.localParticipant.unpublishTracks(tracks);
- logger.info('Media unpublished correctly');
-
- // stream.disposeWebRtcPeer();
-
- if (stream.connection.stream == stream) {
- // The Connection.stream may have changed if Session.publish was called with other Publisher
- delete stream.connection.stream;
+ let typeAux: string = signal.type ? signal.type : 'signal';
+ if (!!typeAux) {
+ if (typeAux.substring(0, 7) !== 'signal:') {
+ typeAux = 'signal:' + typeAux;
}
-
- const streamEvent = new StreamEvent(true, publisher, 'streamDestroyed', publisher.stream, 'unpublish');
- publisher.emitEvent('streamDestroyed', [streamEvent]);
- streamEvent.callDefaultBehavior();
-
- // return resolve();
- // }
- // });
}
- // });
+ signalMessage['type'] = typeAux;
+
+ const data: Uint8Array = new TextEncoder().encode(signalMessage.data);
+ const kind: DataPacket_Kind = DataPacket_Kind.RELIABLE;
+ const publishOptions: DataPublishOptions = {
+ topic: signalMessage.type,
+ destination: signalMessage.to
+ };
+ return this.room.localParticipant.publishData(data, kind, publishOptions);
}
/**
- * Forces some user to leave the session
- *
- * #### Events dispatched
- *
- * The behavior is the same as when some user calls {@link Session.disconnect}, but `reason` property in all events will be `"forceDisconnectByUser"`.
- *
- * The {@link Session} object of every participant will dispatch a `streamDestroyed` event if the evicted user was publishing a stream, with property `reason` set to `"forceDisconnectByUser"`.
- * The {@link Session} object of every participant except the evicted one will dispatch a `connectionDestroyed` event for the evicted user, with property `reason` set to `"forceDisconnectByUser"`.
- *
- * If any, the {@link Publisher} object of the evicted participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`.
- * The {@link Session} object of the evicted participant will dispatch a `sessionDisconnected` event with property `reason` set to `"forceDisconnectByUser"`.
- *
- * See {@link StreamEvent}, {@link ConnectionEvent} and {@link SessionDisconnectedEvent} to learn more.
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the participant has been successfully evicted from the session and rejected with an Error object if not
+ * See {@link EventDispatcher.on}
*/
- forceDisconnect(connection: Connection): Promise {
- throw new Error("'forceDisconnect' method is not ready yet.");
- return new Promise((resolve, reject) => {
- if (!this.sessionConnected()) {
- return reject(this.notConnectedError());
- }
+ on(type: K, handler: (event: SessionEventMap[K]) => void): this {
+ super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler);
- logger.info('Forcing disconnect for connection ' + connection.connectionId);
- this.openvidu.sendRequest('forceDisconnect', { connectionId: connection.connectionId }, (error, response) => {
- if (error) {
- logger.error('Error forcing disconnect for Connection ' + connection.connectionId, error);
- if (error.code === 401) {
- return reject(
- new OpenViduError(
- OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
- "You don't have permissions to force a disconnection"
- )
- );
- } else {
- return reject(error);
- }
- } else {
- logger.info('Forcing disconnect correctly for Connection ' + connection.connectionId);
- return resolve();
- }
- });
- });
- }
-
- /**
- * Forces some user to unpublish a Stream
- *
- * #### Events dispatched
- *
- * The behavior is the same as when some user calls {@link Session.unpublish}, but `reason` property in all events will be `"forceUnpublishByUser"`
- *
- * The {@link Session} object of every participant will dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
- *
- * The {@link Publisher} object of the affected participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
- *
- * See {@link StreamEvent} to learn more.
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the remote Stream has been successfully unpublished from the session and rejected with an Error object if not
- */
- forceUnpublish(stream: Stream): Promise {
- throw new Error("'forceUnpublish' method is not ready yet.");
- return new Promise((resolve, reject) => {
- if (!this.sessionConnected()) {
- return reject(this.notConnectedError());
- }
-
- logger.info('Forcing unpublish for stream ' + stream.streamId);
- this.openvidu.sendRequest('forceUnpublish', { streamId: stream.streamId }, (error, response) => {
- if (error) {
- logger.error('Error forcing unpublish for Stream ' + stream.streamId, error);
- if (error.code === 401) {
- return reject(
- new OpenViduError(
- OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
- "You don't have permissions to force an unpublishing"
- )
- );
- } else {
- return reject(error);
- }
- } else {
- logger.info('Forcing unpublish correctly for Stream ' + stream.streamId);
- return resolve();
- }
- });
- });
- }
-
- /**
- * Sends one signal. `signal` object has the following optional properties:
- * ```json
- * {data:string, to:Connection[], type:string}
- * ```
- * All users subscribed to that signal (`session.on('signal:type', ...)` or `session.on('signal', ...)` for all signals) and whose Connection objects are in `to` array will receive it. Their local
- * Session objects will dispatch a `signal` or `signal:type` event. See {@link SignalEvent} to learn more.
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved if the message successfully reached openvidu-server and rejected with an Error object if not. _This doesn't
- * mean that openvidu-server could resend the message to all the listed receivers._
- */
- signal(signal: SignalOptions): Promise {
- throw new Error("'signal' method is not ready yet.");
- return new Promise((resolve, reject) => {
- if (!this.sessionConnected()) {
- return reject(this.notConnectedError());
- }
-
- const signalMessage = {};
-
- if (signal.to && signal.to.length > 0) {
- const connectionIds: string[] = [];
- signal.to.forEach((connection) => {
- if (!!connection.connectionId) {
- connectionIds.push(connection.connectionId);
- }
- });
- signalMessage['to'] = connectionIds;
- } else {
- signalMessage['to'] = [];
- }
-
- signalMessage['data'] = signal.data ? signal.data : '';
-
- let typeAux: string = signal.type ? signal.type : 'signal';
- if (!!typeAux) {
- if (typeAux.substring(0, 7) !== 'signal:') {
- typeAux = 'signal:' + typeAux;
- }
- }
- signalMessage['type'] = typeAux;
-
- this.openvidu.sendRequest(
- 'sendMessage',
- {
- message: JSON.stringify(signalMessage)
- },
- (error, response) => {
- if (!!error) {
- return reject(error);
- } else {
- return resolve();
- }
- }
- );
- });
- }
-
- /**
- * Subscribe to the Speech-To-Text events for this {@link Stream}. The Session object will emit {@link SpeechToTextEvent} for the Stream
- * when speech is detected in its audio track.
- *
- * @param stream - The Stream for which you want to start receiving {@link SpeechToTextEvent}.
- * @param lang - The language of the Stream's audio track. It must be a valid [BCP-47](https://tools.ietf.org/html/bcp47) language tag like "en-US" or "es-ES".
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved if the speech-to-text subscription
- * was successful and rejected with an Error object if not.
- */
- subscribeToSpeechToText(stream: Stream, lang: string): Promise {
- throw new Error("'subscribeToSpeechToText' method is not ready yet.");
- return new Promise((resolve, reject) => {
- this.openvidu.sendRequest(
- 'subscribeToSpeechToText',
- {
- connectionId: stream.connection.connectionId,
- lang
- },
- (error, response) => {
- if (!!error) {
- return reject(error);
- } else {
- return resolve();
- }
- }
- );
- });
- }
-
- /**
- * Unsubscribe from the Speech-To-Text events for this {@link Stream}.
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved if the speech-to-text subscription
- * was successful and rejected with an Error object if not.
- */
- unsubscribeFromSpeechToText(stream: Stream): Promise {
- throw new Error("'unsubscribeFromSpeechToText' method is not ready yet.");
- return new Promise((resolve, reject) => {
- this.openvidu.sendRequest(
- 'unsubscribeFromSpeechToText',
- {
- connectionId: stream.connection.connectionId
- },
- (error, response) => {
- if (!!error) {
- return reject(error);
- } else {
- return resolve();
- }
- }
- );
- });
- }
-
- /**
- * See {@link EventDispatcher.on}
- */
- on(type: K, handler: (event: SessionEventMap[K]) => void): this {
- super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler);
+ const isListeningOnce: boolean = false;
+ let eventName!: RoomEvent;
+ let myHandler!: Function;
switch (type) {
case 'connectionCreated':
- this.room.on(RoomEvent.ParticipantConnected, (participant: RemoteParticipant) => {
+ myHandler = (participant: RemoteParticipant) => {
console.log('participant connected', participant);
// TODO invoke handler
console.warn('PARTICIPANT CONNECTED event must be linked with connectionCreated');
// this.onParticipantJoined(participant);
- });
+
+ // handler();
+ };
+ eventName = RoomEvent.ParticipantConnected;
+
break;
+
case 'connectionDestroyed':
- this.room.on(RoomEvent.ParticipantDisconnected, (participant: RemoteParticipant) => {
- console.log('participant disconnected', participant);
- // TODO invoke handler
- console.warn('PARTICIPANT DISCONNECTED event must be linked with connectionDestroyed');
- this.onParticipantLeft(participant);
- });
+ eventName = RoomEvent.ParticipantDisconnected;
break;
case 'sessionDisconnected':
- this.room.on(RoomEvent.Disconnected, () => {
- console.log('disconnected');
+ myHandler = (participant: RemoteParticipant) => {
+ console.log('participant disconnected', participant);
// TODO invoke handler
- console.warn('DISCONNECTED event must be linked with sessionDisconnected');
- });
+ console.warn('PARTICIPANT DISCONNECTED event must be linked with connectionDestroyed');
+ // this.onParticipantLeft(participant);
+ // handler();
+ };
+ eventName = RoomEvent.Disconnected;
break;
case 'streamCreated':
- this.room.on(
- RoomEvent.TrackSubscribed,
- (track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant) => {
- this.temporalPublications.set(`${participant.sid}-${publication.kind}`, publication);
- if (this.streamCreatedInterval) {
- console.debug('streamCreatedInterval already set. Skipped');
- return;
- }
- console.log('track published', publication, participant);
- // TODO the publication doesn't have the source track value properly. Its value is 'unknown'
- // TODO the participant shows the microphone and camera enabled to false. Maybe the unknown source is the reason
-
- // console.log('streamManagers', this.streamManagers);
- // const s = this.streamManagers.find((sm) => sm.id === publication.);
-
- this.streamCreatedInterval = setTimeout(async () => {
- console.warn('TRACK PUBLISHED event must be linked with streamCreated');
- this.streamCreatedInterval = undefined;
- this.onParticipantPublished(participant);
- }, 200);
- }
- );
+ myHandler = async (track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant) => {
+ //TODO: maybe this is not needed
+ this.temporalPublications.set(`${participant.sid}-${publication.kind}`, publication);
+ console.warn('stream created', track, publication, participant);
+ await this.onParticipantPublished(participant);
+ // const connection = this.remoteConnections.get(`CON_${participant.sid}`);
+ // if (connection) {
+ // const event: StreamEvent = new StreamEvent(false, this, 'streamCreated', connection.stream, '');
+ // handler(event as SessionEventMap[K]);
+ // }
+ };
+ eventName = RoomEvent.TrackSubscribed;
break;
case 'streamDestroyed':
- this.room.on(
- RoomEvent.TrackUnsubscribed,
- (track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant) => {
- if (this.streamDestroyedInterval) {
- console.debug('streamDestroyedInterval already set. Skipped');
- return;
- }
- console.log('track unpublished', publication, participant);
- // TODO invoke handler
- console.warn('TRACK UNPUBLISHED event must be linked with streamDestroyed');
-
- this.streamDestroyedInterval = setTimeout(async () => {
- console.warn('TRACK UNPUBLISHED event must be linked with streamDestroyed');
- this.streamDestroyedInterval = undefined;
- this.onParticipantUnpublished(participant);
- }, 200);
- }
- );
+ myHandler = (participant: RemoteParticipant) => {
+ console.log('participant disconnected', participant);
+ // TODO invoke handler
+ console.warn('PARTICIPANT DISCONNECTED event must be linked with connectionDestroyed');
+ // this.onParticipantLeft(participant);
+ // handler();
+ };
+ eventName = RoomEvent.TrackUnsubscribed;
break;
-
// case 'signal':
// break;
// case 'recordingStarted':
@@ -887,21 +402,24 @@ export class Session extends EventDispatcher {
// case 'connectionPropertyChanged':
// break;
case 'networkQualityLevelChanged':
- this.room.on(RoomEvent.ConnectionQualityChanged, (quality: ConnectionQuality, participant: Participant) => {
+ myHandler = (quality: ConnectionQuality, participant: Participant) => {
+ console.log('network quality changed', quality);
const oldValue = this.networkQualityLevelValue;
this.processNetworkQualityValue(quality);
-
- const openviduEvent: { connectionId: string; newValue: number; oldValue: number } = {
- connectionId: participant.sid,
- newValue: this.networkQualityLevelValue,
- oldValue
- };
- this.onNetworkQualityLevelChangedChanged(openviduEvent);
- });
+ // TODO invoke handler
+ // const openviduEvent: { connectionId: string; newValue: number; oldValue: number } = {
+ // connectionId: participant.sid,
+ // newValue: this.networkQualityLevelValue,
+ // oldValue
+ // };
+ // this.onNetworkQualityLevelChangedChanged(openviduEvent);
+ // handler();
+ };
+ eventName = RoomEvent.ConnectionQualityChanged;
break;
case 'exception':
console.warn('exception event not implemented in SessionAdapter');
- break;
+ return this;
// case 'publisherStartSpeaking':
// this.room.on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {
@@ -925,1191 +443,135 @@ export class Session extends EventDispatcher {
default:
console.warn('on', type, 'not implemented');
- break;
+ return this;
}
- return this;
- // throw new Error("'on' method is not ready yet.");
- // super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler);
-
- // if (type === 'publisherStartSpeaking') {
- // // If there are already available remote streams with audio, enable hark 'speaking' event in all of them
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream?.hasAudio) {
- // remoteConnection.stream.enableHarkSpeakingEvent();
- // }
- // });
- // if (!!this.connection?.stream?.hasAudio) {
- // // If connected to the Session and publishing with audio, also enable hark 'speaking' event for the Publisher
- // this.connection.stream.enableHarkSpeakingEvent();
- // }
- // }
- // if (type === 'publisherStopSpeaking') {
- // // If there are already available remote streams with audio, enable hark 'stopped_speaking' event in all of them
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream?.hasAudio) {
- // remoteConnection.stream.enableHarkStoppedSpeakingEvent();
- // }
- // });
- // if (!!this.connection?.stream?.hasAudio) {
- // // If connected to the Session and publishing with audio, also enable hark 'stopped_speaking' event for the Publisher
- // this.connection.stream.enableHarkStoppedSpeakingEvent();
- // }
- // }
+ this.handleEvent(eventName, myHandler.bind(this), isListeningOnce);
- // return this;
+ return this;
}
-
/**
* See {@link EventDispatcher.once}
*/
once(type: K, handler: (event: SessionEventMap[K]) => void): this {
- throw new Error("'once' method is not ready yet.");
- // super.onceAux(type, "Event '" + type + "' triggered once by 'Session'", handler);
-
- // if (type === 'publisherStartSpeaking') {
- // // If there are already available remote streams with audio, enable hark 'speaking' event (once) in all of them once
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream?.hasAudio) {
- // remoteConnection.stream.enableOnceHarkSpeakingEvent();
- // }
- // });
- // if (!!this.connection?.stream?.hasAudio) {
- // // If connected to the Session and publishing with audio, also enable hark 'speaking' event (once) for the Publisher
- // this.connection.stream.enableOnceHarkSpeakingEvent();
- // }
- // }
- // if (type === 'publisherStopSpeaking') {
- // // If there are already available remote streams with audio, enable hark 'stopped_speaking' event (once) in all of them once
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream?.hasAudio) {
- // remoteConnection.stream.enableOnceHarkStoppedSpeakingEvent();
- // }
- // });
- // if (!!this.connection?.stream?.hasAudio) {
- // // If connected to the Session and publishing with audio, also enable hark 'stopped_speaking' event (once) for the Publisher
- // this.connection.stream.enableOnceHarkStoppedSpeakingEvent();
- // }
- // }
-
- // return this;
- }
-
- /**
- * See {@link EventDispatcher.off}
- */
- off(type: K, handler?: (event: SessionEventMap[K]) => void): this {
- switch (type) {
- // case 'connectionCreated':
- // break;
- // case 'connectionDestroyed':
- // break;
- case 'sessionDisconnected':
- this.room?.off(RoomEvent.Disconnected, () => {
- console.log('disconnected OFF');
- });
- break;
- case 'streamCreated':
- this.room?.off(RoomEvent.TrackPublished, () => {
- console.log('streamCreated OFF');
- });
- break;
- case 'streamDestroyed':
- this.room?.off(RoomEvent.TrackUnpublished, () => {
- console.warn('streamDestroyed OFF');
- });
- break;
- // case 'signal':
- // break;
- // case 'recordingStarted':
- // break;
- // case 'recordingStopped':
- // break;
- // case 'streamPropertyChanged':
- // break;
- // case 'connectionPropertyChanged':
- // break;
- case 'networkQualityLevelChanged':
- this.room?.off(RoomEvent.ConnectionQualityChanged, () => {
- console.warn('networkQualityLevelChanged OFF');
- });
- break;
- // case 'exception':
- // break;
- // case 'publisherStartSpeaking':
- // break;
- // case 'publisherStopSpeaking':
- // break;
- // case 'broadcastStarted':
- // break;
- // case 'broadcastStopped':
- // break;
- // case 'speechToTextMessage':
- // break;
- // case 'reconnecting':
- // break;
-
- // case 'reconnected':
- // break;
+ super.onceAux(type, "Event '" + type + "' triggered by 'Session'", handler);
- default:
- console.warn('OFF', type, 'not implemented');
- break;
- }
return this;
-
- // throw new Error("'off' method is not ready yet.");
- // super.offAux(type, handler);
-
- // if (type === 'publisherStartSpeaking') {
- // // Check if Session object still has some listener for the event
- // if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false)) {
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream?.streamManager) {
- // // Check if Subscriber object still has some listener for the event
- // if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false, remoteConnection.stream.streamManager)) {
- // remoteConnection.stream.disableHarkSpeakingEvent(false);
- // }
- // }
- // });
- // if (!!this.connection?.stream?.streamManager) {
- // // Check if Publisher object still has some listener for the event
- // if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false, this.connection.stream.streamManager)) {
- // this.connection.stream.disableHarkSpeakingEvent(false);
- // }
- // }
- // }
- // }
- // if (type === 'publisherStopSpeaking') {
- // // Check if Session object still has some listener for the event
- // if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false)) {
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream?.streamManager) {
- // // Check if Subscriber object still has some listener for the event
- // if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false, remoteConnection.stream.streamManager)) {
- // remoteConnection.stream.disableHarkStoppedSpeakingEvent(false);
- // }
- // }
- // });
- // if (!!this.connection?.stream?.streamManager) {
- // // Check if Publisher object still has some listener for the event
- // if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false, this.connection.stream.streamManager)) {
- // this.connection.stream.disableHarkStoppedSpeakingEvent(false);
- // }
- // }
- // }
- // }
- // return this;
- }
-
- /* Hidden methods */
-
- /**
- * @hidden
- */
- onParticipantJoined(event: RemoteConnectionOptions): void {
- throw new Error("'onParticipantJoined' method is not ready yet.");
- // Connection shouldn't exist
- this.getConnection(event.id, '')
- .then((connection) => {
- logger.warn('Connection ' + connection.connectionId + ' already exists in connections list');
- })
- .catch((openViduError) => {
- const connection = new Connection(this, event);
- this.remoteConnections.set(event.id, connection);
- this.ee.emitEvent('connectionCreated', [new ConnectionEvent(false, this, 'connectionCreated', connection, '')]);
- });
- }
-
- /**
- * @hidden
- */
- onParticipantLeft(participant: RemoteParticipant /*event: { connectionId: string; reason: ConnectionEventReason }*/): void {
- const connectionId = participant.sid; //??
- // throw new Error("'onParticipantLeft' method is not ready yet.");
- this.getRemoteConnection(connectionId, 'onParticipantLeft')
- .then((connection) => {
- if (!!connection.stream) {
- const stream = connection.stream;
-
- const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, 'disconnect');
- this.ee.emitEvent('streamDestroyed', [streamEvent]);
- streamEvent.callDefaultBehavior();
-
- this.remoteStreamsCreated.delete(stream.streamId);
- }
- connection.dispose();
- this.remoteConnections.delete(connection.connectionId);
- this.ee.emitEvent('connectionDestroyed', [
- new ConnectionEvent(false, this, 'connectionDestroyed', connection, 'disconnect')
- ]);
- })
- .catch((openViduError) => {
- logger.error(openViduError);
- });
}
-
/**
- * @hidden
+ * See {@link EventDispatcher.off}
*/
- async onParticipantPublished(participant: RemoteParticipant): Promise {
- const videoPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-video`);
- const audioPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-audio`);
-
- let createdAt = new Date().getTime();
- if (participant?.['participantInfo']) {
- createdAt = Number(BigInt(participant['participantInfo'].joinedAt)) * 1000; // miliseconds
- }
-
- let videoDimensions = '';
- let typeOfVideo = TypeOfVideo.CAMERA;
- if (videoPublication?.videoTrack && videoPublication.videoTrack.mediaStreamTrack) {
- const { width, height } = videoPublication.videoTrack.mediaStreamTrack.getSettings();
- videoDimensions = JSON.stringify({ height, width });
- //TODO videoPublication.source is always 'unknown'
- // typeOfVideo = videoPublication.source === Track.Source.Camera ? TypeOfVideo.CAMERA : TypeOfVideo.SCREEN;
- }
-
- const streams: StreamOptionsServer[] = [
- {
- id: participant.sid,
- createdAt: createdAt,
- hasAudio: audioPublication !== undefined,
- hasVideo: videoPublication !== undefined,
- audioActive: audioPublication ? audioPublication.isEnabled : false,
- videoActive: videoPublication ? videoPublication.isEnabled : false,
- typeOfVideo,
- frameRate: 30,
- videoDimensions
- // filter: ,
- }
- ];
-
- const opt: RemoteConnectionOptions = {
- createdAt,
- metadata: participant?.metadata || '',
- streams,
- id: participant.sid
- };
-
- // Get the existing Connection created on 'onParticipantJoined' for
- // existing participants or create a new one for new participants
- let connection!: Connection;
- try {
- // Update existing Connection
-
- connection = await this.getRemoteConnection(opt.id, 'onParticipantPublished');
- connection.stream;
- opt.metadata = connection.data;
- connection.remoteOptions = opt;
- connection.initRemoteStreams(opt.streams);
- // afterConnectionFound(connection);
- } catch (error) {
- // Create new Connection
- connection = new Connection(this, opt);
- // afterConnectionFound(connection);
- } finally {
- // if (!connection) {
- // connection = new Connection(this, opt);
- // }
-
- // const mediaStream = new MediaStream();
- // if (videoPublication?.videoTrack?.mediaStreamTrack) {
- // mediaStream.addTrack(videoPublication.videoTrack.mediaStreamTrack);
- // }
- // if (audioPublication?.audioTrack?.mediaStreamTrack) {
- // mediaStream.addTrack(audioPublication.audioTrack.mediaStreamTrack);
- // }
-
- // connection.stream?.setMediaStream(mediaStream);
- if (connection.stream) {
- if (videoPublication?.videoTrack) {
- connection.stream.temporalVideoTrack = videoPublication.videoTrack as RemoteVideoTrack;
- }
-
- if (audioPublication?.audioTrack) {
- connection.stream.temporalAudioTrack = audioPublication.audioTrack as RemoteAudioTrack;
- }
- }
-
- this.remoteConnections.set(connection.connectionId, connection);
-
- if (connection.stream?.streamId) {
- if (!this.remoteStreamsCreated.get(connection.stream.streamId)) {
- // Avoid race condition between stream.subscribe() in "onParticipantPublished" and in "joinRoom" rpc callback
- // This condition is false if openvidu-server sends "participantPublished" event to a subscriber participant that has
- // already subscribed to certain stream in the callback of "joinRoom" method
-
- this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', connection.stream, '')]);
- }
-
- this.remoteStreamsCreated.set(connection.stream.streamId, true);
- }
- }
+ off(type: K, handler?: (event: SessionEventMap[K]) => void): this {
+ throw new Error('Method not implemented.');
}
- /**
- * @hidden
- */
- onParticipantUnpublished(participant: RemoteParticipant /*, event: { connectionId: string; reason: StreamEventReason }*/): void {
- // const videoPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-video`);
- // const audioPublication: RemoteTrackPublication | undefined = this.temporalPublications.get(`${participant.sid}-audio`);
- this.temporalPublications.delete(`${participant.sid}-video`);
- this.temporalPublications.delete(`${participant.sid}-audio`);
- const connectionId = participant.sid; //??
-
- // throw new Error("'onParticipantUnpublished' method is not ready yet.");
- if (connectionId === this.connection.connectionId) {
- // Your stream has been forcedly unpublished from the session
- this.stopPublisherStream('unpublish');
+ private handleEvent(eventName: RoomEvent, handler: any, once: boolean): void {
+ if (once) {
+ this.room.once(eventName, handler);
} else {
- this.getRemoteConnection(connectionId, 'onParticipantUnpublished')
-
- .then((connection) => {
- const streamEvent = new StreamEvent(true, this, 'streamDestroyed', connection.stream!, 'unpublish');
- this.ee.emitEvent('streamDestroyed', [streamEvent]);
- streamEvent.callDefaultBehavior();
-
- // Deleting the remote stream if it exists
- if (connection.stream != null) {
- const streamId: string = connection.stream!.streamId;
- this.remoteStreamsCreated.delete(streamId);
- connection.removeStream();
- }
- })
- .catch((openViduError) => {
- logger.error(openViduError);
- });
+ this.room.on(eventName, handler);
}
}
/**
* @hidden
*/
- onParticipantEvicted(event: { connectionId: string; reason: ConnectionEventReason }): void {
- throw new Error("'onParticipantEvicted' method is not ready yet.");
- if (event.connectionId === this.connection.connectionId) {
- // You have been evicted from the session
- if (!!this.sessionId && !this.connection.disposed) {
- this.leave(true, event.reason);
- }
+ private async onParticipantPublished(participant: RemoteParticipant): Promise {
+ let connection: Connection = new Connection(this, participant);
+ if (this.remoteConnections.has(`CON_${participant.sid}`)) {
+ /**
+ * Assign the stored streamId to the new connection
+ * New connection is created for complete the participant object because of livekit emits
+ * events of each track published by the participant
+ */
+ const connectionStored = this.remoteConnections.get(`CON_${participant.sid}`)!;
+ connection.updateStreamId(connectionStored.stream.streamId);
}
- }
-
- /**
- * @hidden
- */
- onNewMessage(event: { type?: string; data?: string; from?: string }): void {
- throw new Error("'onNewMessage' method is not ready yet.");
- // logger.info('New signal: ' + JSON.stringify(event));
-
- // const strippedType = !!event.type ? event.type.replace(/^(signal:)/, '') : undefined;
-
- // if (!!event.from) {
- // // Signal sent by other client
- // this.getConnection(
- // event.from,
- // "Connection '" +
- // event.from +
- // "' unknown when 'onNewMessage'. Existing remote connections: " +
- // JSON.stringify(this.remoteConnections.keys()) +
- // '. Existing local connection: ' +
- // this.connection.connectionId
- // )
-
- // .then((connection) => {
- // this.ee.emitEvent('signal', [new SignalEvent(this, strippedType, event.data, connection)]);
- // if (!!event.type && event.type !== 'signal') {
- // this.ee.emitEvent(event.type, [new SignalEvent(this, strippedType, event.data, connection)]);
- // }
- // })
- // .catch((openViduError) => {
- // logger.error(openViduError);
- // });
- // } else {
- // // Signal sent by server
- // this.ee.emitEvent('signal', [new SignalEvent(this, strippedType, event.data, undefined)]);
- // if (!!event.type && event.type !== 'signal') {
- // this.ee.emitEvent(event.type, [new SignalEvent(this, strippedType, event.data, undefined)]);
- // }
- // }
- }
- /**
- * @hidden
- */
- onStreamPropertyChanged(event: {
- connectionId: string;
- streamId: string;
- property: ChangedPropertyType;
- newValue: any;
- reason: StreamPropertyChangedEventReason;
- }): void {
- throw new Error("'onStreamPropertyChanged' method is not ready yet.");
- const callback = (connection: Connection) => {
- if (!!connection.stream && connection.stream.streamId === event.streamId) {
- const stream = connection.stream;
- let oldValue;
- switch (event.property) {
- case 'audioActive':
- oldValue = stream.audioActive;
- event.newValue = event.newValue === 'true';
- stream.audioActive = event.newValue;
- break;
- case 'videoActive':
- oldValue = stream.videoActive;
- event.newValue = event.newValue === 'true';
- stream.videoActive = event.newValue;
- const videoTrack = stream?.getMediaStream()?.getVideoTracks()?.[0];
- if (videoTrack && !videoTrack.enabled && stream.videoActive) {
- videoTrack.enabled = true;
- }
- break;
- case 'videoTrack':
- event.newValue = JSON.parse(event.newValue);
- break;
- case 'audioTrack':
- event.newValue = JSON.parse(event.newValue);
- break;
- case 'videoDimensions':
- oldValue = stream.videoDimensions;
- event.newValue = JSON.parse(JSON.parse(event.newValue));
- stream.videoDimensions = event.newValue;
- break;
- case 'filter':
- oldValue = stream.filter;
- event.newValue = Object.keys(event.newValue).length > 0 ? event.newValue : undefined;
- if (event.newValue !== undefined) {
- stream.filter = new Filter(event.newValue.type, event.newValue.options);
- stream.filter.stream = stream;
- if (event.newValue.lastExecMethod) {
- stream.filter.lastExecMethod = event.newValue.lastExecMethod;
- }
- } else {
- delete stream.filter;
- }
- event.newValue = stream.filter;
- break;
- }
- this.ee.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this, stream, event.property, event.newValue, oldValue, event.reason)
- ]);
- if (!!stream.streamManager) {
- stream.streamManager.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(stream.streamManager, stream, event.property, event.newValue, oldValue, event.reason)
- ]);
- }
- } else {
- logger.error(
- "No stream with streamId '" +
- event.streamId +
- "' found for connection '" +
- event.connectionId +
- "' on 'streamPropertyChanged' event"
- );
+ this.remoteConnections.set(connection.connectionId, connection);
+
+ if (connection?.stream?.streamId) {
+ //TODO: Deberia enviarse el evento una vez el participante se haya formado totalmente
+ console.log('setting streamCreated interval', connection.stream.streamId);
+ const streamEvent = new StreamEvent(false, this, 'streamCreated', connection.stream, '');
+ const isEventIntervalSet = this.participantEventsIntervals.get(connection.stream.streamId);
+ if (isEventIntervalSet) {
+ clearTimeout(isEventIntervalSet);
+ this.participantEventsIntervals.delete(connection.stream.streamId);
+ this.ee.emitEvent('streamCreated', [streamEvent]);
+ return;
}
- };
-
- if (event.connectionId === this.connection.connectionId) {
- // Your stream has been forcedly changed (filter feature)
- callback(this.connection);
- } else {
- this.getRemoteConnection(event.connectionId, 'onStreamPropertyChanged')
- .then((connection) => {
- callback(connection);
- })
- .catch((openViduError) => {
- logger.error(openViduError);
- });
- }
- }
- /**
- * @hidden
- */
- onConnectionPropertyChanged(event: { property: string; newValue: any }): void {
- throw new Error("'onConnectionPropertyChanged' method is not ready yet.");
- let oldValue;
- switch (event.property) {
- case 'role':
- oldValue = this.connection.role.slice();
- this.connection.role = event.newValue;
- this.connection.localOptions!.role = event.newValue;
- break;
- case 'record':
- oldValue = this.connection.record;
- event.newValue = event.newValue === 'true';
- this.connection.record = event.newValue;
- this.connection.localOptions!.record = event.newValue;
- break;
+ //! TODO: Esta solucion es flacky, es posible que la primera vez que se le pidan persmisos de media al usuario,
+ //! este tarde mas de 1 segundo en responder, por lo que el evento de streamCreated se emitiria antes de que el
+ const interval = setTimeout(() => {
+ this.ee.emitEvent('streamCreated', [streamEvent]);
+ }, 1000);
+ this.participantEventsIntervals.set(connection.stream.streamId, interval);
}
- this.ee.emitEvent('connectionPropertyChanged', [
- new ConnectionPropertyChangedEvent(this, this.connection, event.property, event.newValue, oldValue)
- ]);
}
/**
- * @hidden
+ * Processes the session connected event.
+ * Initializes remote connections and emits corresponding events.
*/
- onNetworkQualityLevelChangedChanged(event: { connectionId: string; newValue: number; oldValue: number }): void {
-
- if (event.connectionId === this.connection.connectionId) {
- this.ee.emitEvent('networkQualityLevelChanged', [
- new NetworkQualityLevelChangedEvent(this, event.newValue, event.oldValue, this.connection)
- ]);
- } else {
- this.getConnection(event.connectionId, 'Connection not found for connectionId ' + event.connectionId)
- .then((connection: Connection) => {
- this.ee.emitEvent('networkQualityLevelChanged', [
- new NetworkQualityLevelChangedEvent(this, event.newValue, event.oldValue, connection)
- ]);
- })
- .catch((openViduError) => {
- logger.error(openViduError);
- });
- }
- }
-
- /**
- * @hidden
- */
- recvIceCandidate(event: {
- senderConnectionId: string;
- endpointName: string;
- sdpMLineIndex: number;
- sdpMid: string;
- candidate: string;
- }): void {
- throw new Error("'recvIceCandidate' method is not ready yet.");
- // The event contains fields that can be used to obtain a proper candidate,
- // using the RTCIceCandidate constructor:
- // https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-constructor
- // const candidateInit: RTCIceCandidateInit = {
- // candidate: event.candidate,
- // sdpMLineIndex: event.sdpMLineIndex,
- // sdpMid: event.sdpMid
- // };
- // const iceCandidate = new RTCIceCandidate(candidateInit);
-
- // this.getConnection(
- // event.senderConnectionId,
- // 'Connection not found for connectionId ' +
- // event.senderConnectionId +
- // ' owning endpoint ' +
- // event.endpointName +
- // '. Ice candidate will be ignored: ' +
- // iceCandidate
- // )
- // .then((connection) => {
- // const stream: Stream = connection.stream!;
- // stream
- // .getWebRtcPeer()
- // .addIceCandidate(iceCandidate)
- // .catch((error) => {
- // logger.error(
- // 'Error adding candidate for ' + stream!.streamId + ' stream of endpoint ' + event.endpointName + ': ' + error
- // );
- // });
- // })
- // .catch((openViduError) => {
- // logger.error(openViduError);
- // });
- }
-
- /**
- * @hidden
- */
- onSessionClosed(msg): void {
- throw new Error("'onSessionClosed' method is not ready yet.");
- logger.info('Session closed: ' + JSON.stringify(msg));
- const s = msg.sessionId;
- if (s !== undefined) {
- this.ee.emitEvent('session-closed', [
- {
- session: s
- }
- ]);
- } else {
- logger.warn('Session undefined on session closed', msg);
- }
- }
-
- /**
- * @hidden
- */
- onLostConnection(reason: ConnectionEventReason): void {
- throw new Error("'onLostConnection' method is not ready yet.");
- logger.warn('Lost connection in Session ' + this.sessionId);
- if (!!this.sessionId && !!this.connection && !this.connection.disposed) {
- this.leave(true, reason);
- }
- }
-
- /**
- * @hidden
- */
- onRecoveredConnection(): void {
- throw new Error("'onRecoveredConnection' method is not ready yet.");
- logger.info('Recovered connection in Session ' + this.sessionId);
- this.reconnectBrokenStreams();
- this.ee.emitEvent('reconnected', []);
- }
-
- /**
- * @hidden
- */
- onMediaError(event: { error: string }): void {
- throw new Error("'onMediaError' method is not ready yet.");
- logger.error('Media error: ' + JSON.stringify(event));
- const err = event.error;
- if (err) {
- this.ee.emitEvent('error-media', [{ error: err }]);
- } else {
- logger.warn('Received undefined media error:', event);
- }
- }
-
- /**
- * @hidden
- */
- onRecordingStarted(event: { id: string; name: string }): void {
- throw new Error("'onRecordingStarted' method is not ready yet.");
- this.ee.emitEvent('recordingStarted', [new RecordingEvent(this, 'recordingStarted', event.id, event.name)]);
- }
-
- /**
- * @hidden
- */
- onRecordingStopped(event: { id: string; name: string; reason: RecordingEventReason }): void {
- throw new Error("'onRecordingStopped' method is not ready yet.");
- this.ee.emitEvent('recordingStopped', [new RecordingEvent(this, 'recordingStopped', event.id, event.name, event.reason)]);
- }
-
- /**
- * @hidden
- */
- onBroadcastStarted(): void {
- throw new Error("'onBroadcastStarted' method is not ready yet.");
- this.ee.emitEvent('broadcastStarted', []);
- }
-
- /**
- * @hidden
- */
- onBroadcastStopped(): void {
- throw new Error("'onBroadcastStopped' method is not ready yet.");
- this.ee.emitEvent('broadcastStopped', []);
- }
-
- /**
- * @hidden
- */
- onFilterEventDispatched(event: { connectionId: string; streamId: string; filterType: string; eventType: string; data: string }): void {
- throw new Error("'onFilterEventDispatched' method is not ready yet.");
- const connectionId: string = event.connectionId;
- this.getConnection(connectionId, 'No connection found for connectionId ' + connectionId).then((connection) => {
- logger.info(`Filter event of type "${event.eventType}" dispatched`);
- const stream: Stream = connection.stream!;
- if (!stream || !stream.filter) {
- return logger.error(
- `Filter event of type "${event.eventType}" dispatched for stream ${stream.streamId} but there is no ${
- !stream ? 'stream' : 'filter'
- } defined`
- );
- }
- const eventHandler = stream.filter.handlers.get(event.eventType);
- if (!eventHandler || typeof eventHandler !== 'function') {
- const actualHandlers: string[] = Array.from(stream.filter.handlers.keys());
- return logger.error(
- `Filter event of type "${event.eventType}" not handled or not a function! Active filter events: ${actualHandlers.join(
- ','
- )}`
- );
- } else {
- eventHandler.call(this, new FilterEvent(stream.filter, event.eventType, event.data));
+ private processSessionConnected() {
+ // Initialize remote Connections with value returned by openvidu-server
+ const events = {
+ connections: new Array(),
+ streams: new Array()
+ };
+ const remoteParticipants: Map = this.room.participants;
+ remoteParticipants.forEach((remoteParticipant: RemoteParticipant) => {
+ const connection = new Connection(this, remoteParticipant);
+ this.remoteConnections.set(connection.connectionId, connection);
+ events.connections.push(connection);
+ if (!!connection.stream) {
+ // this.remoteStreamsCreated.set(connection.stream.streamId, true);
+ events.streams.push(connection.stream);
}
});
- }
- /**
- * @hidden
- */
- onForciblyReconnectSubscriber(event: { connectionId: string; streamId: string; sdpOffer: string }): Promise {
- throw new Error("'onForciblyReconnectSubscriber' method is not ready yet.");
- return new Promise((resolve, reject) => {
- this.getRemoteConnection(event.connectionId, 'onForciblyReconnectSubscriber')
- .then((connection) => {
- if (!!connection.stream && connection.stream.streamId === event.streamId) {
- const stream = connection.stream;
-
- if (stream.setupReconnectionEventEmitter(resolve, reject)) {
- // Ongoing reconnection
- // Wait for the event emitter to be free (with success or error) and call the method again
- if (stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'] != null) {
- // Two or more onForciblyReconnectSubscriber events were received while a reconnection process
- // of the subscriber was already taking place. Always use the last one to retry the re-subscription
- // process, as that SDP offer will be the only one available at the server side. Ignore previous ones
- stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'] = event;
- return reject('Ongoing forced subscriber reconnection');
- } else {
- // One onForciblyReconnectSubscriber even has been received while a reconnection process
- // of the subscriber was already taking place. Set up a listener to wait for it to retry the
- // forced reconnection process
- stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'] = event;
- const callback = () => {
- const eventAux = stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'];
- delete stream.reconnectionEventEmitter!['onForciblyReconnectSubscriberLastEvent'];
- this.onForciblyReconnectSubscriber(eventAux);
- };
- stream.reconnectionEventEmitter!.once('success', () => {
- callback();
- });
- stream.reconnectionEventEmitter!.once('error', () => {
- callback();
- });
- }
- return;
- }
-
- stream
- .completeWebRtcPeerReceive(true, true, event.sdpOffer)
- .then(() => stream.finalResolveForSubscription(true, resolve))
- .catch((error) =>
- stream.finalRejectForSubscription(
- true,
- `Error while forcibly reconnecting remote stream ${event.streamId}: ${error.toString()}`,
- reject
- )
- );
- } else {
- const errMsg =
- "No stream with streamId '" +
- event.streamId +
- "' found for connection '" +
- event.connectionId +
- "' on 'streamPropertyChanged' event";
- logger.error(errMsg);
- return reject(errMsg);
- }
- })
- .catch((openViduError) => {
- logger.error(openViduError);
- return reject(openViduError);
- });
- });
- }
+ // Own 'connectionCreated' event
+ this.ee.emitEvent('connectionCreated', [new ConnectionEvent(false, this, 'connectionCreated', this.connection, '')]);
- /**
- * @hidden
- */
- reconnectBrokenStreams(): void {
- throw new Error("'reconnectBrokenStreams' method is not ready yet.");
- // logger.info('Re-establishing media connections...');
- // let someReconnection = false;
- // // Re-establish Publisher stream
- // if (!!this.connection.stream && this.connection.stream.streamIceConnectionStateBroken()) {
- // logger.warn('Re-establishing Publisher ' + this.connection.stream.streamId);
- // this.connection.stream.initWebRtcPeerSend(true);
- // someReconnection = true;
- // }
- // // Re-establish Subscriber streams
- // this.remoteConnections.forEach((remoteConnection) => {
- // if (!!remoteConnection.stream && remoteConnection.stream.streamIceConnectionStateBroken()) {
- // logger.warn('Re-establishing Subscriber ' + remoteConnection.stream.streamId);
- // remoteConnection.stream.initWebRtcPeerReceive(true);
- // someReconnection = true;
- // }
+ // One 'connectionCreated' event for each existing connection in the session
+ events.connections.forEach((connection) => {
+ this.ee.emitEvent('connectionCreated', [new ConnectionEvent(false, this, 'connectionCreated', connection, '')]);
+ });
+ // One 'streamCreated' event for each active stream in the session
+ // events.streams.forEach((stream) => {
+ // debugger
+ // this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', stream, '')]);
// });
- // if (!someReconnection) {
- // logger.info('There were no media streams in need of a reconnection');
- // }
- }
-
- /**
- * @hidden
- */
- async onSpeechToTextMessage(event: {
- timestamp?: Date;
- streamId: string;
- connectionId: string;
- sessionId: string;
- text: string;
- reason: string;
- raw: string;
- lang: string;
- }): Promise {
- throw new Error("'onSpeechToTextMessage' method is not ready yet.");
- const connection = await this.getConnection(event.connectionId, 'No connection found for connectionId ' + event.connectionId);
- const ev = new SpeechToTextEvent(this, connection, event.text, event.reason.toLowerCase(), event.raw, event.lang);
- this.ee.emitEvent('speechToTextMessage', [ev]);
- }
-
- /**
- * @hidden
- */
- async onSpeechToTextDisconnected(event: { message: string }): Promise {
- throw new Error("'onSpeechToTextDisconnected' method is not ready yet.");
- this.emitEvent('exception', [new ExceptionEvent(this, ExceptionEventName.SPEECH_TO_TEXT_DISCONNECTED, this, event.message)]);
- }
- /**
- * @hidden
- */
- emitEvent(type: string, eventArray: any[]): void {
- // throw new Error("'emitEvent' method is not ready yet.");
- this.ee.emitEvent(type, eventArray);
- }
-
- /**
- * @hidden
- */
- leave(forced: boolean, reason: ConnectionEventReason): void {
- // throw new Error("'leave' method is not ready yet.");
- forced = !!forced;
- logger.info('Leaving Session (forced=' + forced + ')');
- // this.stopVideoDataIntervals();
-
- if (!!this.connection) {
- // if (!this.connection.disposed && !forced) {
- // this.openvidu.sendRequest('leaveRoom', (error, response) => {
- // if (error) {
- // logger.error(`leaveRoom error: ${JSON.stringify(error)}`);
- // }
- // this.openvidu.closeWs();
- // });
- // }
- // else {
- // this.openvidu.closeWs();
- // }
-
- // this.stopPublisherStream(reason);
- if (this.room) this.room.disconnect();
-
- // if (!this.connection.disposed) {
- // Make Session object dispatch 'sessionDisconnected' event (if it is not already disposed)
- const sessionDisconnectEvent = new SessionDisconnectedEvent(this, reason);
- this.ee.emitEvent('sessionDisconnected', [sessionDisconnectEvent]);
- sessionDisconnectEvent.callDefaultBehavior();
- // }
- } else {
- logger.warn('You were not connected to the session ' + this.sessionId);
- }
- logger.flush();
- }
-
- /**
- * @hidden
- */
- // initializeParams(token: string) {
- // throw new Error("'initializeParams' method is not ready yet.");
- // const joinParams = {
- // token: !!token ? token : '',
- // session: this.sessionId,
- // platform: !!platform.getDescription() ? platform.getDescription() : 'unknown',
- // sdkVersion: this.openvidu.libraryVersion,
- // metadata: !!this.options.metadata ? this.options.metadata : '',
- // secret: this.openvidu.getSecret(),
- // recorder: this.openvidu.getRecorder(),
- // stt: this.openvidu.getStt()
- // };
- // return joinParams;
- // }
-
- /**
- * @hidden
- */
- sessionConnected() {
- return this.room.state === ConnectionState.Connected;
- // throw new Error("'sessionConnected' method is not ready yet.");
- // return this.connection != null;
- }
-
- /**
- * @hidden
- */
- notConnectedError(): OpenViduError {
- return new OpenViduError(
- OpenViduErrorName.OPENVIDU_NOT_CONNECTED,
- "There is no connection to the session. Method 'Session.connect' must be successfully completed first"
- );
- }
-
- /**
- * @hidden
- */
- anySpeechEventListenerEnabled(event: string, onlyOnce: boolean, streamManager?: StreamManager): boolean {
- throw new Error("'anySpeechEventListenerEnabled' method is not ready yet.");
- // let handlersInSession = this.ee.getListeners(event);
- // if (onlyOnce) {
- // handlersInSession = handlersInSession.filter((h) => (h as any).once);
- // }
- // let listenersInSession = handlersInSession.length;
- // if (listenersInSession > 0) return true;
- // let listenersInStreamManager = 0;
- // if (!!streamManager) {
- // let handlersInStreamManager = streamManager.ee.getListeners(event);
- // if (onlyOnce) {
- // handlersInStreamManager = handlersInStreamManager.filter((h) => (h as any).once);
- // }
- // listenersInStreamManager = handlersInStreamManager.length;
- // }
- // return listenersInStreamManager > 0;
- }
-
- /**
- * @hidden
- */
- getTokenParams(token: string) {
- throw new Error("'getTokenParams' method is not ready yet.");
- // const match = token.match(/^(wss?)\:\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
- // if (!!match) {
- // const url = {
- // protocol: match[1],
- // host: match[2],
- // hostname: match[3],
- // port: match[4],
- // pathname: match[5],
- // search: match[6],
- // hash: match[7]
- // };
-
- // const params = token.split('?');
- // const queryParams = decodeURI(params[1])
- // .split('&')
- // .map((param) => param.split('='))
- // .reduce((values, [key, value]) => {
- // values[key] = value;
- // return values;
- // }, {});
-
- // return {
- // sessionId: queryParams['sessionId'],
- // secret: queryParams['secret'],
- // recorder: queryParams['recorder'],
- // stt: queryParams['stt'],
- // webrtcStatsInterval: queryParams['webrtcStatsInterval'],
- // sendBrowserLogs: queryParams['sendBrowserLogs'],
- // edition: queryParams['edition'],
- // wsUri: url.protocol + '://' + url.host + '/openvidu',
- // httpUri: 'https://' + url.host
- // };
- // } else {
- // throw new Error(`Token not valid: "${token}"`);
+ // if (!!response.recordingId && !!response.recordingName) {
+ // this.ee.emitEvent('recordingStarted', [
+ // new RecordingEvent(this, 'recordingStarted', response.recordingId, response.recordingName)
+ // ]);
// }
}
- /* Private methods */
-
- // private connectAux(token: string): Promise {
- // // throw new Error("'connectAux' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // // this.openvidu.startWs((error) => {
- // // if (!!error) {
- // // return reject(error);
- // // } else {
- // // const joinParams = this.initializeParams(token);
-
- // // this.openvidu.sendRequest('joinRoom', joinParams, (error, response: LocalConnectionOptions) => {
- // // if (!!error) {
- // // return reject(error);
- // // } else {
- // // // Process join room response
- // this.processJoinRoomResponse(response, token);
-
- // // Initialize local Connection object with values returned by openvidu-server
- // this.connection = new Connection(this, response);
-
- // // Initialize remote Connections with value returned by openvidu-server
- // const events = {
- // connections: new Array(),
- // streams: new Array()
- // };
- // const existingParticipants: RemoteConnectionOptions[] = response.value;
- // existingParticipants.forEach((remoteConnectionOptions: RemoteConnectionOptions) => {
- // const connection = new Connection(this, remoteConnectionOptions);
- // this.remoteConnections.set(connection.connectionId, connection);
- // events.connections.push(connection);
- // if (!!connection.stream) {
- // this.remoteStreamsCreated.set(connection.stream.streamId, true);
- // events.streams.push(connection.stream);
- // }
- // });
-
- // // Own 'connectionCreated' event
- // this.ee.emitEvent('connectionCreated', [
- // new ConnectionEvent(false, this, 'connectionCreated', this.connection, '')
- // ]);
-
- // // One 'connectionCreated' event for each existing connection in the session
- // events.connections.forEach((connection) => {
- // this.ee.emitEvent('connectionCreated', [
- // new ConnectionEvent(false, this, 'connectionCreated', connection, '')
- // ]);
- // });
-
- // // // One 'streamCreated' event for each active stream in the session
- // // events.streams.forEach((stream) => {
- // // this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', stream, '')]);
- // // });
-
- // // if (!!response.recordingId && !!response.recordingName) {
- // // this.ee.emitEvent('recordingStarted', [
- // // new RecordingEvent(this, 'recordingStarted', response.recordingId, response.recordingName)
- // // ]);
- // // }
-
- // return resolve();
- // // }
- // // });
- // // }
- // // });
- // });
- // }
-
- private stopPublisherStream(reason: StreamEventReason) {
- console.warn("'stopPublisherStream' method is not ready yet.");
- if (!!this.connection.stream) {
- // Dispose Publisher's local stream
- // this.connection.stream.disposeWebRtcPeer();
- if (this.connection.stream.isLocalStreamPublished) {
- // Make Publisher object dispatch 'streamDestroyed' event if the Stream was published
- this.connection.stream.ee.emitEvent('local-stream-destroyed', [reason]);
- }
- }
- }
-
- private stringClientMetadata(metadata: any): string {
- throw new Error("'stringClientMetadata' method is not ready yet.");
- if (typeof metadata !== 'string') {
- return JSON.stringify(metadata);
- } else {
- return metadata;
- }
- }
-
- protected getConnection(connectionId: string, errorMessage: string): Promise {
- throw new Error("'getConnection' method is not ready yet.");
- return new Promise((resolve, reject) => {
- const connection = this.remoteConnections.get(connectionId);
- if (!!connection) {
- // Resolve remote connection
- return resolve(connection);
- } else {
- if (this.connection.connectionId === connectionId) {
- // Resolve local connection
- return resolve(this.connection);
- } else {
- // Connection not found. Reject with OpenViduError
- return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
- }
- }
- });
- }
-
- private getRemoteConnection(connectionId: string, operation: string): Promise {
- // throw new Error("'getRemoteConnection' method is not ready yet.");
- return new Promise((resolve, reject) => {
- const remoteConnectionKeys = Array.from(this.remoteConnections.keys());
- const matchingKey = remoteConnectionKeys.find((key) => key.includes(connectionId));
-
- if (matchingKey) {
- const connection = this.remoteConnections.get(matchingKey);
- // Resolve remote connection
- if (!!connection) {
- return resolve(connection);
- }
- return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Connection not found'));
- } else {
- // Remote connection not found. Reject with OpenViduError
- const errorMessage = `
- Remote connection ${connectionId} is unknown when '${operation}'.
- Existing remote connections: ${JSON.stringify(this.remoteConnections.keys())}
- `;
- return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
- }
- });
- }
-
- private processToken(token: string): void {
- throw new Error("'processToken' method is not ready yet.");
- // const tokenParams = this.getTokenParams(token);
- // this.sessionId = tokenParams.sessionId;
+ private processNetworkQualityValue(quality: ConnectionQuality): void {
+ switch (quality) {
+ case ConnectionQuality.Excellent:
+ this.networkQualityLevelValue = 5;
+ break;
- // if (!!tokenParams.secret) {
- // this.openvidu.secret = tokenParams.secret;
- // }
- // if (!!tokenParams.recorder) {
- // this.openvidu.recorder = true;
- // }
- // if (!!tokenParams.stt) {
- // this.openvidu.stt = true;
- // }
- // if (!!tokenParams.webrtcStatsInterval) {
- // this.openvidu.webrtcStatsInterval = tokenParams.webrtcStatsInterval;
- // }
- // if (!!tokenParams.sendBrowserLogs) {
- // this.openvidu.sendBrowserLogs = tokenParams.sendBrowserLogs;
- // }
- // this.openvidu.isAtLeastPro = tokenParams.edition === 'pro' || tokenParams.edition === 'enterprise';
- // this.openvidu.isEnterprise = tokenParams.edition === 'enterprise';
+ case ConnectionQuality.Good:
+ this.networkQualityLevelValue = 4;
+ break;
- // this.openvidu.wsUri = tokenParams.wsUri;
- // this.openvidu.httpUri = tokenParams.httpUri;
- }
+ case ConnectionQuality.Poor:
+ this.networkQualityLevelValue = 2;
+ break;
- private processJoinRoomResponse(opts: LocalConnectionOptions, token: string) {
- // throw new Error("'processJoinRoomResponse' method is not ready yet.");
- // this.sessionId = opts.session;
- if (opts.customIceServers != null && opts.customIceServers.length > 0) {
- this.openvidu.iceServers = [];
- for (const iceServer of opts.customIceServers) {
- let rtcIceServer: RTCIceServer = {
- urls: [iceServer.url]
- };
- logger.log('STUN/TURN server IP: ' + iceServer.url);
- if (iceServer.username != null && iceServer.credential != null) {
- rtcIceServer.username = iceServer.username;
- rtcIceServer.credential = iceServer.credential;
- logger.log('TURN credentials [' + iceServer.username + ':' + iceServer.credential + ']');
- }
- this.openvidu.iceServers.push(rtcIceServer);
- }
- }
- this.openvidu.role = opts.role;
- this.openvidu.finalUserId = opts.finalUserId;
- this.openvidu.mediaServer = opts.mediaServer;
- this.openvidu.videoSimulcast = opts.videoSimulcast;
- this.capabilities = {
- subscribe: true,
- publish: this.openvidu.role !== 'SUBSCRIBER',
- forceUnpublish: this.openvidu.role === 'MODERATOR',
- forceDisconnect: this.openvidu.role === 'MODERATOR'
- };
- logger.info('openvidu-server version: ' + opts.version);
- if (opts.life != null) {
- this.openvidu.life = opts.life;
- }
- const minorDifference: number = semverMinor(opts.version) - semverMinor(this.openvidu.libraryVersion);
- if (semverMajor(opts.version) !== semverMajor(this.openvidu.libraryVersion) || !(minorDifference == 0 || minorDifference == 1)) {
- logger.error(
- `openvidu-browser (${this.openvidu.libraryVersion}) and openvidu-server (${opts.version}) versions are incompatible. ` +
- 'Errors are likely to occur. openvidu-browser SDK is only compatible with the same version or the immediately following minor version of an OpenVidu deployment'
- );
- } else if (minorDifference == 1) {
- logger.warn(
- `openvidu-browser version ${this.openvidu.libraryVersion} does not match openvidu-server version ${opts.version}. ` +
- `These versions are still compatible with each other, but openvidu-browser version must be updated as soon as possible to ${semverMajor(
- opts.version
- )}.${semverMinor(opts.version)}.x. ` +
- `This client using openvidu-browser ${this.openvidu.libraryVersion} will become incompatible with the next release of openvidu-server`
- );
+ case ConnectionQuality.Unknown:
+ this.networkQualityLevelValue = 1;
+ break;
+ default:
+ break;
}
-
- // Configure JSNLogs
- // OpenViduLogger.configureJSNLog(this.openvidu, token);
-
- // Store token
- this.token = token;
}
private getLivekitUrlFromMetadata(token: string): string {
@@ -2136,25 +598,13 @@ export class Session extends EventDispatcher {
}
}
- private processNetworkQualityValue(quality: ConnectionQuality): void {
- switch (quality) {
- case ConnectionQuality.Excellent:
- this.networkQualityLevelValue = 5;
- break;
-
- case ConnectionQuality.Good:
- this.networkQualityLevelValue = 4;
- break;
-
- case ConnectionQuality.Poor:
- this.networkQualityLevelValue = 2;
- break;
-
- case ConnectionQuality.Unknown:
- this.networkQualityLevelValue = 1;
- break;
- default:
- break;
- }
+ /**
+ * @hidden
+ */
+ notConnectedError(): OpenViduError {
+ return new OpenViduError(
+ OpenViduErrorName.OPENVIDU_NOT_CONNECTED,
+ "There is no connection to the session. Method 'Session.connect' must be successfully completed first"
+ );
}
}
diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts
index a957aff4d5b..d51ab0fdb15 100644
--- a/openvidu-browser/src/OpenVidu/Stream.ts
+++ b/openvidu-browser/src/OpenVidu/Stream.ts
@@ -1,77 +1,19 @@
-/*
- * (C) Copyright 2017-2022 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.
- *
- */
+// Participant tracks combined into a single Stream
+import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
+import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
+import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
+import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
import { Connection } from './Connection';
-import { Filter } from './Filter';
import { Publisher } from './Publisher';
import { Session } from './Session';
import { StreamManager } from './StreamManager';
-import { Subscriber } from './Subscriber';
-import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
-import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
-import {
- WebRtcPeer,
- WebRtcPeerSendonly,
- WebRtcPeerRecvonly,
- WebRtcPeerSendrecv,
- WebRtcPeerConfiguration
-} from '../OpenViduInternal/WebRtcPeer/WebRtcPeer';
-import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats';
-import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
-import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent';
-import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
-import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
-import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
-import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
-import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
-import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
-
-import { v4 as uuidv4 } from 'uuid';
-
-/**
- * @hidden
- */
-import hark = require('hark');
-/**
- * @hidden
- */
import EventEmitter = require('wolfy87-eventemitter');
-import {
- AudioTrack,
- LocalAudioTrack,
- LocalParticipant,
- LocalTrackPublication,
- LocalVideoTrack,
- RemoteAudioTrack,
- RemoteParticipant,
- RemoteVideoTrack,
- Track,
- TrackPublishOptions,
- VideoTrack
-} from 'livekit-client';
-/**
- * @hidden
- */
-const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
-let platform: PlatformUtils;
+const platform: PlatformUtils = PlatformUtils.getInstance();
/**
* Represents each one of the media streams available in OpenVidu Server for certain session.
@@ -83,7 +25,6 @@ export class Stream {
* The Connection object that is publishing the stream
*/
connection: Connection;
-
/**
* Frame rate of the video in frames per second. This property is only defined if the {@link Publisher} of
* the stream was initialized passing a _frameRate_ property on {@link OpenVidu.initPublisher} method
@@ -129,7 +70,6 @@ export class Stream {
* - Publisher object: property `creationTime` is only defined after successful execution of {@link Session.publish}
*/
creationTime: number;
-
/**
* Can be:
* - `"CAMERA"`: when the video source comes from a webcam.
@@ -139,13 +79,7 @@ export class Stream {
*
* If {@link hasVideo} is false, this property is undefined
*/
- typeOfVideo?: keyof typeof TypeOfVideo; // TODO: Change this type to enum TypeOfVideo on the next breaking-change release
-
- /**
- * StreamManager object ({@link Publisher} or {@link Subscriber}) in charge of displaying this stream in the DOM
- */
- streamManager: StreamManager;
-
+ typeOfVideo?: keyof typeof TypeOfVideo;
/**
* Width and height in pixels of the encoded video stream. If {@link hasVideo} is false, this property is undefined
*
@@ -156,40 +90,10 @@ export class Stream {
* Whenever this happens a {@link StreamPropertyChangedEvent} will be dispatched by the Session object as well as by the affected Subscriber/Publisher object
*/
videoDimensions: { width: number; height: number };
-
- /**
- * **WARNING**: experimental option. This interface may change in the near future
- *
- * Filter applied to the Stream. You can apply filters by calling {@link Stream.applyFilter}, execute methods of the applied filter with
- * {@link Filter.execMethod} and remove it with {@link Stream.removeFilter}. Be aware that the client calling this methods must have the
- * necessary permissions: the token owned by the client must have been initialized with the appropriated `allowedFilters` array.
- */
- filter?: Filter;
-
- protected webRtcPeer: WebRtcPeer;
- protected mediaStreama?: MediaStream;
- private webRtcStats: WebRtcStats;
-
- private isSubscribeToRemote = false;
-
- private virtualBackgroundSourceElements?: { videoClone: HTMLVideoElement; mediaStreamClone: MediaStream };
- /**
- * @hidden
- */
- virtualBackgroundSinkElements?: { VB: any; video: HTMLVideoElement };
-
/**
- * @hidden
- */
- isLocalStreamReadyToPublish = false;
- /**
- * @hidden
- */
- isLocalStreamPublished = false;
- /**
- * @hidden
+ * StreamManager object ({@link Publisher} or {@link Subscriber}) in charge of displaying this stream in the DOM
*/
- publishedOnce = false;
+ streamManager: StreamManager;
/**
* @hidden
*/
@@ -202,729 +106,62 @@ export class Stream {
* @hidden
*/
outboundStreamOpts: OutboundStreamOptions;
- /**
- * @hidden
- */
- speechEvent: any;
- /**
- * @hidden
- */
- harkSpeakingEnabled = false;
- /**
- * @hidden
- */
- harkSpeakingEnabledOnce = false;
- /**
- * @hidden
- */
- harkStoppedSpeakingEnabled = false;
- /**
- * @hidden
- */
- harkStoppedSpeakingEnabledOnce = false;
- /**
- * @hidden
- */
- harkVolumeChangeEnabled = false;
- /**
- * @hidden
- */
- harkVolumeChangeEnabledOnce = false;
- /**
- * @hidden
- */
- harkOptions;
- /**
- * @hidden
- */
- localMediaStreamWhenSubscribedToRemote?: MediaStream;
/**
* @hidden
*/
ee = new EventEmitter();
- /**
- * @hidden
- */
- reconnectionEventEmitter: EventEmitter | undefined;
- /**
- * @hidden
- */
- lastVideoTrackConstraints: MediaTrackConstraints | boolean | undefined;
- /**
- * @hidden
- */
- lastVBFilter?: Filter;
- /**
- * @hidden
- * Track to be published
- */
- temporalVideoTrack: VideoTrack | undefined;
- /**
- * @hidden
- * * Track to be published
- */
- temporalAudioTrack: AudioTrack | undefined;
-
- /**
- * @hidden
- * * Track to be published
- */
- temporalVideoScreenTrack: VideoTrack | undefined;
- /**
- * @hidden
- * * Track to be published
- */
- temporalAudioScreenTrack: AudioTrack | undefined;
+ // TODO: Is this needed?
+ isLocalStreamReadyToPublish: boolean;
/**
* @hidden
*/
constructor(session: Session, options: InboundStreamOptions | OutboundStreamOptions | {}) {
- console.warn("'Stream' class is not ready yet.");
- // platform = PlatformUtils.getInstance();
this.session = session;
- if (options.hasOwnProperty('id')) {
- // InboundStreamOptions: stream belongs to a Subscriber
- this.inboundStreamOpts = options;
- this.streamId = this.inboundStreamOpts.id;
- this.creationTime = this.inboundStreamOpts.createdAt;
- this.hasAudio = this.inboundStreamOpts.hasAudio;
- this.hasVideo = this.inboundStreamOpts.hasVideo;
- if (this.hasAudio) {
- this.audioActive = this.inboundStreamOpts.audioActive;
- }
- if (this.hasVideo) {
- this.videoActive = this.inboundStreamOpts.videoActive;
- this.typeOfVideo = !this.inboundStreamOpts.typeOfVideo ? undefined : this.inboundStreamOpts.typeOfVideo;
- this.frameRate = this.inboundStreamOpts.frameRate === -1 ? undefined : this.inboundStreamOpts.frameRate;
- this.videoDimensions = this.inboundStreamOpts.videoDimensions;
- }
- if (!!this.inboundStreamOpts.filter && Object.keys(this.inboundStreamOpts.filter).length > 0) {
- if (
- !!this.inboundStreamOpts.filter.lastExecMethod &&
- Object.keys(this.inboundStreamOpts.filter.lastExecMethod).length === 0
- ) {
- delete this.inboundStreamOpts.filter.lastExecMethod;
- }
- this.filter = this.inboundStreamOpts.filter;
- }
+ if ('id' in options) {
+ // InboundStreamOptions: stream belongs to a Subscriber (remote)
+ this.handleInboundStreamOptions(options as InboundStreamOptions);
} else {
- // OutboundStreamOptions: stream belongs to a Publisher
- this.outboundStreamOpts = options;
-
- this.hasAudio = this.isSendAudio();
- this.hasVideo = this.isSendVideo();
-
- if (this.hasAudio) {
- this.audioActive = !!this.outboundStreamOpts.publisherProperties.publishAudio;
- }
- if (this.hasVideo) {
- this.videoActive = !!this.outboundStreamOpts.publisherProperties.publishVideo;
- this.frameRate = this.outboundStreamOpts.publisherProperties.frameRate;
- if (
- typeof MediaStreamTrack !== 'undefined' &&
- this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack
- ) {
- this.typeOfVideo = TypeOfVideo.CUSTOM;
- } else {
- this.typeOfVideo = this.isSendScreen() ? TypeOfVideo.SCREEN : TypeOfVideo.CAMERA;
- }
- }
- // if (!!this.outboundStreamOpts.publisherProperties.filter) {
- // this.filter = this.outboundStreamOpts.publisherProperties.filter;
- // }
+ // OutboundStreamOptions: stream belongs to a Publisher (local)
+ this.handleOutboundStreamOptions(options as OutboundStreamOptions);
}
- this.ee.on('mediastream-updated', () => {
- // this.streamManager.updateMediaStream(this.mediaStream!);
- // logger.debug('Video srcObject [' + this.mediaStream?.id + '] updated in stream [' + this.streamId + ']');
- });
- }
-
- /**
- * Recreates the media connection with the server. This entails the disposal of the previous RTCPeerConnection and the re-negotiation
- * of a new one, that will apply the same properties.
- *
- * This method can be useful in those situations were there the media connection breaks and OpenVidu is not able to recover on its own
- * for any kind of unanticipated reason (see [Automatic reconnection](/en/stable/advanced-features/automatic-reconnection/)).
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved if the reconnection operation was successful and rejected with an Error object if not
- */
- public reconnect(): Promise {
- throw new Error("'reconnect' method is not ready yet.");
- return this.reconnectStream('API');
- }
-
- /**
- * Applies an audio/video filter to the stream.
- *
- * @param type Type of filter applied. See {@link Filter.type}
- * @param options Parameters used to initialize the filter. See {@link Filter.options}
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved to the applied filter if success and rejected with an Error object if not
- */
- applyFilter(type: string, options: Object): Promise {
- throw new Error("'applyFilter' method is not ready yet.");
- // return new Promise(async (resolve, reject) => {
- // if (!!this.filter) {
- // return reject(
- // new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'There is already a filter applied to Stream ' + this.streamId)
- // );
- // }
-
- // const resolveApplyFilter = (error, triggerEvent) => {
- // if (error) {
- // logger.error('Error applying filter for Stream ' + this.streamId, error);
- // if (error.code === 401) {
- // return reject(
- // new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to apply a filter")
- // );
- // } else {
- // return reject(error);
- // }
- // } else {
- // logger.info('Filter successfully applied on Stream ' + this.streamId);
- // const oldValue: Filter = this.filter!;
- // this.filter = new Filter(type, options);
- // this.filter.stream = this;
- // if (triggerEvent) {
- // this.session.emitEvent('streamPropertyChanged', [
- // new StreamPropertyChangedEvent(this.session, this, 'filter', this.filter, oldValue, 'applyFilter')
- // ]);
- // this.streamManager.emitEvent('streamPropertyChanged', [
- // new StreamPropertyChangedEvent(this.streamManager, this, 'filter', this.filter, oldValue, 'applyFilter')
- // ]);
- // }
- // return resolve(this.filter);
- // }
- // };
-
- // if (type.startsWith('VB:')) {
- // // Client filters
-
- // if (!this.hasVideo) {
- // return reject(
- // new OpenViduError(
- // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
- // 'The Virtual Background filter requires a video track to be applied'
- // )
- // );
- // }
- // if (!this.mediaStream || this.streamManager.videos.length === 0) {
- // return reject(
- // new OpenViduError(
- // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
- // 'The StreamManager requires some video element to be attached to it in order to apply a Virtual Background filter'
- // )
- // );
- // }
-
- // let openviduToken: string;
- // if (!!this.session.token) {
- // openviduToken = this.session.token;
- // } else {
- // openviduToken = options['token'];
- // }
- // if (!openviduToken) {
- // return reject(
- // new OpenViduError(
- // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
- // 'Virtual Background requires the client to be connected to a Session or to have a "token" property available in "options" parameter with a valid OpenVidu token'
- // )
- // );
- // }
-
- // const tokenParams = this.session.getTokenParams(openviduToken);
- // if (tokenParams.edition !== 'pro' && tokenParams.edition !== 'enterprise') {
- // return reject(
- // new OpenViduError(
- // OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
- // 'OpenVidu Virtual Background API is available from OpenVidu Pro edition onwards'
- // )
- // );
- // }
-
- // openviduToken = encodeURIComponent(btoa(openviduToken));
-
- // logger.info('Applying Virtual Background to stream ' + this.streamId);
-
- // const afterScriptLoaded = async () => {
- // try {
- // const id = this.streamId + '_' + uuidv4();
- // const mediaStreamClone = this.mediaStream!.clone();
- // const videoClone = this.streamManager.videos[0].video.cloneNode(false) as HTMLVideoElement;
- // // @ts-ignore
- // videoClone.id = VirtualBackground.VirtualBackground.SOURCE_VIDEO_PREFIX + id;
- // videoClone.srcObject = mediaStreamClone;
- // videoClone.muted = true;
- // this.virtualBackgroundSourceElements = { videoClone, mediaStreamClone };
-
- // // @ts-ignore
- // VirtualBackground.VirtualBackground.hideHtmlElement(videoClone, false);
- // // @ts-ignore
- // VirtualBackground.VirtualBackground.appendHtmlElementToHiddenContainer(videoClone, id);
-
- // await videoClone.play();
-
- // // @ts-ignore
- // const VB = new VirtualBackground.VirtualBackground({
- // id,
- // openviduServerUrl: new URL(tokenParams.httpUri),
- // openviduToken,
- // inputVideo: videoClone,
- // inputResolution: '160x96',
- // outputFramerate: 24
- // });
-
- // let filteredVideo: HTMLVideoElement;
- // switch (type) {
- // case 'VB:blur': {
- // filteredVideo = await VB.backgroundBlur(options);
- // break;
- // }
- // case 'VB:image': {
- // filteredVideo = await VB.backgroundImage(options);
- // break;
- // }
- // default:
- // throw new Error('Unknown Virtual Background filter: ' + type);
- // }
-
- // this.virtualBackgroundSinkElements = { VB, video: filteredVideo };
-
- // videoClone.style.display = 'none';
-
- // if (this.streamManager.remote) {
- // this.streamManager.replaceTrackInMediaStream(
- // (this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0],
- // false
- // );
- // } else {
- // (this.streamManager as Publisher).replaceTrackAux(
- // (this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0],
- // false
- // );
- // }
-
- // resolveApplyFilter(undefined, false);
- // } catch (error) {
- // if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
- // resolveApplyFilter(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, error.message), false);
- // } else {
- // resolveApplyFilter(error, false);
- // }
- // }
- // };
-
- // // @ts-ignore
- // if (typeof VirtualBackground === 'undefined') {
- // let script: HTMLScriptElement = document.createElement('script');
- // script.type = 'text/javascript';
- // script.src = tokenParams.httpUri + '/openvidu/virtual-background/openvidu-virtual-background.js?token=' + openviduToken;
- // script.onload = async () => {
- // try {
- // await afterScriptLoaded();
- // resolve(new Filter(type, options));
- // } catch (error) {
- // reject(error);
- // }
- // };
- // document.body.appendChild(script);
- // } else {
- // afterScriptLoaded()
- // .then(() => resolve(new Filter(type, options)))
- // .catch((error) => reject(error));
- // }
- // } else {
- // // Server filters
-
- // if (!this.session.sessionConnected()) {
- // return reject(this.session.notConnectedError());
- // }
-
- // logger.info('Applying server filter to stream ' + this.streamId);
- // options = options != null ? options : {};
- // let optionsString = options;
- // if (typeof optionsString !== 'string') {
- // optionsString = JSON.stringify(optionsString);
- // }
- // this.session.openvidu.sendRequest(
- // 'applyFilter',
- // { streamId: this.streamId, type, options: optionsString },
- // (error, response) => {
- // resolveApplyFilter(error, true);
- // }
- // );
- // }
- // });
- }
-
- /**
- * Removes an audio/video filter previously applied.
- *
- * @returns A Promise (to which you can optionally subscribe to) that is resolved if the previously applied filter has been successfully removed and rejected with an Error object in other case
- */
- async removeFilter(): Promise {
- throw new Error("'removeFilter' method is not ready yet.");
- return await this.removeFilterAux(false);
- }
-
- /**
- * Returns the internal RTCPeerConnection object associated to this stream (https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection)
- *
- * @returns Native RTCPeerConnection Web API object
- */
- getRTCPeerConnection(): RTCPeerConnection {
- throw new Error("'getRTCPeerConnection' method is not ready yet.");
- return this.webRtcPeer.pc;
- }
-
- /**
- * Returns the internal MediaStream object associated to this stream (https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)
- *
- * @returns Native MediaStream Web API object
- */
- getMediaStream(): MediaStream {
- // const mediaSream = new MediaStream();
- // if (this.videoTrack) {
- // mediaSream.addTrack(this.videoTrack.mediaStreamTrack);
- // }
- // if (this.audioTrack) {
- // mediaSream.addTrack(this.audioTrack.mediaStreamTrack);
- // }
- // return mediaSream;
- throw new Error("'getMediaStream' method is not ready yet.");
-
- // return this.mediaStream!;
- }
-
- /* Hidden methods */
-
- /**
- * @hidden
- */
- removeFilterAux(isDisposing: boolean): Promise {
- throw new Error("'removeFilterAux' method is not ready yet.");
- return new Promise(async (resolve, reject) => {
- const resolveRemoveFilter = (error, triggerEvent) => {
- if (error) {
- delete this.filter;
- logger.error('Error removing filter for Stream ' + this.streamId, error);
- if (error.code === 401) {
- return reject(
- new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to remove a filter")
- );
- } else {
- return reject(error);
- }
- } else {
- logger.info('Filter successfully removed from Stream ' + this.streamId);
- const oldValue = this.filter!;
- delete this.filter;
- if (triggerEvent) {
- this.session.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this.session, this, 'filter', this.filter!, oldValue, 'applyFilter')
- ]);
- this.streamManager.emitEvent('streamPropertyChanged', [
- new StreamPropertyChangedEvent(this.streamManager, this, 'filter', this.filter!, oldValue, 'applyFilter')
- ]);
- }
- return resolve();
- }
- };
-
- if (!!this.filter) {
- // There is a filter applied
-
- if (this.filter?.type.startsWith('VB:')) {
- // Client filters
-
- try {
- const mediaStreamClone = this.virtualBackgroundSourceElements!.mediaStreamClone;
- if (!isDisposing) {
- if (this.streamManager.remote) {
- this.streamManager.replaceTrackInMediaStream(mediaStreamClone.getVideoTracks()[0], false);
- } else {
- await (this.streamManager as Publisher).replaceTrackAux(mediaStreamClone.getVideoTracks()[0], false);
- }
- } else {
- mediaStreamClone.getTracks().forEach((track) => track.stop());
- }
-
- this.virtualBackgroundSinkElements!.VB.cleanUp();
-
- delete this.virtualBackgroundSinkElements;
- delete this.virtualBackgroundSourceElements;
-
- return resolveRemoveFilter(undefined, false);
- } catch (error) {
- return resolveRemoveFilter(error, false);
- }
- } else {
- // Server filters
-
- if (!this.session.sessionConnected()) {
- return reject(this.session.notConnectedError());
- }
-
- logger.info('Removing filter of stream ' + this.streamId);
- this.session.openvidu.sendRequest('removeFilter', { streamId: this.streamId }, (error, response) => {
- return resolveRemoveFilter(error, true);
- });
- }
- } else {
- // There is no filter applied
- return reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Stream ' + this.streamId + ' has no filter applied'));
- }
- });
- }
-
- /**
- * @hidden
- */
- setMediaStream(mediaStream: MediaStream): void {
- // this.mediaStream = mediaStream;
- }
-
- /**
- * @hidden
- */
- updateMediaStreamInVideos() {
- throw new Error("'updateMediaStreamInVideos' method is not ready yet.");
- // this.ee.emitEvent('mediastream-updated', []);
- }
-
- /**
- * @hidden
- */
- getWebRtcPeer(): WebRtcPeer {
- throw new Error("'getWebRtcPeer' method is not ready yet.");
- return this.webRtcPeer;
- }
-
- /**
- * @hidden
- */
- subscribeToMyRemote(value: boolean): void {
- throw new Error("'subscribeToMyRemote' method is not ready yet.");
- this.isSubscribeToRemote = value;
+ this.setupEventListeners();
}
/**
* @hidden
*/
- setOutboundStreamOptions(outboundStreamOpts: OutboundStreamOptions): void {
- this.outboundStreamOpts = outboundStreamOpts;
- }
-
- /**
- * @hidden
- */
- // subscribe(): Promise {
+ async publish(publisher: Publisher): Promise {
+ console.log(' Stream.publish()');
- // throw new Error("'subscribe' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // this.initWebRtcPeerReceive(false)
- // .then(() => resolve())
- // .catch((error) => reject(error));
- // });
- // }
+ const { videoOptions, videoPublishOptions, audioOptions, audioPublishOptions } = publisher;
- /**
- * @hidden
- */
- async publish(): Promise {
- console.warn("'publish' method is not ready yet.");
-
- const { localParticipant } = this.session.room;
- // const videoStreamTrack = this.getMediaStream().getVideoTracks()[0];
- // const audioStreamTrack = this.getMediaStream().getAudioTracks()[0];
- const promises: Promise[] = [];
-
- // TODO here should be initialized the following properties?
- // params = {
- // doLoopback: this.displayMyRemote() || false,
- // hasAudio: this.isSendAudio(),
- // hasVideo: this.isSendVideo(),
- // audioActive: this.audioActive,
- // videoActive: this.videoActive,
- // typeOfVideo,
- // frameRate: !!this.frameRate ? this.frameRate : -1,
- // videoDimensions: JSON.stringify(this.videoDimensions),
- // filter: this.outboundStreamOpts.publisherProperties.filter,
- // sdpOffer: sdpOfferParam
- // };
- this.streamId = localParticipant.identity + '_' + uuidv4();
-
- const publishOpt: TrackPublishOptions = {
- name: this.streamId,
- stream: this.streamId
- };
-
- if (this.temporalVideoTrack || this.temporalAudioTrack) {
- if (this.temporalVideoTrack) {
- // this.videoScreenTrack?.stop();
- // this.videoScreenTrack = undefined;
- publishOpt.source = Track.Source.Camera;
- // localParticipant.setCameraEnabled(true);
- const videoTrackPublication = await localParticipant.publishTrack(this.temporalVideoTrack.mediaStreamTrack, publishOpt);
- debugger;
- }
-
- if (this.temporalAudioTrack) {
- // this.audioScreenTrack?.stop();
- // this.audioScreenTrack = undefined;
- publishOpt.source = Track.Source.Microphone;
- const audioTrackPublication = await localParticipant.publishTrack(this.temporalAudioTrack.mediaStreamTrack, publishOpt);
- }
- } else if (this.temporalVideoScreenTrack || this.temporalAudioScreenTrack) {
- if (this.temporalVideoScreenTrack) {
- // if (this.videoTrack) {
- // (this.videoTrack as VideoTrack).stop();
- // }
- // this.videoTrack = undefined;
- publishOpt.source = Track.Source.ScreenShare;
- await localParticipant.publishTrack(this.temporalVideoScreenTrack.mediaStreamTrack, publishOpt);
- }
-
- if (this.temporalAudioScreenTrack) {
- // if (this.audioTrack) {
- // (this.audioTrack as AudioTrack).stop();
- // }
- // this.audioTrack = undefined;
- publishOpt.source = Track.Source.ScreenShareAudio;
- await localParticipant.publishTrack(this.temporalAudioScreenTrack.mediaStreamTrack, publishOpt);
- }
+ if (videoOptions) {
+ await this.session.localParticipant.setCameraEnabled(true, videoOptions, videoPublishOptions);
}
-
- // const [videoTrackPublication, audioTrackPublication] = await Promise.all(promises);
- // console.log('videoTrack', videoTrackPublication);
- // console.log('audioTrack', audioTrackPublication);
- // if (videoTrackPublication.videoTrack) {
- // this.videoTrack = videoTrackPublication.videoTrack;
- // }
- // if (audioTrackPublication.audioTrack) {
- // this.audioTrack = audioTrackPublication.audioTrack;
- // }
-
- this.creationTime = new Date().getTime();
- this.isLocalStreamPublished = true;
- this.publishedOnce = true;
- if (this.displayMyRemote()) {
- // this.localMediaStreamWhenSubscribedToRemote = this.mediaStream;
- // this.remotePeerSuccessfullyEstablished(reconnect);
+ if (audioOptions) {
+ await this.session.localParticipant.setMicrophoneEnabled(true, audioOptions, audioPublishOptions);
}
- // if (reconnect) {
- // this.ee.emitEvent('stream-reconnected-by-publisher', []);
- // } else {
- this.ee.emitEvent('stream-created-by-publisher', []);
- // }
- this.streamManager.stream = this;
-
- logger.info(`'Publisher' (${this.streamId}) successfully published to session`);
-
- // if (!!this.mediaStream && this.streamManager.videos.length > 0) {
- // // this.updateMediaStreamInVideos();
- // }
-
- // return new Promise((resolve, reject) => {
- // if (this.isLocalStreamReadyToPublish) {
- // this.initWebRtcPeerSend(false)
- // .then(() => resolve())
- // .catch((error) => reject(error));
- // } else {
- // this.ee.once('stream-ready-to-publish', () => {
- // this.publish()
- // .then(() => resolve())
- // .catch((error) => reject(error));
- // });
- // }
- // });
- }
-
- /**
- * @hidden
- */
- disposeWebRtcPeer(): void {
- throw new Error("'disposeWebRtcPeer' method is not ready yet.");
- // let webrtcId;
- // if (!!this.webRtcPeer) {
- // this.webRtcPeer.dispose();
- // webrtcId = this.webRtcPeer.getId();
- // }
- // this.stopWebRtcStats();
- // logger.info(
- // (!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') +
- // 'RTCPeerConnection with id [' +
- // webrtcId +
- // "] from 'Stream' with id [" +
- // this.streamId +
- // '] is now closed'
- // );
+ this.ee.emitEvent('stream-ready-to-publish', []);
}
/**
* @hidden
*/
- async disposeMediaStream(): Promise {
- console.warn("'disposeMediaStream' method is not ready yet.");
-
- // if (!!this.filter && this.filter.type.startsWith('VB:')) {
- // try {
- // await this.removeFilterAux(true);
- // console.debug(`Success removing Virtual Background filter for stream ${this.streamId}`);
- // } catch (error) {
- // console.error(`Error removing Virtual Background filter for stream ${this.streamId}`, error);
- // }
- // }
-
- // if (this.mediaStream) {
- // this.mediaStream.getAudioTracks().forEach((track) => {
- // track.stop();
- // });
- // this.mediaStream.getVideoTracks().forEach((track) => {
- // track.stop();
- // });
- // delete this.mediaStream;
- // }
-
- // If subscribeToRemote local MediaStream must be stopped
- // if (this.localMediaStreamWhenSubscribedToRemote) {
- // this.localMediaStreamWhenSubscribedToRemote.getAudioTracks().forEach((track) => {
- // track.stop();
- // });
- // this.localMediaStreamWhenSubscribedToRemote.getVideoTracks().forEach((track) => {
- // track.stop();
- // });
- // delete this.localMediaStreamWhenSubscribedToRemote;
- // }
-
- // if (!!this.speechEvent) {
- // if (!!this.speechEvent.stop) {
- // this.speechEvent.stop();
- // }
- // delete this.speechEvent;
- // }
- logger.info(
- (!!this.outboundStreamOpts ? 'Local ' : 'Remote ') + "MediaStream from 'Stream' with id [" + this.streamId + '] is now disposed'
- );
- }
-
- /**
- * @hidden
- */
- displayMyRemote(): boolean {
- return this.isSubscribeToRemote;
- // throw new Error("'displayMyRemote' method is not ready yet.");
+ isLocal(): boolean {
+ // throw new Error("'isLocal' method is not ready yet.");
+ // inbound options undefined and outbound options defined
+ return !this.inboundStreamOpts && !!this.outboundStreamOpts;
}
/**
* @hidden
*/
- isSendAudio(): boolean {
+ isSendingAudio(): boolean {
// throw new Error("'isSendAudio' method is not ready yet.");
return (
!!this.outboundStreamOpts &&
@@ -932,11 +169,10 @@ export class Stream {
this.outboundStreamOpts.publisherProperties.audioSource !== false
);
}
-
/**
* @hidden
*/
- isSendVideo(): boolean {
+ isSendingVideo(): boolean {
return (
!!this.outboundStreamOpts &&
this.outboundStreamOpts.publisherProperties.videoSource !== null &&
@@ -947,7 +183,7 @@ export class Stream {
/**
* @hidden
*/
- isSendScreen(): boolean {
+ isSendingScreen(): boolean {
let screen = false;
if (
typeof MediaStreamTrack !== 'undefined' &&
@@ -969,1101 +205,66 @@ export class Stream {
return !!this.outboundStreamOpts && screen;
}
- /**
- * @hidden
- */
- enableHarkSpeakingEvent(): void {
- throw new Error("'enableHarkSpeakingEvent' method is not ready yet.");
- this.setHarkListenerIfNotExists();
- if (!this.harkSpeakingEnabled && !!this.speechEvent) {
- this.harkSpeakingEnabled = true;
- this.speechEvent.on('speaking', () => {
- this.session.emitEvent('publisherStartSpeaking', [
- new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)
- ]);
- this.streamManager.emitEvent('publisherStartSpeaking', [
- new PublisherSpeakingEvent(this.streamManager, 'publisherStartSpeaking', this.connection, this.streamId)
- ]);
- this.harkSpeakingEnabledOnce = false; // Disable 'once' version if 'on' version was triggered
- });
- }
+ private setupEventListeners(): void {
+ this.ee.on('mediastream-updated', () => {
+ console.log('Stream.ee.on("mediastream-updated")');
+ // this.streamManager.updateMediaStream(this.mediaStream!);
+ // logger.debug('Video srcObject [' + this.mediaStream?.id + '] updated in stream [' + this.streamId + ']');
+ });
}
- /**
- * @hidden
- */
- enableOnceHarkSpeakingEvent(): void {
- throw new Error("'enableOnceHarkSpeakingEvent' method is not ready yet.");
- this.setHarkListenerIfNotExists();
- if (!this.harkSpeakingEnabledOnce && !!this.speechEvent) {
- this.harkSpeakingEnabledOnce = true;
- this.speechEvent.once('speaking', () => {
- if (this.harkSpeakingEnabledOnce) {
- // If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
- this.session.emitEvent('publisherStartSpeaking', [
- new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)
- ]);
- this.streamManager.emitEvent('publisherStartSpeaking', [
- new PublisherSpeakingEvent(this.streamManager, 'publisherStartSpeaking', this.connection, this.streamId)
- ]);
- }
- this.disableHarkSpeakingEvent(true);
- });
- }
- }
+ private handleInboundStreamOptions(options: InboundStreamOptions): void {
+ this.inboundStreamOpts = options;
+ this.streamId = this.inboundStreamOpts.id;
+ this.creationTime = this.inboundStreamOpts.createdAt;
+ this.hasAudio = this.inboundStreamOpts.hasAudio;
+ this.hasVideo = this.inboundStreamOpts.hasVideo;
- /**
- * @hidden
- */
- disableHarkSpeakingEvent(disabledByOnce: boolean): void {
- throw new Error("'disableHarkSpeakingEvent' method is not ready yet.");
- if (!!this.speechEvent) {
- this.harkSpeakingEnabledOnce = false;
- if (disabledByOnce) {
- if (this.harkSpeakingEnabled) {
- // The 'on' version of this same event is enabled too. Do not remove the hark listener
- return;
- }
- } else {
- this.harkSpeakingEnabled = false;
- }
- // Shutting down the hark event
- if (
- this.harkVolumeChangeEnabled ||
- this.harkVolumeChangeEnabledOnce ||
- this.harkStoppedSpeakingEnabled ||
- this.harkStoppedSpeakingEnabledOnce
- ) {
- // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
- this.speechEvent.off('speaking');
- } else {
- // No other hark event is enabled. We can get entirely rid of it
- this.speechEvent.stop();
- delete this.speechEvent;
- }
+ if (this.hasAudio) {
+ this.audioActive = this.inboundStreamOpts.audioActive;
}
- }
-
- /**
- * @hidden
- */
- enableHarkStoppedSpeakingEvent(): void {
- throw new Error("'enableHarkStoppedSpeakingEvent' method is not ready yet.");
- this.setHarkListenerIfNotExists();
- if (!this.harkStoppedSpeakingEnabled && !!this.speechEvent) {
- this.harkStoppedSpeakingEnabled = true;
- this.speechEvent.on('stopped_speaking', () => {
- this.session.emitEvent('publisherStopSpeaking', [
- new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)
- ]);
- this.streamManager.emitEvent('publisherStopSpeaking', [
- new PublisherSpeakingEvent(this.streamManager, 'publisherStopSpeaking', this.connection, this.streamId)
- ]);
- this.harkStoppedSpeakingEnabledOnce = false; // Disable 'once' version if 'on' version was triggered
- });
- }
- }
-
- /**
- * @hidden
- */
- enableOnceHarkStoppedSpeakingEvent(): void {
- throw new Error("'enableOnceHarkStoppedSpeakingEvent' method is not ready yet.");
- this.setHarkListenerIfNotExists();
- if (!this.harkStoppedSpeakingEnabledOnce && !!this.speechEvent) {
- this.harkStoppedSpeakingEnabledOnce = true;
- this.speechEvent.once('stopped_speaking', () => {
- if (this.harkStoppedSpeakingEnabledOnce) {
- // If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
- this.session.emitEvent('publisherStopSpeaking', [
- new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)
- ]);
- this.streamManager.emitEvent('publisherStopSpeaking', [
- new PublisherSpeakingEvent(this.streamManager, 'publisherStopSpeaking', this.connection, this.streamId)
- ]);
- }
- this.disableHarkStoppedSpeakingEvent(true);
- });
+ if (this.hasVideo) {
+ this.videoActive = this.inboundStreamOpts.videoActive;
+ this.typeOfVideo = this.inboundStreamOpts.typeOfVideo || undefined;
+ this.frameRate = this.inboundStreamOpts.frameRate === -1 ? undefined : this.inboundStreamOpts.frameRate;
+ this.videoDimensions = this.inboundStreamOpts.videoDimensions;
}
- }
- /**
- * @hidden
- */
- disableHarkStoppedSpeakingEvent(disabledByOnce: boolean): void {
- throw new Error("'disableHarkStoppedSpeakingEvent' method is not ready yet.");
- if (!!this.speechEvent) {
- this.harkStoppedSpeakingEnabledOnce = false;
- if (disabledByOnce) {
- if (this.harkStoppedSpeakingEnabled) {
- // We are cancelling the 'once' listener for this event, but the 'on' version
- // of this same event is enabled too. Do not remove the hark listener
- return;
- }
- } else {
- this.harkStoppedSpeakingEnabled = false;
- }
- // Shutting down the hark event
- if (
- this.harkVolumeChangeEnabled ||
- this.harkVolumeChangeEnabledOnce ||
- this.harkSpeakingEnabled ||
- this.harkSpeakingEnabledOnce
- ) {
- // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
- this.speechEvent.off('stopped_speaking');
- } else {
- // No other hark event is enabled. We can get entirely rid of it
- this.speechEvent.stop();
- delete this.speechEvent;
- }
- }
+ // TODO: Add filter
+ // this.handleFilter(options.filter);
}
- /**
- * @hidden
- */
- enableHarkVolumeChangeEvent(force: boolean): void {
- throw new Error("'enableHarkVolumeChangeEvent' method is not ready yet.");
- if (this.setHarkListenerIfNotExists()) {
- if (!this.harkVolumeChangeEnabled || force) {
- this.harkVolumeChangeEnabled = true;
- this.speechEvent.on('volume_change', (harkEvent) => {
- const oldValue = this.speechEvent.oldVolumeValue;
- const value = { newValue: harkEvent, oldValue };
- this.speechEvent.oldVolumeValue = harkEvent;
- this.streamManager.emitEvent('streamAudioVolumeChange', [
- new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)
- ]);
- });
- }
- } else {
- // This way whenever the MediaStream object is available, the event listener will be automatically added
- this.harkVolumeChangeEnabled = true;
- }
- }
+ private handleOutboundStreamOptions(options: OutboundStreamOptions): void {
+ this.outboundStreamOpts = options;
+ this.hasAudio = this.isSendingAudio();
+ this.hasVideo = this.isSendingVideo();
- /**
- * @hidden
- */
- enableOnceHarkVolumeChangeEvent(force: boolean): void {
- throw new Error("'enableOnceHarkVolumeChangeEvent' method is not ready yet.");
- if (this.setHarkListenerIfNotExists()) {
- if (!this.harkVolumeChangeEnabledOnce || force) {
- this.harkVolumeChangeEnabledOnce = true;
- this.speechEvent.once('volume_change', (harkEvent) => {
- const oldValue = this.speechEvent.oldVolumeValue;
- const value = { newValue: harkEvent, oldValue };
- this.speechEvent.oldVolumeValue = harkEvent;
- this.disableHarkVolumeChangeEvent(true);
- this.streamManager.emitEvent('streamAudioVolumeChange', [
- new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)
- ]);
- });
- }
- } else {
- // This way whenever the MediaStream object is available, the event listener will be automatically added
- this.harkVolumeChangeEnabledOnce = true;
+ if (this.hasAudio) {
+ this.audioActive = !!this.outboundStreamOpts.publisherProperties?.publishAudio;
}
- }
-
- /**
- * @hidden
- */
- disableHarkVolumeChangeEvent(disabledByOnce: boolean): void {
- throw new Error("'disableHarkVolumeChangeEvent' method is not ready yet.");
- if (!!this.speechEvent) {
- this.harkVolumeChangeEnabledOnce = false;
- if (disabledByOnce) {
- if (this.harkVolumeChangeEnabled) {
- // We are cancelling the 'once' listener for this event, but the 'on' version
- // of this same event is enabled too. Do not remove the hark listener
- return;
- }
- } else {
- this.harkVolumeChangeEnabled = false;
- }
- // Shutting down the hark event
+ if (this.hasVideo) {
+ this.videoActive = !!this.outboundStreamOpts.publisherProperties?.publishVideo;
+ this.frameRate = this.outboundStreamOpts.publisherProperties?.frameRate;
if (
- this.harkSpeakingEnabled ||
- this.harkSpeakingEnabledOnce ||
- this.harkStoppedSpeakingEnabled ||
- this.harkStoppedSpeakingEnabledOnce
+ typeof MediaStreamTrack !== 'undefined' &&
+ this.outboundStreamOpts?.publisherProperties?.videoSource instanceof MediaStreamTrack
) {
- // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
- this.speechEvent.off('volume_change');
+ this.typeOfVideo = TypeOfVideo.CUSTOM;
} else {
- // No other hark event is enabled. We can get entirely rid of it
- this.speechEvent.stop();
- delete this.speechEvent;
+ this.typeOfVideo = this.isSendingScreen() ? TypeOfVideo.SCREEN : TypeOfVideo.CAMERA;
}
}
- }
-
- /**
- * @hidden
- */
- isLocal(): boolean {
- // throw new Error("'isLocal' method is not ready yet.");
- // inbound options undefined and outbound options defined
- return !this.inboundStreamOpts && !!this.outboundStreamOpts;
- }
-
- /**
- * @hidden
- */
- getSelectedIceCandidate(): Promise {
- throw new Error("'getSelectedIceCandidate' method is not ready yet.");
- return new Promise((resolve, reject) => {
- this.webRtcStats
- .getSelectedIceCandidateInfo()
- .then((report) => resolve(report))
- .catch((error) => reject(error));
- });
- }
-
- /**
- * @hidden
- */
- getRemoteIceCandidateList(): RTCIceCandidate[] {
- throw new Error("'getRemoteIceCandidateList' method is not ready yet.");
- return this.webRtcPeer.remoteCandidatesQueue;
- }
-
- /**
- * @hidden
- */
- getLocalIceCandidateList(): RTCIceCandidate[] {
- throw new Error("'getLocalIceCandidateList' method is not ready yet.");
- return this.webRtcPeer.localCandidatesQueue;
- }
-
- /**
- * @hidden
- */
- streamIceConnectionStateBroken() {
- throw new Error('Method not implemented');
- if (!this.getWebRtcPeer() || !this.getRTCPeerConnection()) {
- return false;
- }
- if (!!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
- logger.warn(
- `OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${
- this.streamId
- } (${this.isLocal() ? 'Publisher' : 'Subscriber'}) will force a reconnection`
- );
- return true;
- } else {
- const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
- return iceConnectionState !== 'connected' && iceConnectionState !== 'completed';
- }
- }
-
- /* Private methods */
- private setHarkListenerIfNotExists(): boolean {
- throw new Error("'setHarkListenerIfNotExists' method is not ready yet.");
- // if (!!this.mediaStream) {
- // if (!this.speechEvent) {
- // const harkOptions = !!this.harkOptions
- // ? this.harkOptions
- // : this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
- // harkOptions.interval = typeof harkOptions.interval === 'number' ? harkOptions.interval : 100;
- // harkOptions.threshold = typeof harkOptions.threshold === 'number' ? harkOptions.threshold : -50;
- // this.speechEvent = hark(this.mediaStream, harkOptions);
- // }
- // return true;
- // }
- // return false;
- }
-
- /**
- * @hidden
- */
- setupReconnectionEventEmitter(resolve: (value: void | PromiseLike) => void, reject: (reason?: any) => void): boolean {
- throw new Error("'setupReconnectionEventEmitter' method is not ready yet.");
- // if (this.reconnectionEventEmitter == undefined) {
- // // There is no ongoing reconnection
- // this.reconnectionEventEmitter = new EventEmitter();
- // return false;
- // } else {
- // // Ongoing reconnection
- // console.warn(
- // `Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) but an ongoing reconnection process is active. Waiting for response...`
- // );
- // this.reconnectionEventEmitter.once('success', () => resolve());
- // this.reconnectionEventEmitter.once('error', (error) => reject(error));
- // return true;
- // }
+ // this.handleFilter(options.publisherProperties?.filter);
}
- /**
- * @hidden
- */
- // initWebRtcPeerSend(reconnect: boolean): Promise {
-
- // throw new Error("'initWebRtcPeerSend' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // if (reconnect) {
- // if (this.setupReconnectionEventEmitter(resolve, reject)) {
- // // Ongoing reconnection
- // return;
- // }
- // } else {
- // // MediaStream will already have hark events for reconnected streams
- // this.initHarkEvents(); // Init hark events for the local stream
+ // TODO: Uncomment and implement the handleFilter method
+ // private handleFilter(filter: any): void {
+ // if (!!filter && Object.keys(filter).length > 0) {
+ // if (!!filter.lastExecMethod && Object.keys(filter.lastExecMethod).length === 0) {
+ // delete filter.lastExecMethod;
// }
-
- // const finalResolve = () => {
- // if (reconnect) {
- // this.reconnectionEventEmitter?.emitEvent('success');
- // delete this.reconnectionEventEmitter;
- // }
- // return resolve();
- // };
-
- // const finalReject = (error) => {
- // if (reconnect) {
- // this.reconnectionEventEmitter?.emitEvent('error', [error]);
- // delete this.reconnectionEventEmitter;
- // }
- // return reject(error);
- // };
-
- // const successOfferCallback = (sdpOfferParam) => {
- // logger.debug('Sending SDP offer to publish as ' + this.streamId, sdpOfferParam);
-
- // const method = reconnect ? 'reconnectStream' : 'publishVideo';
- // let params;
- // if (reconnect) {
- // params = {
- // stream: this.streamId,
- // sdpString: sdpOfferParam
- // };
- // } else {
- // let typeOfVideo;
- // if (this.isSendVideo()) {
- // typeOfVideo =
- // typeof MediaStreamTrack !== 'undefined' &&
- // this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack
- // ? TypeOfVideo.CUSTOM
- // : this.isSendScreen()
- // ? TypeOfVideo.SCREEN
- // : TypeOfVideo.CAMERA;
- // }
- // params = {
- // doLoopback: this.displayMyRemote() || false,
- // hasAudio: this.isSendAudio(),
- // hasVideo: this.isSendVideo(),
- // audioActive: this.audioActive,
- // videoActive: this.videoActive,
- // typeOfVideo,
- // frameRate: !!this.frameRate ? this.frameRate : -1,
- // videoDimensions: JSON.stringify(this.videoDimensions),
- // filter: this.outboundStreamOpts.publisherProperties.filter,
- // sdpOffer: sdpOfferParam
- // };
- // }
-
- // this.session.openvidu.sendRequest(method, params, (error, response) => {
- // if (error) {
- // if (error.code === 401) {
- // finalReject(
- // new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish")
- // );
- // } else {
- // finalReject('Error on publishVideo: ' + JSON.stringify(error));
- // }
- // } else {
- // this.webRtcPeer
- // .processRemoteAnswer(response.sdpAnswer)
- // .then(() => {
- // this.streamId = response.id;
- // this.creationTime = response.createdAt;
- // this.isLocalStreamPublished = true;
- // this.publishedOnce = true;
- // if (this.displayMyRemote()) {
- // this.localMediaStreamWhenSubscribedToRemote = this.mediaStream;
- // this.remotePeerSuccessfullyEstablished(reconnect);
- // }
- // if (reconnect) {
- // this.ee.emitEvent('stream-reconnected-by-publisher', []);
- // } else {
- // this.ee.emitEvent('stream-created-by-publisher', []);
- // }
- // this.initWebRtcStats();
- // logger.info(
- // "'Publisher' (" +
- // this.streamId +
- // ') successfully ' +
- // (reconnect ? 'reconnected' : 'published') +
- // ' to session'
- // );
-
- // finalResolve();
- // })
- // .catch((error) => {
- // finalReject(error);
- // });
- // }
- // });
- // };
-
- // const config: WebRtcPeerConfiguration = {
- // mediaConstraints: {
- // audio: this.hasAudio,
- // video: this.hasVideo
- // },
- // simulcast: this.outboundStreamOpts.publisherProperties.videoSimulcast ?? this.session.openvidu.videoSimulcast,
- // onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
- // onIceConnectionStateException: this.onIceConnectionStateExceptionHandler.bind(this),
- // iceServers: this.getIceServersConf(),
- // mediaStream: this.mediaStream,
- // mediaServer: this.session.openvidu.mediaServer,
- // typeOfVideo: this.typeOfVideo ? TypeOfVideo[this.typeOfVideo] : undefined
- // };
-
- // if (this.session.openvidu.mediaServer !== 'mediasoup') {
- // // Simulcast is only supported by mediasoup
- // config.simulcast = false;
+ // this.filter = filter;
// }
-
- // if (reconnect) {
- // this.disposeWebRtcPeer();
- // }
- // if (this.displayMyRemote()) {
- // this.webRtcPeer = new WebRtcPeerSendrecv(config);
- // } else {
- // this.webRtcPeer = new WebRtcPeerSendonly(config);
- // }
- // this.webRtcPeer.addIceConnectionStateChangeListener('publisher of ' + this.connection.connectionId);
- // this.webRtcPeer
- // .createOffer()
- // .then((sdpOffer) => {
- // this.webRtcPeer
- // .processLocalOffer(sdpOffer)
- // .then(() => {
- // successOfferCallback(sdpOffer.sdp);
- // })
- // .catch((error) => {
- // finalReject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
- // });
- // })
- // .catch((error) => {
- // finalReject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
- // });
- // });
// }
-
- /**
- * @hidden
- */
- finalResolveForSubscription(reconnect: boolean, resolve: (value: void | PromiseLike) => void) {
- throw new Error('Method not implemented');
- // logger.info("'Subscriber' (" + this.streamId + ') successfully ' + (reconnect ? 'reconnected' : 'subscribed'));
- // this.remotePeerSuccessfullyEstablished(reconnect);
- // this.initWebRtcStats();
- // if (reconnect) {
- // this.reconnectionEventEmitter?.emitEvent('success');
- // delete this.reconnectionEventEmitter;
- // }
- // return resolve();
- }
-
- /**
- * @hidden
- */
- finalRejectForSubscription(reconnect: boolean, error: any, reject: (reason?: any) => void) {
- throw new Error('Method not implemented');
- // logger.error(
- // "Error for 'Subscriber' (" +
- // this.streamId +
- // ') while trying to ' +
- // (reconnect ? 'reconnect' : 'subscribe') +
- // ': ' +
- // error.toString()
- // );
- // if (reconnect) {
- // this.reconnectionEventEmitter?.emitEvent('error', [error]);
- // delete this.reconnectionEventEmitter;
- // }
- // return reject(error);
- }
-
- /**
- * @hidden
- */
- initWebRtcPeerReceive(reconnect: boolean): Promise {
- throw new Error("'initWebRtcPeerReceive' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // if (reconnect) {
- // if (this.setupReconnectionEventEmitter(resolve, reject)) {
- // // Ongoing reconnection
- // return;
- // }
- // }
-
- // if (this.session.openvidu.mediaServer === 'mediasoup') {
- // // Server initiates negotiation
-
- // this.initWebRtcPeerReceiveFromServer(reconnect)
- // .then(() => this.finalResolveForSubscription(reconnect, resolve))
- // .catch((error) => this.finalRejectForSubscription(reconnect, error, reject));
- // } else {
- // // Client initiates negotiation
-
- // this.initWebRtcPeerReceiveFromClient(reconnect)
- // .then(() => this.finalResolveForSubscription(reconnect, resolve))
- // .catch((error) => this.finalRejectForSubscription(reconnect, error, reject));
- // }
- // });
- }
-
- /**
- * @hidden
- */
- initWebRtcPeerReceiveFromClient(reconnect: boolean): Promise {
- throw new Error("'initWebRtcPeerReceiveFromClient' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // this.completeWebRtcPeerReceive(reconnect, false)
- // .then((response) => {
- // this.webRtcPeer
- // .processRemoteAnswer(response.sdpAnswer)
- // .then(() => resolve())
- // .catch((error) => reject(error));
- // })
- // .catch((error) => reject(error));
- // });
- }
-
- /**
- * @hidden
- */
- initWebRtcPeerReceiveFromServer(reconnect: boolean): Promise {
- throw new Error("'initWebRtcPeerReceiveFromServer' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // // Server initiates negotiation
- // this.session.openvidu.sendRequest('prepareReceiveVideoFrom', { sender: this.streamId, reconnect }, (error, response) => {
- // if (error) {
- // return reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error)));
- // } else {
- // this.completeWebRtcPeerReceive(reconnect, false, response.sdpOffer)
- // .then(() => resolve())
- // .catch((error) => reject(error));
- // }
- // });
- // });
- }
-
- /**
- * @hidden
- */
- completeWebRtcPeerReceive(reconnect: boolean, forciblyReconnect: boolean, sdpOfferByServer?: string): Promise {
- throw new Error("'completeWebRtcPeerReceive' method is not ready yet.");
- // return new Promise((resolve, reject) => {
- // logger.debug("'Session.subscribe(Stream)' called");
-
- // const sendSdpToServer = (sdpString: string) => {
- // logger.debug(`Sending local SDP ${!!sdpOfferByServer ? 'answer' : 'offer'} to subscribe to ${this.streamId}`, sdpString);
-
- // const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
- // const params = {};
- // params[reconnect ? 'stream' : 'sender'] = this.streamId;
- // if (!!sdpOfferByServer) {
- // params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpString;
- // } else {
- // params['sdpOffer'] = sdpString;
- // }
- // if (reconnect) {
- // params['forciblyReconnect'] = forciblyReconnect;
- // }
-
- // this.session.openvidu.sendRequest(method, params, (error, response) => {
- // if (error) {
- // return reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
- // } else {
- // return resolve(response);
- // }
- // });
- // };
-
- // const config: WebRtcPeerConfiguration = {
- // mediaConstraints: {
- // audio: this.hasAudio,
- // video: this.hasVideo
- // },
- // simulcast: false,
- // onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
- // onIceConnectionStateException: this.onIceConnectionStateExceptionHandler.bind(this),
- // iceServers: this.getIceServersConf(),
- // mediaServer: this.session.openvidu.mediaServer,
- // typeOfVideo: this.typeOfVideo ? TypeOfVideo[this.typeOfVideo] : undefined
- // };
-
- // if (reconnect) {
- // this.disposeWebRtcPeer();
- // }
-
- // this.webRtcPeer = new WebRtcPeerRecvonly(config);
- // this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
-
- // if (!!sdpOfferByServer) {
- // this.webRtcPeer
- // .processRemoteOffer(sdpOfferByServer)
- // .then(() => {
- // this.webRtcPeer
- // .createAnswer()
- // .then((sdpAnswer) => {
- // this.webRtcPeer
- // .processLocalAnswer(sdpAnswer)
- // .then(() => {
- // sendSdpToServer(sdpAnswer.sdp!);
- // })
- // .catch((error) => {
- // return reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
- // });
- // })
- // .catch((error) => {
- // return reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error)));
- // });
- // })
- // .catch((error) => {
- // return reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error)));
- // });
- // } else {
- // this.webRtcPeer
- // .createOffer()
- // .then((sdpOffer) => {
- // this.webRtcPeer
- // .processLocalOffer(sdpOffer)
- // .then(() => {
- // sendSdpToServer(sdpOffer.sdp!);
- // })
- // .catch((error) => {
- // return reject(new Error('(subscribe) SDP process local offer error: ' + JSON.stringify(error)));
- // });
- // })
- // .catch((error) => {
- // return reject(new Error('(subscribe) SDP create offer error: ' + JSON.stringify(error)));
- // });
- // }
- // });
- }
-
- /**
- * @hidden
- */
- remotePeerSuccessfullyEstablished(reconnect: boolean): void {
- throw new Error("'remotePeerSuccessfullyEstablished' method is not ready yet.");
- // if (reconnect && this.mediaStream != null) {
- // // Now we can destroy the existing MediaStream
- // this.disposeMediaStream();
- // }
-
- // this.mediaStream = new MediaStream();
- // let receiver: RTCRtpReceiver;
- // for (receiver of this.webRtcPeer.pc.getReceivers()) {
- // if (!!receiver.track) {
- // this.mediaStream.addTrack(receiver.track);
- // }
- // }
- // logger.debug('Peer remote stream', this.mediaStream);
-
- // if (!!this.mediaStream) {
- // if (this.streamManager instanceof Subscriber) {
- // // Apply SubscriberProperties.subscribeToAudio and SubscriberProperties.subscribeToVideo
- // if (!!this.mediaStream.getAudioTracks()[0]) {
- // const enabled = reconnect ? this.audioActive : !!(this.streamManager as Subscriber).properties.subscribeToAudio;
- // this.mediaStream.getAudioTracks()[0].enabled = enabled;
- // }
- // if (!!this.mediaStream.getVideoTracks()[0]) {
- // const enabled = reconnect ? this.videoActive : !!this.videoActive && !!(this.streamManager as Subscriber).properties.subscribeToVideo;
- // this.mediaStream.getVideoTracks()[0].enabled = enabled;
- // }
- // }
-
- // this.updateMediaStreamInVideos();
- // this.initHarkEvents(); // Init hark events for the remote stream
- // }
- }
-
- /**
- * @hidden
- */
- // initHarkEvents(): void {
- // throw new Error("'initHarkEvents' method is not ready yet.");
- // if (!!this.mediaStream!.getAudioTracks()[0]) {
- // // Hark events can only be set if audio track is available
- // if (this.session.anySpeechEventListenerEnabled('publisherStartSpeaking', true, this.streamManager)) {
- // this.enableOnceHarkSpeakingEvent();
- // }
- // if (this.session.anySpeechEventListenerEnabled('publisherStartSpeaking', false, this.streamManager)) {
- // this.enableHarkSpeakingEvent();
- // }
- // if (this.session.anySpeechEventListenerEnabled('publisherStopSpeaking', true, this.streamManager)) {
- // this.enableOnceHarkStoppedSpeakingEvent();
- // }
- // if (this.session.anySpeechEventListenerEnabled('publisherStopSpeaking', false, this.streamManager)) {
- // this.enableHarkStoppedSpeakingEvent();
- // }
- // if (this.harkVolumeChangeEnabledOnce) {
- // this.enableOnceHarkVolumeChangeEvent(true);
- // }
- // if (this.harkVolumeChangeEnabled) {
- // this.enableHarkVolumeChangeEvent(true);
- // }
- // }
- // }
-
- private onIceConnectionStateExceptionHandler(exceptionName: ExceptionEventName, message: string, data?: any): void {
- throw new Error("'onIceConnectionStateExceptionHandler' method is not ready yet.");
- // switch (exceptionName) {
- // case ExceptionEventName.ICE_CONNECTION_FAILED:
- // this.onIceConnectionFailed();
- // break;
- // case ExceptionEventName.ICE_CONNECTION_DISCONNECTED:
- // this.onIceConnectionDisconnected();
- // break;
- // }
- // this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]);
- }
-
- private onIceConnectionFailed() {
- throw new Error("'onIceConnectionFailed' method is not ready yet.");
- // // Immediately reconnect, as this is a terminal error
- // logger.log(
- // `[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // })`
- // );
- // this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED);
- }
-
- private onIceConnectionDisconnected() {
- throw new Error("'onIceConnectionDisconnected' method is not ready yet.");
- // Wait to see if the ICE connection is able to reconnect
- // logger.log(
- // `[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${this.streamId
- // } (${this.isLocal() ? 'Publisher' : 'Subscriber'}) if not possible`
- // );
- // const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
- // this.awaitWebRtcPeerConnectionState(timeout).then((state) => {
- // switch (state) {
- // case 'failed':
- // // Do nothing, as an ICE_CONNECTION_FAILED event will have already raised
- // logger.warn(
- // `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) is now failed after ICE_CONNECTION_DISCONNECTED`
- // );
- // break;
- // case 'connected':
- // case 'completed':
- // logger.log(
- // `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}`
- // );
- // break;
- // case 'closed':
- // case 'checking':
- // case 'new':
- // case 'disconnected':
- // // Rest of states
- // logger.warn(
- // `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}`
- // );
- // this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED);
- // break;
- // }
- // });
- }
-
- private async reconnectStreamAndLogResultingIceConnectionState(event: string) {
- throw new Error("'reconnectStreamAndLogResultingIceConnectionState' method is not ready yet.");
- // try {
- // const finalIceStateAfterReconnection = await this.reconnectStreamAndReturnIceConnectionState(event);
- // switch (finalIceStateAfterReconnection) {
- // case 'connected':
- // case 'completed':
- // logger.log(
- // `[${event}] Stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`
- // );
- // break;
- // default:
- // logger.error(
- // `[${event}] Stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`
- // );
- // break;
- // }
- // } catch (error) {
- // logger.error(
- // `[${event}] Error reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) after ${event}: ${error}`
- // );
- // }
- }
-
- private async reconnectStreamAndReturnIceConnectionState(event: string): Promise {
- throw new Error("'reconnectStreamAndReturnIceConnectionState' method is not ready yet.");
- // logger.log(`[${event}] Reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'}) after event ${event}`);
- // try {
- // await this.reconnectStream(event);
- // const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
- // return this.awaitWebRtcPeerConnectionState(timeout);
- // } catch (error) {
- // logger.warn(
- // `[${event}] Error reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'}). Reason: ${error}`
- // );
- // return this.awaitWebRtcPeerConnectionState(1);
- // }
- }
-
- private async awaitWebRtcPeerConnectionState(timeout: number): Promise {
- throw new Error("'awaitWebRtcPeerConnectionState' method is not ready yet.");
- // let state = this.getRTCPeerConnection().iceConnectionState;
- // const interval = 150;
- // const intervals = Math.ceil(timeout / interval);
- // for (let i = 0; i < intervals; i++) {
- // state = this.getRTCPeerConnection().iceConnectionState;
- // if (state === 'connected' || state === 'completed') {
- // break;
- // }
- // // Sleep
- // await new Promise((resolve) => setTimeout(resolve, interval));
- // }
- // return state;
- }
-
- private async reconnectStream(event: string) {
- throw new Error("'reconnectStream' method is not ready yet.");
- // const isWsConnected = await this.isWebsocketConnected(event, 3000);
- // if (isWsConnected) {
- // // There is connection to openvidu-server. The RTCPeerConnection is the only one broken
- // logger.log(
- // `[${event}] Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) and the websocket is opened`
- // );
- // if (this.isLocal()) {
- // return this.initWebRtcPeerSend(true);
- // } else {
- // return this.initWebRtcPeerReceive(true);
- // }
- // } else {
- // // There is no connection to openvidu-server. Nothing can be done. The automatic reconnection
- // // feature should handle a possible reconnection of RTCPeerConnection in case network comes back
- // const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
- // }) but the websocket wasn't opened`;
- // logger.error(errorMsg);
- // throw Error(errorMsg);
- // }
- }
-
- private isWebsocketConnected(event: string, msResponseTimeout: number): Promise {
- throw new Error("'isWebsocketConnected' method is not ready yet.");
- return new Promise((resolve, reject) => {
- const wsReadyState = this.session.openvidu.getWsReadyState();
- if (wsReadyState === 1) {
- const responseTimeout = setTimeout(() => {
- console.warn(`[${event}] Websocket timeout of ${msResponseTimeout}ms`);
- return resolve(false);
- }, msResponseTimeout);
- this.session.openvidu.sendRequest('echo', {}, (error, response) => {
- clearTimeout(responseTimeout);
- if (!!error) {
- console.warn(`[${event}] Websocket 'echo' returned error: ${error}`);
- return resolve(false);
- } else {
- return resolve(true);
- }
- });
- } else {
- console.warn(`[${event}] Websocket readyState is ${wsReadyState}`);
- return resolve(false);
- }
- });
- }
-
- /**
- * @hidden
- */
- initWebRtcStats(): void {
- throw new Error("'initWebRtcStats' method is not ready yet.");
- this.webRtcStats = new WebRtcStats(this);
- this.webRtcStats.initWebRtcStats();
-
- //TODO: send common webrtc stats from client to openvidu-server
- /*if (this.session.openvidu.webrtcStatsInterval > 0) {
- setInterval(() => {
- this.gatherStatsForPeer().then(jsonStats => {
- const body = {
- sessionId: this.session.sessionId,
- participantPrivateId: this.connection.rpcSessionId,
- stats: jsonStats
- }
- var xhr = new XMLHttpRequest();
- xhr.open('POST', this.session.openvidu.httpUri + '/elasticsearch/webrtc-stats', true);
- xhr.setRequestHeader('Content-Type', 'application/json');
- xhr.send(JSON.stringify(body));
- })
- }, this.session.openvidu.webrtcStatsInterval * 1000);
- }*/
- }
-
- private stopWebRtcStats(): void {
- throw new Error("'stopWebRtcStats' method is not ready yet.");
- if (!!this.webRtcStats && this.webRtcStats.isEnabled()) {
- this.webRtcStats.stopWebRtcStats();
- }
- }
-
- private getIceServersConf(): RTCIceServer[] | undefined {
- throw new Error("'getIceServersConf' method is not ready yet.");
- let returnValue;
- if (!!this.session.openvidu.advancedConfiguration.iceServers) {
- returnValue =
- this.session.openvidu.advancedConfiguration.iceServers === 'freeice'
- ? undefined
- : this.session.openvidu.advancedConfiguration.iceServers;
- } else if (this.session.openvidu.iceServers) {
- returnValue = this.session.openvidu.iceServers;
- } else {
- returnValue = undefined;
- }
- return returnValue;
- }
-
- private gatherStatsForPeer(): Promise {
- throw new Error("'gatherStatsForPeer' method is not ready yet.");
- return new Promise((resolve, reject) => {
- if (this.isLocal()) {
- // Publisher stream stats
-
- this.getRTCPeerConnection()
- .getSenders()
- .forEach((sender) =>
- sender.getStats().then((response) => {
- response.forEach((report) => {
- if (this.isReportWanted(report)) {
- const finalReport = {};
-
- finalReport['type'] = report.type;
- finalReport['timestamp'] = report.timestamp;
- finalReport['id'] = report.id;
-
- // Common to Chrome, Firefox and Safari
- if (report.type === 'outbound-rtp') {
- finalReport['ssrc'] = report.ssrc;
- finalReport['firCount'] = report.firCount;
- finalReport['pliCount'] = report.pliCount;
- finalReport['nackCount'] = report.nackCount;
- finalReport['qpSum'] = report.qpSum;
-
- // Set media type
- if (!!report.kind) {
- finalReport['mediaType'] = report.kind;
- } else if (!!report.mediaType) {
- finalReport['mediaType'] = report.mediaType;
- } else {
- // Safari does not have 'mediaType' defined for inbound-rtp. Must be inferred from 'id' field
- finalReport['mediaType'] = report.id.indexOf('VideoStream') !== -1 ? 'video' : 'audio';
- }
-
- if (finalReport['mediaType'] === 'video') {
- finalReport['framesEncoded'] = report.framesEncoded;
- }
-
- finalReport['packetsSent'] = report.packetsSent;
- finalReport['bytesSent'] = report.bytesSent;
- }
-
- // Only for Chrome and Safari
- if (report.type === 'candidate-pair' && report.totalRoundTripTime !== undefined) {
- // This is the final selected candidate pair
- finalReport['availableOutgoingBitrate'] = report.availableOutgoingBitrate;
- finalReport['rtt'] = report.currentRoundTripTime;
- finalReport['averageRtt'] = report.totalRoundTripTime / report.responsesReceived;
- }
-
- // Only for Firefox >= 66.0
- if (report.type === 'remote-inbound-rtp' || report.type === 'remote-outbound-rtp') {
- }
-
- logger.log(finalReport);
- }
- });
- })
- );
- } else {
- // Subscriber stream stats
-
- this.getRTCPeerConnection()
- .getReceivers()
- .forEach((receiver) =>
- receiver.getStats().then((response) => {
- response.forEach((report) => {
- if (this.isReportWanted(report)) {
- const finalReport = {};
-
- finalReport['type'] = report.type;
- finalReport['timestamp'] = report.timestamp;
- finalReport['id'] = report.id;
-
- // Common to Chrome, Firefox and Safari
- if (report.type === 'inbound-rtp') {
- finalReport['ssrc'] = report.ssrc;
- finalReport['firCount'] = report.firCount;
- finalReport['pliCount'] = report.pliCount;
- finalReport['nackCount'] = report.nackCount;
- finalReport['qpSum'] = report.qpSum;
-
- // Set media type
- if (!!report.kind) {
- finalReport['mediaType'] = report.kind;
- } else if (!!report.mediaType) {
- finalReport['mediaType'] = report.mediaType;
- } else {
- // Safari does not have 'mediaType' defined for inbound-rtp. Must be inferred from 'id' field
- finalReport['mediaType'] = report.id.indexOf('VideoStream') !== -1 ? 'video' : 'audio';
- }
-
- if (finalReport['mediaType'] === 'video') {
- finalReport['framesDecoded'] = report.framesDecoded;
- }
-
- finalReport['packetsReceived'] = report.packetsReceived;
- finalReport['packetsLost'] = report.packetsLost;
- finalReport['jitter'] = report.jitter;
- finalReport['bytesReceived'] = report.bytesReceived;
- }
-
- // Only for Chrome and Safari
- if (report.type === 'candidate-pair' && report.totalRoundTripTime !== undefined) {
- // This is the final selected candidate pair
- finalReport['availableIncomingBitrate'] = report.availableIncomingBitrate;
- finalReport['rtt'] = report.currentRoundTripTime;
- finalReport['averageRtt'] = report.totalRoundTripTime / report.responsesReceived;
- }
-
- // Only for Firefox >= 66.0
- if (report.type === 'remote-inbound-rtp' || report.type === 'remote-outbound-rtp') {
- }
- logger.log(finalReport);
- }
- });
- })
- );
- }
- });
- }
-
- private isReportWanted(report: any): boolean {
- throw new Error("'isReportWanted' method is not ready yet.");
- return (
- (report.type === 'inbound-rtp' && !this.isLocal()) ||
- (report.type === 'outbound-rtp' && this.isLocal()) ||
- (report.type === 'candidate-pair' && report.nominated && report.bytesSent > 0)
- );
- }
}
diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts
index d0cc7555299..88f2497b08b 100644
--- a/openvidu-browser/src/OpenVidu/StreamManager.ts
+++ b/openvidu-browser/src/OpenVidu/StreamManager.ts
@@ -1,32 +1,9 @@
-/*
- * (C) Copyright 2017-2022 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 { Subscriber } from './Subscriber';
-import { EventDispatcher } from './EventDispatcher';
-import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
-import { StreamManagerEventMap } from '../OpenViduInternal/Events/EventMap/StreamManagerEventMap';
+import { RoomEvent } from 'livekit-client';
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
-import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
-import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
-import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
-import { LocalAudioTrack, LocalTrack, LocalVideoTrack, RemoteAudioTrack, RemoteTrack, RemoteVideoTrack } from 'livekit-client';
+import { EventDispatcher } from './EventDispatcher';
+import { Stream } from './Stream';
/**
* @hidden
@@ -36,29 +13,14 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
-let platform: PlatformUtils;
+let platform: PlatformUtils = PlatformUtils.getInstance();
-/**
- * Interface in charge of displaying the media streams in the HTML DOM. This wraps any {@link Publisher} and {@link Subscriber} object.
- * You can insert as many video players fo the same Stream as you want by calling {@link StreamManager.addVideoElement} or
- * {@link 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
- *
- * See available event listeners at {@link StreamManagerEventMap}.
- */
export abstract class StreamManager extends EventDispatcher {
/**
* 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 {@link Publisher}
@@ -73,48 +35,25 @@ export abstract class StreamManager extends EventDispatcher {
*/
targetElement: HTMLElement;
- /**
- * `id` attribute of the DOM video element displaying the Publisher/Subscriber's stream. This property is only defined if:
- * - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
- * - {@link Subscriber} has been initialized by calling method {@link Session.subscribe} with a valid `targetElement` parameter
- */
- id: string;
+ // TODO: Why this is necessary if target element is already defined?
+ element: HTMLElement;
- /**
- * @hidden
- */
- protected firstVideoElement?: StreamManagerVideo;
- /**
- * @hidden
- */
- protected element: HTMLElement;
/**
* @hidden
*/
protected canPlayListener: EventListener;
- /**
- * @hidden
- */
- protected streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
- /**
- * @hidden
- */
- private lazyLaunchVideoElementCreatedEvent = false;
/**
* @hidden
*/
constructor(stream: Stream, targetElement?: HTMLElement | string) {
- console.warn("'StreamManager' class is not ready yet");
super();
-
- // platform = PlatformUtils.getInstance();
this.stream = stream;
this.stream.streamManager = this;
this.remote = !this.stream.isLocal();
if (!!targetElement) {
- // TODO. si el susuario proporciona un targetElement habria que crear un video dentro del componente con ese targetElement
+ // TODO. si el usuario proporciona un targetElement habria que crear un video dentro del componente con ese targetElement
let targetElementAux: HTMLElement | null = null;
if (typeof targetElement === 'string') {
targetElementAux = document.getElementById(targetElement);
@@ -122,13 +61,14 @@ export abstract class StreamManager extends EventDispatcher {
targetElementAux = targetElement;
}
- if (!!targetElementAux) {
- this.firstVideoElement = {
- targetElement: targetElementAux,
- video: document.createElement('video'),
- id: '',
- canplayListenerAdded: false
- };
+ // TODO: If targetElementAux is not null, create a video inside the component with that targetElement
+ if (targetElementAux) {
+ // this.firstVideoElement = {
+ // targetElement: targetElementAux,
+ // video: document.createElement('video'),
+ // id: '',
+ // canplayListenerAdded: false
+ // };
// if (
// platform.isSafariBrowser() ||
// (platform.isIPhoneOrIPad() &&
@@ -140,139 +80,16 @@ export abstract class StreamManager extends EventDispatcher {
// this.firstVideoElement.video.playsInline = true;
// }
this.targetElement = targetElementAux;
- this.element = targetElementAux;
+ // this.element = targetElementAux;
}
}
this.canPlayListener = () => {
- this.deactivateStreamPlayingEventExceptionTimeout();
+ // this.deactivateStreamPlayingEventExceptionTimeout();
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
};
}
- /**
- * See {@link EventDispatcher.on}
- */
- on(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
- console.warn("STREAM MANAGER 'on' method is not ready yet");
- super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
-
- if (type === 'videoElementCreated') {
- // throw new Error("'videoElementCreated' event is not ready yet");
- // TODO it is necessary the lazyLaunchVideoElementCreatedEvent?
- // this.lazyLaunchVideoElementCreatedEvent = true;
- if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
- this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
- this.lazyLaunchVideoElementCreatedEvent = false;
- }
- }
- if (type === 'streamPlaying') {
- // throw new Error("'streamPlaying' event is not ready yet");
-
- 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, 'streamPlaying', undefined)]);
- }
- }
- if (this.stream.hasAudio) {
- if (type === 'publisherStartSpeaking') {
- throw new Error("'publisherStartSpeaking' event is not ready yet");
- this.stream.enableHarkSpeakingEvent();
- }
- if (type === 'publisherStopSpeaking') {
- throw new Error("'publisherStopSpeaking' event is not ready yet");
- this.stream.enableHarkStoppedSpeakingEvent();
- }
- if (type === 'streamAudioVolumeChange') {
- throw new Error("'streamAudioVolumeChange' event is not ready yet");
- this.stream.enableHarkVolumeChangeEvent(false);
- }
- }
- return this;
- }
-
- /**
- * See {@link EventDispatcher.once}
- */
- once(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
- super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
-
- if (type === 'videoElementCreated') {
- if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
- this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
- }
- }
- if (type === 'streamPlaying') {
- 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, 'streamPlaying', undefined)]);
- }
- }
- // if (this.stream.hasAudio) {
- // if (type === 'publisherStartSpeaking') {
- // this.stream.enableOnceHarkSpeakingEvent();
- // }
- // if (type === 'publisherStopSpeaking') {
- // this.stream.enableOnceHarkStoppedSpeakingEvent();
- // }
- // if (type === 'streamAudioVolumeChange') {
- // this.stream.enableOnceHarkVolumeChangeEvent(false);
- // }
- // }
- return this;
- }
-
- /**
- * See {@link EventDispatcher.off}
- */
- off(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
- super.offAux(type, handler);
-
- if (type === 'publisherStartSpeaking') {
- throw new Error("'off publisherStartSpeaking' method is not ready yet");
-
- // Both StreamManager and Session can have "publisherStartSpeaking" event listeners
- const remainingStartSpeakingEventListeners =
- this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
- if (remainingStartSpeakingEventListeners === 0) {
- this.stream.disableHarkSpeakingEvent(false);
- }
- }
- if (type === 'publisherStopSpeaking') {
- throw new Error("'off publisherStopSpeaking' method is not ready yet");
-
- // Both StreamManager and Session can have "publisherStopSpeaking" event listeners
- const remainingStopSpeakingEventListeners =
- this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
- if (remainingStopSpeakingEventListeners === 0) {
- this.stream.disableHarkStoppedSpeakingEvent(false);
- }
- }
- if (type === 'streamAudioVolumeChange') {
- throw new Error("'off streamAudioVolumeChange' method is not ready yet");
-
- // Only StreamManager can have "streamAudioVolumeChange" event listeners
- const remainingVolumeEventListeners = this.ee.getListeners(type).length;
- if (remainingVolumeEventListeners === 0) {
- this.stream.disableHarkVolumeChangeEvent(false);
- }
- }
-
- return this;
- }
-
/**
* Makes `video` element parameter display this {@link stream}. This is useful when you are
* [managing the video elements on your own](/en/stable/cheatsheet/manage-videos/#you-take-care-of-the-video-players)
@@ -285,302 +102,25 @@ export abstract class StreamManager extends EventDispatcher {
* Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one.
*/
addVideoElement(video: HTMLVideoElement): number {
- console.warn('addVideoElement not ready yet in StreamManagerAdapter');
- this.initializeVideoProperties(video);
+ if (this.stream.isLocal()) {
+ this.stream.ee.once('stream-ready-to-publish', () => {
+ console.log('stream-ready-to-publish');
- // TODO: livekit attach method must be called here
- if (this.stream.temporalVideoTrack || this.stream.temporalAudioTrack) {
+ this.stream.session.localParticipant.getTracks().forEach((track) => {
+ console.log('attaching track ', track);
+ track.track?.attach(video);
+ });
+ });
+ } else {
+ console.error('Add video element remote!!', this.stream);
- if(this.stream.temporalVideoTrack) {
- this.stream.temporalVideoTrack.attach(video);
- }
- if(this.stream.temporalAudioTrack) {
- this.stream.temporalAudioTrack.attach(video);
- }
-
-
- } else if (this.stream.temporalVideoScreenTrack || this.stream.temporalAudioScreenTrack) {
-
- if (this.stream.temporalVideoScreenTrack) {
- this.stream.temporalVideoScreenTrack.attach(video);
- }
- if (this.stream.temporalAudioScreenTrack) {
- this.stream.temporalAudioScreenTrack.attach(video);
- }
- }
-
- // if (!this.remote && this.stream.displayMyRemote()) {
- // if (video.srcObject !== this.stream.getMediaStream()) {
- // video.srcObject = this.stream.getMediaStream();
- // }
- // }
-
- // If the video element is already part of this StreamManager do nothing
- for (const v of this.videos) {
- if (v.video === video) {
- return 0;
- }
+ this.stream.connection.participant.getTracks().forEach((track) => {
+ console.log('attaching track ', track);
+ track.track?.attach(video);
+ });
}
- let returnNumber = 1;
-
- 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,
- canplayListenerAdded: false
- });
-
- logger.info('New video element associated to ', this);
-
- return returnNumber;
- }
-
- /**
- * Creates a new video element displaying this {@link stream}. This allows you to have multiple video elements displaying the same media stream.
- *
- * #### Events dispatched
- *
- * The Publisher/Subscriber object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM. See {@link VideoElementEvent}
- *
- * @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher/Subscriber will be inserted
- * @param insertMode How the video element will be inserted accordingly to `targetElemet`
- *
- * @returns The created HTMLVideoElement
- */
- createVideoElement(targetElement?: string | HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement {
- throw new Error("'createVideoElement' method is not ready yet");
- // let targEl;
- // if (typeof targetElement === 'string') {
- // targEl = document.getElementById(targetElement);
- // 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 = this.createVideo();
- // 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,
- // canplayListenerAdded: false
- // };
- // this.pushNewStreamManagerVideo(v);
-
- // this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]);
- // this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement;
-
- // return video;
- }
-
- /**
- * Updates the current configuration for the {@link PublisherSpeakingEvent} feature and the [StreamManagerEvent.streamAudioVolumeChange](/en/stable/api/openvidu-browser/classes/StreamManagerEvent.html) feature for this specific
- * StreamManager audio stream, overriding the global options set with {@link OpenVidu.setAdvancedConfiguration}. This way you can customize the audio events options
- * for each specific StreamManager and change them dynamically.
- *
- * @param publisherSpeakingEventsOptions New options to be applied to this StreamManager's audio stream. It is an object which includes the following optional properties:
- * - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
- * - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
- */
- updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number; threshold?: number }): void {
- throw new Error("'updatePublisherSpeakingEventsOptions' method is not ready yet");
- const currentHarkOptions = !!this.stream.harkOptions
- ? this.stream.harkOptions
- : this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
- const newInterval =
- typeof publisherSpeakingEventsOptions.interval === 'number'
- ? publisherSpeakingEventsOptions.interval
- : typeof currentHarkOptions.interval === 'number'
- ? currentHarkOptions.interval
- : 100;
- const newThreshold =
- typeof publisherSpeakingEventsOptions.threshold === 'number'
- ? publisherSpeakingEventsOptions.threshold
- : typeof currentHarkOptions.threshold === 'number'
- ? currentHarkOptions.threshold
- : -50;
- this.stream.harkOptions = {
- interval: newInterval,
- threshold: newThreshold
- };
- if (!!this.stream.speechEvent) {
- this.stream.speechEvent.setInterval(newInterval);
- this.stream.speechEvent.setThreshold(newThreshold);
- }
- }
-
- /* Hidden methods */
-
- /**
- * @hidden
- */
- initializeVideoProperties(video: HTMLVideoElement): void {
- // throw new Error("'initializeVideoProperties' method is not ready yet");
- // if (!(!this.remote && this.stream.displayMyRemote())) {
- // // Avoid setting the MediaStream into the srcObject if remote subscription before publishing
- // if (video.srcObject !== this.stream.getMediaStream()) {
- // // If srcObject already set don't do it again
- // video.srcObject = this.stream.getMediaStream();
- // }
- // }
- video.autoplay = true;
- video.controls = false;
-
- // if (
- // platform.isSafariBrowser() ||
- // (platform.isIPhoneOrIPad() &&
- // (platform.isChromeMobileBrowser() ||
- // platform.isEdgeMobileBrowser() ||
- // platform.isOperaMobileBrowser() ||
- // platform.isFirefoxMobileBrowser()))
- // ) {
- video.playsInline = true;
- // }
-
- if (!video.id) {
- video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId;
- // DEPRECATED property: assign once the property id if the user provided a valid targetElement
- if (!this.id && !!this.targetElement) {
- this.id = video.id;
- }
- }
-
- // if (this.remote && this.isMirroredVideo(video)) {
- // // Subscriber video associated to a previously mirrored video element
- // this.removeMirrorVideo(video);
- // } else
- if (!this.remote && !this.stream.displayMyRemote()) {
- // Publisher video
- video.muted = true;
- }
- // if (this.isMirroredVideo(video) && !this.stream.outboundStreamOpts.publisherProperties.mirror) {
- // // If the video was already rotated and now is set to not mirror
- // this.removeMirrorVideo(video);
- // } else if (this.stream.outboundStreamOpts.publisherProperties.mirror && !this.stream.isSendScreen()) {
- // // If the video is now set to mirror and is not screen share
- // this.mirrorVideo(video);
- // }
- // }
- }
-
- /**
- * @hidden
- */
- removeAllVideos(): void {
- console.warn("'removeAllVideos' method is not ready yet");
- 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.forEach((streamManagerVideo) => {
- // // Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
- // if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
- // streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
- // }
- // streamManagerVideo.canplayListenerAdded = false;
- // if (!!streamManagerVideo.targetElement) {
- // // Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
- // // and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
- // streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
- // this.ee.emitEvent('videoElementDestroyed', [
- // new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')
- // ]);
- // }
- // // Remove srcObject from the video
- // this.removeSrcObject(streamManagerVideo);
- // // Remove from collection of videos every video managed by OpenVidu Browser
- // this.videos.filter((v) => !v.targetElement);
- // });
- }
-
- /**
- * @hidden
- */
- disassociateVideo(video: HTMLVideoElement): boolean {
- // throw new Error("'disassociateVideo' method is not ready yet");
- let disassociated = false;
- for (let i = 0; i < this.videos.length; i++) {
- if (this.videos[i].video === video) {
- this.videos[i].video.removeEventListener('canplay', this.canPlayListener);
- this.videos.splice(i, 1);
- disassociated = true;
- logger.info('Video element disassociated from ', this);
- break;
- }
- }
- return disassociated;
- }
-
- /**
- * @hidden
- */
- addPlayEventToFirstVideo() {
- console.warn("'addPlayEventToFirstVideo' method is not ready yet");
- if (!!this.videos[0] && !!this.videos[0].video && !this.videos[0].canplayListenerAdded) {
- this.activateStreamPlayingEventExceptionTimeout();
- this.videos[0].video.addEventListener('canplay', this.canPlayListener);
- this.videos[0].canplayListenerAdded = true;
- }
- }
-
- /**
- * @hidden
- */
- updateMediaStream(mediaStream: MediaStream) {
- throw new Error("'updateMediaStream' method is not ready yet");
- this.videos.forEach((streamManagerVideo) => {
- streamManagerVideo.video.srcObject = mediaStream;
- if (platform.isIonicIos()) {
- // iOS Ionic. LIMITATION: must reinsert the video in the DOM for
- // the media stream to be updated
- const vParent = streamManagerVideo.video.parentElement;
- const newVideo = streamManagerVideo.video;
- vParent!!.replaceChild(newVideo, streamManagerVideo.video);
- streamManagerVideo.video = newVideo;
- }
- });
+ return 0;
}
/**
@@ -590,92 +130,5 @@ export abstract class StreamManager extends EventDispatcher {
this.ee.emitEvent(type, eventArray);
}
- /**
- * @hidden
- */
- createVideo(): HTMLVideoElement {
- throw new Error("'createVideo' method is not ready yet");
- return document.createElement('video');
- }
-
- /**
- * @hidden
- */
- removeSrcObject(streamManagerVideo: StreamManagerVideo) {
- throw new Error("'removeSrcObject' method is not ready yet");
- streamManagerVideo.video.srcObject = null;
- this.deactivateStreamPlayingEventExceptionTimeout();
- }
-
- /**
- * @hidden
- */
- abstract replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void;
-
- /* Private methods */
-
- protected pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) {
- // throw new Error("'pushNewStreamManagerVideo' method is not ready yet");
- this.videos.push(streamManagerVideo);
- this.addPlayEventToFirstVideo();
- if (this.stream.session.streamManagers.indexOf(this) === -1) {
- this.stream.session.streamManagers.push(this);
- this.id;
- }
- }
-
- private mirrorVideo(video: HTMLVideoElement): void {
- throw new Error("'mirrorVideo' method is not ready yet");
- if (!platform.isIonicIos()) {
- video.style.transform = 'rotateY(180deg)';
- video.style.webkitTransform = 'rotateY(180deg)';
- }
- }
-
- private removeMirrorVideo(video: HTMLVideoElement): void {
- throw new Error("'removeMirrorVideo' method is not ready yet");
- video.style.transform = 'unset';
- video.style.webkitTransform = 'unset';
- }
-
- private isMirroredVideo(video: HTMLVideoElement): boolean {
- throw new Error("'isMirroredVideo' method is not ready yet");
- return video.style.transform === 'rotateY(180deg)' || video.style.webkitTransform === 'rotateY(180deg)';
- }
-
- // TODO Necesario?
- private activateStreamPlayingEventExceptionTimeout() {
- if (!this.remote) {
- // ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers
- return;
- }
- if (this.streamPlayingEventExceptionTimeout != null) {
- // The timeout is already activated
- return;
- }
- // Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
- const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
- this.streamPlayingEventExceptionTimeout = setTimeout(() => {
- const msg =
- 'StreamManager of Stream ' +
- this.stream.streamId +
- ' (' +
- (this.remote ? 'Subscriber' : 'Publisher') +
- ') did not trigger "streamPlaying" event in ' +
- msTimeout +
- ' ms';
- logger.warn(msg);
- this.stream.session.emitEvent('exception', [
- new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (this) as Subscriber, msg)
- ]);
- delete this.streamPlayingEventExceptionTimeout;
- }, msTimeout);
- }
-
- //TODO Necesario?
- private deactivateStreamPlayingEventExceptionTimeout() {
- // throw new Error("'deactivateStreamPlayingEventExceptionTimeout' method is not ready yet");
- clearTimeout(this.streamPlayingEventExceptionTimeout as any);
- delete this.streamPlayingEventExceptionTimeout;
- }
+ removeAllVideos() {}
}
diff --git a/openvidu-browser/src/OpenVidu/Subscriber.ts b/openvidu-browser/src/OpenVidu/Subscriber.ts
index 13f9c0cca6c..7cbe6cfb47e 100644
--- a/openvidu-browser/src/OpenVidu/Subscriber.ts
+++ b/openvidu-browser/src/OpenVidu/Subscriber.ts
@@ -19,6 +19,7 @@ import { Stream } from './Stream';
import { StreamManager } from './StreamManager';
import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
+import { EventMap } from '../OpenViduInternal/Events/EventMap/EventMap';
/**
* @hidden
@@ -40,7 +41,6 @@ export class Subscriber extends StreamManager {
* @hidden
*/
constructor(stream: Stream, targEl: string | HTMLElement | undefined, properties: SubscriberProperties) {
-
super(stream, targEl);
this.element = this.targetElement;
this.stream = stream;
@@ -52,17 +52,16 @@ export class Subscriber extends StreamManager {
* @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it
*/
subscribeToAudio(value: boolean): Subscriber {
-
throw new Error('subscribeToAudio is not ready yet');
- this.stream
- .getMediaStream()
- .getAudioTracks()
- .forEach((track) => {
- track.enabled = value;
- });
- this.stream.audioActive = value;
- logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its audio stream');
- return this;
+ // this.stream
+ // .getMediaStream()
+ // .getAudioTracks()
+ // .forEach((track) => {
+ // track.enabled = value;
+ // });
+ // this.stream.audioActive = value;
+ // logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its audio stream');
+ // return this;
}
/**
@@ -70,17 +69,26 @@ export class Subscriber extends StreamManager {
* @param value `true` to subscribe to the video stream, `false` to unsubscribe from it
*/
subscribeToVideo(value: boolean): Subscriber {
-
throw new Error('subscribeToVideo is not ready yet');
- this.stream
- .getMediaStream()
- .getVideoTracks()
- .forEach((track) => {
- track.enabled = value;
- });
- this.stream.videoActive = value;
- logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its video stream');
- return this;
+ // this.stream
+ // .getMediaStream()
+ // .getVideoTracks()
+ // .forEach((track) => {
+ // track.enabled = value;
+ // });
+ // this.stream.videoActive = value;
+ // logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its video stream');
+ // return this;
+ }
+
+ on(type: K, handler: (event: EventMap[K]) => void): this {
+ throw new Error('Method not implemented.');
+ }
+ once(type: K, handler: (event: EventMap[K]) => void): this {
+ throw new Error('Method not implemented.');
+ }
+ off(type: K, handler?: ((event: EventMap[K]) => void) | undefined): this {
+ throw new Error('Method not implemented.');
}
/* Hidden methods */
@@ -89,20 +97,19 @@ export class Subscriber extends StreamManager {
* @hidden
*/
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
-
throw new Error('replaceTrackInMediaStream is not ready yet');
- const mediaStream: MediaStream = this.stream.getMediaStream();
- let removedTrack: MediaStreamTrack;
- if (track.kind === 'video') {
- removedTrack = mediaStream.getVideoTracks()[0];
- if (updateLastConstraints) {
- this.stream.lastVideoTrackConstraints = track.getConstraints();
- }
- } else {
- removedTrack = mediaStream.getAudioTracks()[0];
- }
- mediaStream.removeTrack(removedTrack);
- removedTrack.stop();
- mediaStream.addTrack(track);
+ // const mediaStream: MediaStream = this.stream.getMediaStream();
+ // let removedTrack: MediaStreamTrack;
+ // if (track.kind === 'video') {
+ // removedTrack = mediaStream.getVideoTracks()[0];
+ // if (updateLastConstraints) {
+ // this.stream.lastVideoTrackConstraints = track.getConstraints();
+ // }
+ // } else {
+ // removedTrack = mediaStream.getAudioTracks()[0];
+ // }
+ // mediaStream.removeTrack(removedTrack);
+ // removedTrack.stop();
+ // mediaStream.addTrack(track);
}
}
diff --git a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts
index 34fd2e6e26c..d8b68463af8 100644
--- a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts
+++ b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts
@@ -64,15 +64,15 @@ export class SessionDisconnectedEvent extends Event {
const connectionId = remoteConnection.connectionId;
if (!!session.remoteConnections.get(connectionId)?.stream) {
// session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
- session.remoteConnections.get(connectionId)?.stream!.disposeMediaStream();
- if (session.remoteConnections.get(connectionId)?.stream!.streamManager) {
- session.remoteConnections.get(connectionId)?.stream!.streamManager.removeAllVideos();
- }
+ // session.remoteConnections.get(connectionId)?.stream!.disposeMediaStream();
+ // if (session.remoteConnections.get(connectionId)?.stream!.streamManager) {
+ // session.remoteConnections.get(connectionId)?.stream!.streamManager.removeAllVideos();
+ // }
const streamId = session.remoteConnections.get(connectionId)?.stream?.streamId;
if (!!streamId) {
- session.remoteStreamsCreated.delete(streamId);
+ // session.remoteStreamsCreated.delete(streamId);
}
- session.remoteConnections.get(connectionId)?.dispose();
+ // session.remoteConnections.get(connectionId)?.dispose();
}
session.remoteConnections.delete(connectionId);
});
diff --git a/openvidu-browser/src/OpenViduInternal/Events/SpeechToTextEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SpeechToTextEvent.ts
index fba138f931b..7e09b0a69ab 100644
--- a/openvidu-browser/src/OpenViduInternal/Events/SpeechToTextEvent.ts
+++ b/openvidu-browser/src/OpenViduInternal/Events/SpeechToTextEvent.ts
@@ -37,7 +37,7 @@ export class SpeechToTextEvent extends Event {
text: string;
/**
- * All speech-to-text events are generated
+ * All speech-to-text events are generated
*/
reason: SpeechToTextEventReason;
diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts
index 5d6d04afb21..8cfe11e138e 100644
--- a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts
+++ b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts
@@ -75,8 +75,8 @@ export class StreamEvent extends Event {
} else if (this.target instanceof Publisher) {
// Local Stream
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Publisher'");
- clearInterval((this.target).screenShareResizeInterval);
- this.stream.isLocalStreamReadyToPublish = false;
+ // clearInterval((this.target).screenShareResizeInterval);
+ // this.stream.isLocalStreamReadyToPublish = false;
// Delete Publisher object from OpenVidu publishers array
const openviduPublishers = (this.target).openvidu.publishers;
@@ -96,18 +96,18 @@ export class StreamEvent extends Event {
if (this.stream.streamManager) this.stream.streamManager.removeAllVideos();
// Delete stream from Session.remoteStreamsCreated map
- this.stream.session.remoteStreamsCreated.delete(this.stream.streamId);
+ // this.stream.session.remoteStreamsCreated.delete(this.stream.streamId);
// Delete StreamOptionsServer from remote Connection
- const remoteConnection = this.stream.session.remoteConnections.get(this.stream.connection.connectionId);
- if (!!remoteConnection && !!remoteConnection.remoteOptions) {
- const streamOptionsServer = remoteConnection.remoteOptions.streams;
- for (let i = streamOptionsServer.length - 1; i >= 0; --i) {
- if (streamOptionsServer[i].id === this.stream.streamId) {
- streamOptionsServer.splice(i, 1);
- }
- }
- }
+ // const remoteConnection = this.stream.session.remoteConnections.get(this.stream.connection.connectionId);
+ // if (!!remoteConnection && !!remoteConnection.remoteOptions) {
+ // const streamOptionsServer = remoteConnection.remoteOptions.streams;
+ // for (let i = streamOptionsServer.length - 1; i >= 0; --i) {
+ // if (streamOptionsServer[i].id === this.stream.streamId) {
+ // streamOptionsServer.splice(i, 1);
+ // }
+ // }
+ // }
}
}
}
diff --git a/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts b/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts
index deaad660719..bec7dac7ced 100644
--- a/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts
+++ b/openvidu-browser/src/OpenViduInternal/Logger/OpenViduLogger.ts
@@ -26,113 +26,113 @@ export class OpenViduLogger {
* @hidden
*/
static configureJSNLog(openVidu: OpenVidu, token: string) {
- try {
- // If dev mode or...
- if (
- globalThis['LOG_JSNLOG_RESULTS'] ||
- // If instance is created and it is OpenVidu Pro
- (this.instance &&
- openVidu.isAtLeastPro &&
- // If logs are enabled
- this.instance.isOpenViduBrowserLogsDebugActive(openVidu) &&
- // Only reconfigure it if session or finalUserId has changed
- this.instance.canConfigureJSNLog(openVidu, this.instance))
- ) {
- // Check if app logs can be sent
- // and replace console.log function to send
- // logs of the application
- if (openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app) {
- this.instance.replaceWindowConsole();
- }
-
- // isJSNLogSetup will not be true until completed setup
- this.instance.isJSNLogSetup = false;
- this.instance.info('Configuring JSNLogs.');
-
- const finalUserId = openVidu.finalUserId;
- const sessionId = openVidu.session.sessionId;
-
- const beforeSendCallback = (xhr) => {
- // If 401 or 403 or 404 modify ready and status so JSNLog don't retry to send logs
- // https://github.com/mperdeck/jsnlog.js/blob/v2.30.0/jsnlog.ts#L805-L818
- const parentReadyStateFunction = xhr.onreadystatechange;
- xhr.onreadystatechange = () => {
- if (this.isInvalidResponse(xhr)) {
- Object.defineProperty(xhr, 'readyState', { value: 4 });
- Object.defineProperty(xhr, 'status', { value: 200 });
- // Disable JSNLog too to not send periodically errors
- this.instance.disableLogger();
- }
- parentReadyStateFunction();
- };
-
- // Headers to identify and authenticate logs
- xhr.setRequestHeader('Authorization', 'Basic ' + btoa(`${finalUserId}%/%${sessionId}` + ':' + token));
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- // Additional headers for OpenVidu
- xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
- xhr.setRequestHeader('OV-Session-Id', sessionId);
- xhr.setRequestHeader('OV-Token', token);
- };
-
- // Creation of the appender.
- this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
- this.instance.currentAppender.setOptions({
- beforeSend: beforeSendCallback,
- maxBatchSize: 1000,
- batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
- batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES
- });
-
- // Avoid circular dependencies
- const logSerializer = (obj): string => {
- const getCircularReplacer = () => {
- const seen = new WeakSet();
- return (key, value) => {
- if (typeof value === 'object' && value != null) {
- if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) {
- return;
- }
- seen.add(value);
- }
- return value;
- };
- };
-
- // Cut long messages
- let stringifyJson = JSON.stringify(obj, getCircularReplacer());
- if (stringifyJson.length > this.instance.MAX_LENGTH_STRING_JSON) {
- stringifyJson = `${stringifyJson.substring(0, this.instance.MAX_LENGTH_STRING_JSON)}...`;
- }
-
- if (globalThis['LOG_JSNLOG_RESULTS']) {
- console.log(stringifyJson);
- }
-
- return stringifyJson;
- };
-
- // Initialize JL to send logs
- JL.setOptions({
- defaultAjaxUrl: openVidu.httpUri + this.instance.JSNLOG_URL,
- serialize: logSerializer,
- enabled: true
- });
- JL().setOptions({
- appenders: [this.instance.currentAppender]
- });
-
- this.instance.isJSNLogSetup = true;
- this.instance.loggingSessionId = sessionId;
- this.instance.info('JSNLog configured.');
- }
- } catch (e) {
- // Print error
- console.error('Error configuring JSNLog: ');
- console.error(e);
- // Restore defaults values just in case any exception happen-
- this.instance.disableLogger();
- }
+ // try {
+ // // If dev mode or...
+ // if (
+ // globalThis['LOG_JSNLOG_RESULTS'] ||
+ // // If instance is created and it is OpenVidu Pro
+ // (this.instance &&
+ // openVidu.isAtLeastPro &&
+ // // If logs are enabled
+ // this.instance.isOpenViduBrowserLogsDebugActive(openVidu) &&
+ // // Only reconfigure it if session or finalUserId has changed
+ // this.instance.canConfigureJSNLog(openVidu, this.instance))
+ // ) {
+ // // Check if app logs can be sent
+ // // and replace console.log function to send
+ // // logs of the application
+ // // if (openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app) {
+ // // this.instance.replaceWindowConsole();
+ // // }
+
+ // // isJSNLogSetup will not be true until completed setup
+ // this.instance.isJSNLogSetup = false;
+ // this.instance.info('Configuring JSNLogs.');
+
+ // const finalUserId = openVidu.finalUserId;
+ // const sessionId = openVidu.session.sessionId;
+
+ // const beforeSendCallback = (xhr) => {
+ // // If 401 or 403 or 404 modify ready and status so JSNLog don't retry to send logs
+ // // https://github.com/mperdeck/jsnlog.js/blob/v2.30.0/jsnlog.ts#L805-L818
+ // const parentReadyStateFunction = xhr.onreadystatechange;
+ // xhr.onreadystatechange = () => {
+ // if (this.isInvalidResponse(xhr)) {
+ // Object.defineProperty(xhr, 'readyState', { value: 4 });
+ // Object.defineProperty(xhr, 'status', { value: 200 });
+ // // Disable JSNLog too to not send periodically errors
+ // this.instance.disableLogger();
+ // }
+ // parentReadyStateFunction();
+ // };
+
+ // // Headers to identify and authenticate logs
+ // xhr.setRequestHeader('Authorization', 'Basic ' + btoa(`${finalUserId}%/%${sessionId}` + ':' + token));
+ // xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ // // Additional headers for OpenVidu
+ // xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
+ // xhr.setRequestHeader('OV-Session-Id', sessionId);
+ // xhr.setRequestHeader('OV-Token', token);
+ // };
+
+ // // Creation of the appender.
+ // this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
+ // this.instance.currentAppender.setOptions({
+ // beforeSend: beforeSendCallback,
+ // maxBatchSize: 1000,
+ // batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
+ // batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES
+ // });
+
+ // // Avoid circular dependencies
+ // const logSerializer = (obj): string => {
+ // const getCircularReplacer = () => {
+ // const seen = new WeakSet();
+ // return (key, value) => {
+ // if (typeof value === 'object' && value != null) {
+ // if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) {
+ // return;
+ // }
+ // seen.add(value);
+ // }
+ // return value;
+ // };
+ // };
+
+ // // Cut long messages
+ // let stringifyJson = JSON.stringify(obj, getCircularReplacer());
+ // if (stringifyJson.length > this.instance.MAX_LENGTH_STRING_JSON) {
+ // stringifyJson = `${stringifyJson.substring(0, this.instance.MAX_LENGTH_STRING_JSON)}...`;
+ // }
+
+ // if (globalThis['LOG_JSNLOG_RESULTS']) {
+ // console.log(stringifyJson);
+ // }
+
+ // return stringifyJson;
+ // };
+
+ // // Initialize JL to send logs
+ // // JL.setOptions({
+ // // defaultAjaxUrl: openVidu.httpUri + this.instance.JSNLOG_URL,
+ // // serialize: logSerializer,
+ // // enabled: true
+ // // });
+ // // JL().setOptions({
+ // // appenders: [this.instance.currentAppender]
+ // // });
+
+ // this.instance.isJSNLogSetup = true;
+ // this.instance.loggingSessionId = sessionId;
+ // this.instance.info('JSNLog configured.');
+ // }
+ // } catch (e) {
+ // // Print error
+ // console.error('Error configuring JSNLog: ');
+ // console.error(e);
+ // // Restore defaults values just in case any exception happen-
+ // this.instance.disableLogger();
+ // }
}
/**
@@ -154,10 +154,11 @@ export class OpenViduLogger {
}
private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) {
- return (
- openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
- openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app
- );
+ return false;
+ // return (
+ // openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
+ // openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app
+ // );
}
// Return console functions with jsnlog integration
diff --git a/openvidu-browser/src/OpenViduInternal/WebRtcStats/WebRtcStats.ts b/openvidu-browser/src/OpenViduInternal/WebRtcStats/WebRtcStats.ts
index 7fe5177fa13..50df5ae9dd8 100644
--- a/openvidu-browser/src/OpenViduInternal/WebRtcStats/WebRtcStats.ts
+++ b/openvidu-browser/src/OpenViduInternal/WebRtcStats/WebRtcStats.ts
@@ -226,87 +226,87 @@ export class WebRtcStats {
public getSelectedIceCandidateInfo(): Promise {
throw new Error('getSelectedIceCandidateInfo is not ready yet');
- return new Promise(async (resolve, reject) => {
- const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
- let transportStat;
- const candidatePairs: Map = new Map();
- const localCandidates: Map = new Map();
- const remoteCandidates: Map = new Map();
- statsReport.forEach((stat: any) => {
- if (stat.type === 'transport' && (platform.isChromium() || platform.isSafariBrowser() || platform.isReactNative())) {
- transportStat = stat;
- }
- switch (stat.type) {
- case 'candidate-pair':
- candidatePairs.set(stat.id, stat);
- break;
- case 'local-candidate':
- localCandidates.set(stat.id, stat);
- break;
- case 'remote-candidate':
- remoteCandidates.set(stat.id, stat);
- break;
- }
- });
- let selectedCandidatePair;
- if (transportStat != null) {
- const selectedCandidatePairId = transportStat.selectedCandidatePairId;
- selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
- } else {
- // This is basically Firefox
- const length = candidatePairs.size;
- const iterator = candidatePairs.values();
- for (let i = 0; i < length; i++) {
- const candidatePair = iterator.next().value;
- if (candidatePair['selected']) {
- selectedCandidatePair = candidatePair;
- break;
- }
- }
- }
- const localCandidateId = selectedCandidatePair.localCandidateId;
- const remoteCandidateId = selectedCandidatePair.remoteCandidateId;
- let finalLocalCandidate = localCandidates.get(localCandidateId);
- if (!!finalLocalCandidate) {
- const candList = this.stream.getLocalIceCandidateList();
- const cand = candList.filter((c: RTCIceCandidate) => {
- return (
- !!c.candidate &&
- (c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
- c.candidate.indexOf(finalLocalCandidate.port) >= 0
- );
- });
- finalLocalCandidate.raw = [];
- for (let c of cand) {
- finalLocalCandidate.raw.push(c.candidate);
- }
- } else {
- finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
- }
-
- let finalRemoteCandidate = remoteCandidates.get(remoteCandidateId);
- if (!!finalRemoteCandidate) {
- const candList = this.stream.getRemoteIceCandidateList();
- const cand = candList.filter((c: RTCIceCandidate) => {
- return (
- !!c.candidate &&
- (c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
- c.candidate.indexOf(finalRemoteCandidate.port) >= 0
- );
- });
- finalRemoteCandidate.raw = [];
- for (let c of cand) {
- finalRemoteCandidate.raw.push(c.candidate);
- }
- } else {
- finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
- }
-
- return resolve({
- localCandidate: finalLocalCandidate,
- remoteCandidate: finalRemoteCandidate
- });
- });
+ // return new Promise(async (resolve, reject) => {
+ // const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
+ // let transportStat;
+ // const candidatePairs: Map = new Map();
+ // const localCandidates: Map = new Map();
+ // const remoteCandidates: Map = new Map();
+ // statsReport.forEach((stat: any) => {
+ // if (stat.type === 'transport' && (platform.isChromium() || platform.isSafariBrowser() || platform.isReactNative())) {
+ // transportStat = stat;
+ // }
+ // switch (stat.type) {
+ // case 'candidate-pair':
+ // candidatePairs.set(stat.id, stat);
+ // break;
+ // case 'local-candidate':
+ // localCandidates.set(stat.id, stat);
+ // break;
+ // case 'remote-candidate':
+ // remoteCandidates.set(stat.id, stat);
+ // break;
+ // }
+ // });
+ // let selectedCandidatePair;
+ // if (transportStat != null) {
+ // const selectedCandidatePairId = transportStat.selectedCandidatePairId;
+ // selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
+ // } else {
+ // // This is basically Firefox
+ // const length = candidatePairs.size;
+ // const iterator = candidatePairs.values();
+ // for (let i = 0; i < length; i++) {
+ // const candidatePair = iterator.next().value;
+ // if (candidatePair['selected']) {
+ // selectedCandidatePair = candidatePair;
+ // break;
+ // }
+ // }
+ // }
+ // const localCandidateId = selectedCandidatePair.localCandidateId;
+ // const remoteCandidateId = selectedCandidatePair.remoteCandidateId;
+ // let finalLocalCandidate = localCandidates.get(localCandidateId);
+ // if (!!finalLocalCandidate) {
+ // const candList = this.stream.getLocalIceCandidateList();
+ // const cand = candList.filter((c: RTCIceCandidate) => {
+ // return (
+ // !!c.candidate &&
+ // (c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
+ // c.candidate.indexOf(finalLocalCandidate.port) >= 0
+ // );
+ // });
+ // finalLocalCandidate.raw = [];
+ // for (let c of cand) {
+ // finalLocalCandidate.raw.push(c.candidate);
+ // }
+ // } else {
+ // finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
+ // }
+
+ // let finalRemoteCandidate = remoteCandidates.get(remoteCandidateId);
+ // if (!!finalRemoteCandidate) {
+ // const candList = this.stream.getRemoteIceCandidateList();
+ // const cand = candList.filter((c: RTCIceCandidate) => {
+ // return (
+ // !!c.candidate &&
+ // (c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
+ // c.candidate.indexOf(finalRemoteCandidate.port) >= 0
+ // );
+ // });
+ // finalRemoteCandidate.raw = [];
+ // for (let c of cand) {
+ // finalRemoteCandidate.raw.push(c.candidate);
+ // }
+ // } else {
+ // finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
+ // }
+
+ // return resolve({
+ // localCandidate: finalLocalCandidate,
+ // remoteCandidate: finalRemoteCandidate
+ // });
+ // });
}
public stopWebRtcStats() {
@@ -383,75 +383,75 @@ export class WebRtcStats {
public async getCommonStats(): Promise {
throw new Error('getCommonStats is not ready yet');
- return new Promise(async (resolve, reject) => {
- try {
- const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
- const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
- const videoTrackStats = ['framesReceived', 'framesDropped', 'framesSent', 'frameHeight', 'frameWidth'];
- const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
-
- statsReport.forEach((stat: any) => {
- let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
- const addStat = (direction: string, key: string): void => {
- if (stat[key] != null && response[direction] != null) {
- if (!mediaType && videoTrackStats.indexOf(key) > -1) {
- mediaType = 'video';
- }
- if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
- response[direction][mediaType][key] = Number(stat[key]);
- } else if (direction != null && key != null && candidatePairStats.includes(key)) {
- // candidate-pair-stats
- response[direction][key] = Number(stat[key]);
- }
- }
- };
-
- switch (stat.type) {
- case 'outbound-rtp':
- addStat('outbound', 'bytesSent');
- addStat('outbound', 'packetsSent');
- addStat('outbound', 'framesEncoded');
- addStat('outbound', 'nackCount');
- addStat('outbound', 'firCount');
- addStat('outbound', 'pliCount');
- addStat('outbound', 'qpSum');
- break;
- case 'inbound-rtp':
- addStat('inbound', 'bytesReceived');
- addStat('inbound', 'packetsReceived');
- addStat('inbound', 'packetsLost');
- addStat('inbound', 'jitter');
- addStat('inbound', 'framesDecoded');
- addStat('inbound', 'nackCount');
- addStat('inbound', 'firCount');
- addStat('inbound', 'pliCount');
- break;
- case 'track':
- addStat('inbound', 'jitterBufferDelay');
- addStat('inbound', 'framesReceived');
- addStat('outbound', 'framesDropped');
- addStat('outbound', 'framesSent');
- addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameHeight');
- addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameWidth');
- break;
- case 'candidate-pair':
- addStat('candidatepair', 'currentRoundTripTime');
- addStat('candidatepair', 'availableOutgoingBitrate');
- break;
- }
- });
-
- // Delete candidatepair from response if null
- if (!response?.candidatepair || Object.keys(