csantosm f23f39dd6f openvidu-components: Improved user-settings component
- Changed replaceTrack to republishTrack until replaceTrack issue is fixed (https://github.com/OpenVidu/openvidu/pull/700)
- Fixed bug in mat options 
- Added audio detector
2022-02-25 14:36:36 +01:00

397 lines
14 KiB
TypeScript

import { Injectable } from '@angular/core';
import {
Connection,
OpenVidu,
OpenViduError,
OpenViduErrorName,
Publisher,
PublisherProperties,
Session,
SignalOptions
} from 'openvidu-browser';
import { LoggerService } from '../logger/logger.service';
import { ILogger } from '../../models/logger.model';
import { Signal } from '../../models/signal.model';
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
import { PlatformService } from '../platform/platform.service';
import { DeviceService } from '../device/device.service';
import { CameraType } from '../../models/device.model';
import { VideoType } from '../../models/video-type.model';
import { ParticipantService } from '../participant/participant.service';
@Injectable({
providedIn: 'root'
})
export class OpenViduService {
protected OV: OpenVidu = null;
protected OVScreen: OpenVidu = null;
protected webcamSession: Session = null;
protected screenSession: Session = null;
protected videoSource = undefined;
protected audioSource = undefined;
protected log: ILogger;
constructor(
protected openviduAngularConfigSrv: OpenViduAngularConfigService,
protected platformService: PlatformService,
protected loggerSrv: LoggerService,
private participantService: ParticipantService,
protected deviceService: DeviceService
) {
this.log = this.loggerSrv.get('OpenViduService');
}
initialize() {
this.OV = new OpenVidu();
if (this.openviduAngularConfigSrv.isProduction()) this.OV.enableProdMode();
this.webcamSession = this.OV.initSession();
// Initialize screen session only if it is not mobile platform
if (!this.platformService.isMobile()) {
this.OVScreen = new OpenVidu();
if (this.openviduAngularConfigSrv.isProduction()) this.OVScreen.enableProdMode();
this.screenSession = this.OVScreen.initSession();
}
}
getSession(): Session {
return this.getWebcamSession();
}
getWebcamSession(): Session {
return this.webcamSession;
}
isWebcamSessionConnected(): boolean {
return !!this.webcamSession.capabilities;
}
getScreenSession(): Session {
return this.screenSession;
}
isScreenSessionConnected(): boolean {
return !!this.screenSession.capabilities;
}
async connectSession(session: Session, token: string): Promise<void> {
if (!!token && session) {
const nickname = this.participantService.getWebcamNickname();
const participantId = this.participantService.getMyParticipantId();
if (session === this.webcamSession) {
this.log.d('Connecting webcam session');
await this.webcamSession.connect(token, {
clientData: nickname,
participantId,
type: VideoType.CAMERA
});
this.participantService.setMyCameraConnectionId(this.webcamSession.connection.connectionId);
} else if (session === this.screenSession) {
this.log.d('Connecting screen session');
await this.screenSession.connect(token, {
clientData: `${nickname}_${VideoType.SCREEN}`,
participantId,
type: VideoType.SCREEN
});
this.participantService.setMyScreenConnectionId(this.screenSession.connection.connectionId);
}
}
}
disconnect() {
this.disconnectSession(this.webcamSession);
this.disconnectSession(this.screenSession);
this.videoSource = undefined;
this.audioSource = undefined;
// this.stopTracks(this.participantService.getMyCameraPublisher()?.stream?.getMediaStream());
// this.stopTracks(this.participantService.getMyScreenPublisher()?.stream?.getMediaStream());
}
/**
* Initialize a publisher checking devices saved on storage or if participant have devices available.
*/
async initDefaultPublisher(targetElement: string | HTMLElement): Promise<Publisher> {
const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable();
const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable();
const isVideoActive = !this.deviceService.isVideoMuted();
const isAudioActive = !this.deviceService.isAudioMuted();
let videoSource = null;
let audioSource = null;
if (hasVideoDevices) {
// Video is active, assign the device selected
videoSource = this.deviceService.getCameraSelected().device;
} else if (!isVideoActive && hasVideoDevices) {
// Video is muted, assign the default device
// videoSource = undefined;
}
if (hasAudioDevices) {
// Audio is active, assign the device selected
audioSource = this.deviceService.getMicrophoneSelected().device;
} else if (!isAudioActive && hasAudioDevices) {
// Audio is muted, assign the default device
// audioSource = undefined;
}
// const videoSource = publishVideo ? this.deviceService.getCameraSelected().device : false;
// const audioSource = publishAudio ? this.deviceService.getMicrophoneSelected().device : false;
const mirror = this.deviceService.getCameraSelected() && this.deviceService.getCameraSelected().type === CameraType.FRONT;
const properties: PublisherProperties = {
videoSource,
audioSource,
publishVideo: isVideoActive,
publishAudio: isAudioActive,
mirror
};
if (hasVideoDevices || hasAudioDevices) {
const publisher = await this.initPublisher(targetElement, properties);
this.participantService.setMyCameraPublisher(publisher);
this.participantService.updateLocalParticipant();
return publisher;
} else {
this.participantService.setMyCameraPublisher(null);
}
}
async initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties): Promise<Publisher> {
this.log.d('Initializing publisher with properties: ', properties);
return await this.OV.initPublisherAsync(targetElement, properties);
}
async publish(publisher: Publisher): Promise<void> {
if (!!publisher) {
if (publisher === this.participantService.getMyCameraPublisher()) {
if (this.webcamSession?.capabilities?.publish) {
return await this.webcamSession.publish(publisher);
}
this.log.e('Webcam publisher cannot be published');
} else if (publisher === this.participantService.getMyScreenPublisher()) {
if (this.screenSession?.capabilities?.publish) {
return await this.screenSession.publish(publisher);
}
this.log.e('Screen publisher cannot be published');
}
}
}
unpublish(publisher: Publisher): void {
if (!!publisher) {
if (publisher === this.participantService.getMyCameraPublisher()) {
this.publishAudio(this.participantService.getMyScreenPublisher(), this.participantService.hasCameraAudioActive());
this.webcamSession.unpublish(publisher);
} else if (publisher === this.participantService.getMyScreenPublisher()) {
this.screenSession.unpublish(publisher);
}
}
}
publishVideo(publisher: Publisher, value: boolean): void {
if (!!publisher) {
publisher.publishVideo(value);
this.participantService.updateLocalParticipant();
}
}
publishAudio(publisher: Publisher, value: boolean): void {
if (!!publisher) {
publisher.publishAudio(value);
this.participantService.updateLocalParticipant();
}
}
// TODO: Remove this method when replaceTrack issue is fixed
// https://github.com/OpenVidu/openvidu/pull/700
republishTrack(properties: PublisherProperties): Promise<void> {
const {videoSource, audioSource, mirror} = properties;
return new Promise(async (resolve, reject) => {
if (!!videoSource) {
this.log.d('Replacing video track ' + videoSource);
this.videoSource = videoSource;
}
if (!!audioSource) {
this.log.d('Replacing audio track ' + audioSource);
this.audioSource = audioSource;
}
this.destroyPublisher(this.participantService.getMyCameraPublisher());
const properties: PublisherProperties = {
videoSource: this.videoSource,
audioSource: this.audioSource,
publishVideo: this.participantService.hasCameraVideoActive(),
publishAudio: this.participantService.hasCameraAudioActive(),
mirror
};
const publisher = await this.initPublisher(undefined, properties);
this.participantService.setMyCameraPublisher(publisher);
publisher.once('streamPlaying', () => {
this.participantService.setMyCameraPublisher(publisher);
resolve();
});
publisher.once('accessDenied', () => {
reject();
});
});
}
sendSignal(type: Signal, connections?: Connection[], data?: any): void {
const signalOptions: SignalOptions = {
data: JSON.stringify(data),
type: type,
to: connections && connections.length > 0 ? connections : undefined
};
this.webcamSession.signal(signalOptions);
if (type === Signal.NICKNAME_CHANGED && !!this.getScreenSession().connection) {
signalOptions.data = JSON.stringify({ clientData: this.participantService.getScreenNickname() });
this.getScreenSession()?.signal(signalOptions);
}
}
async replaceTrack(videoType: VideoType, props: PublisherProperties) {
try {
this.log.d(`Replacing ${videoType} track`, props);
if (videoType === VideoType.CAMERA) {
//TODO: Uncomment this section when replaceTrack issue is fixed
// https://github.com/OpenVidu/openvidu/pull/700
throw('Replace track feature has a bug. We are trying to fix it');
// let mediaStream: MediaStream;
// const oldMediaStream = this.participantService.getMyCameraPublisher().stream.getMediaStream();
// const isFirefoxPlatform = this.platformService.isFirefox();
// const isReplacingAudio = !!props.audioSource;
// const isReplacingVideo = !!props.videoSource;
// if (isReplacingVideo) {
// if (isFirefoxPlatform) {
// // Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
// // NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
// oldMediaStream.getVideoTracks()[0].stop();
// }
// mediaStream = await this.createMediaStream(props);
// // Replace video track
// const videoTrack: MediaStreamTrack = mediaStream.getVideoTracks()[0];
// await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack);
// } else if (isReplacingAudio) {
// if (isFirefoxPlatform) {
// // Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
// // NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
// oldMediaStream.getAudioTracks()[0].stop();
// }
// mediaStream = await this.createMediaStream(props);
// // Replace audio track
// const audioTrack: MediaStreamTrack = mediaStream.getAudioTracks()[0];
// await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack);
// }
} else if (videoType === VideoType.SCREEN) {
let newScreenMediaStream;
try {
newScreenMediaStream = await this.OVScreen.getUserMedia(props);
this.participantService.getMyScreenPublisher().stream.getMediaStream().getVideoTracks()[0].stop();
await this.participantService.getMyScreenPublisher().replaceTrack(newScreenMediaStream.getVideoTracks()[0]);
} catch (error) {
this.log.w('Cannot create the new MediaStream', error);
}
}
} catch (error) {
this.log.e('Error replacing track ', error);
}
}
private destroyPublisher(publisher: Publisher): void {
if (!!publisher) {
if (publisher.stream.getWebRtcPeer()) {
publisher.stream.disposeWebRtcPeer();
}
publisher.stream.disposeMediaStream();
if (publisher.id === this.participantService.getMyCameraPublisher().id) {
this.participantService.setMyCameraPublisher(publisher);
} else if (publisher.id === this.participantService.getMyScreenPublisher().id) {
this.participantService.setMyScreenPublisher(publisher);
}
}
}
private async createMediaStream(pp: PublisherProperties): Promise<MediaStream> {
let mediaStream: MediaStream;
const isFirefoxPlatform = this.platformService.isFirefox();
const isReplacingAudio = !!pp.audioSource;
const isReplacingVideo = !!pp.videoSource;
try {
mediaStream = await this.OV.getUserMedia(pp);
} catch (error) {
if ((<OpenViduError>error).name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
if (isFirefoxPlatform) {
this.log.w('The device requested is not available. Restoring the older one');
// The track requested is not available so we are getting the old tracks ids for recovering the track
if (isReplacingVideo) {
pp.videoSource = this.deviceService.getCameraSelected().device;
} else if (isReplacingAudio) {
pp.audioSource = this.deviceService.getMicrophoneSelected().device;
}
mediaStream = await this.OV.getUserMedia(pp);
// TODO show error alert informing that the new device is not available
}
}
} finally {
return mediaStream;
}
}
needSendNicknameSignal(): boolean {
let oldNickname: string;
try {
const connData = JSON.parse(this.webcamSession.connection.data.split('%/%')[0]);
oldNickname = connData.clientData;
} catch (error) {
this.log.e(error);
}
return oldNickname !== this.participantService.getWebcamNickname();
}
isMyOwnConnection(connectionId: string): boolean {
return (
this.webcamSession?.connection?.connectionId === connectionId || this.screenSession?.connection?.connectionId === connectionId
);
}
getRemoteCameraConnections(): Connection[] {
// Avoid screen connections
const remoteCameraConnections: Connection[] = Array.from(this.webcamSession.remoteConnections.values()).filter((conn) => {
let type: VideoType;
type = JSON.parse(conn.data).type;
return type !== VideoType.SCREEN;
});
return remoteCameraConnections;
}
private disconnectSession(session: Session) {
if (session) {
if (session.sessionId === this.webcamSession?.sessionId) {
this.log.d('Disconnecting webcam session');
this.webcamSession?.disconnect();
this.webcamSession = null;
} else if (session.sessionId === this.screenSession?.sessionId) {
this.log.d('Disconnecting screen session');
this.screenSession?.disconnect();
this.screenSession = null;
}
}
}
// private stopTracks(mediaStream: MediaStream) {
// if (mediaStream) {
// mediaStream?.getAudioTracks().forEach((track) => track.stop());
// mediaStream?.getVideoTracks().forEach((track) => track.stop());
// // this.webcamMediaStream?.getAudioTracks().forEach((track) => track.stop());
// }
// }
}