diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/audio-wave/audio-wave.component.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/audio-wave/audio-wave.component.ts index fe8be885..90216fa6 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/audio-wave/audio-wave.component.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/audio-wave/audio-wave.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { PublisherSpeakingEvent, StreamManager } from 'openvidu-browser'; @@ -7,19 +7,22 @@ import { PublisherSpeakingEvent, StreamManager } from 'openvidu-browser'; templateUrl: './audio-wave.component.html', styleUrls: ['./audio-wave.component.css'] }) -export class AudioWaveComponent implements OnInit { +export class AudioWaveComponent implements OnInit, OnDestroy { isSpeaking: boolean = false; audioVolume: number = 0; + private _streamManager: StreamManager; + @Input() set streamManager(streamManager: StreamManager) { + this._streamManager = streamManager; - if(streamManager) { - streamManager.on('publisherStartSpeaking', (event: PublisherSpeakingEvent) => { + if(this._streamManager) { + this._streamManager.on('publisherStartSpeaking', (event: PublisherSpeakingEvent) => { this.isSpeaking = true; }); - streamManager.on('publisherStopSpeaking', (event: PublisherSpeakingEvent) => { + this._streamManager.on('publisherStopSpeaking', (event: PublisherSpeakingEvent) => { this.isSpeaking = false; }); @@ -35,6 +38,12 @@ export class AudioWaveComponent implements OnInit { } constructor() {} + ngOnDestroy(): void { + if(this._streamManager){ + this._streamManager.off('publisherStartSpeaking'); + this._streamManager.off('publisherStopSpeaking'); + } + } ngOnInit(): void {} } diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/stream/stream.component.html b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/stream/stream.component.html index f64a7593..eec326e4 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/stream/stream.component.html +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/stream/stream.component.html @@ -4,7 +4,7 @@ [id]="'container-' + this._stream.streamManager?.stream?.streamId" #streamContainer > -
+
{{ this._stream.nickname }} (edit) @@ -31,7 +31,7 @@
-
+
@@ -49,7 +49,7 @@
-
+
- - -
@@ -78,17 +78,17 @@ (click)="toggleMic()" class="deviceButton" id="configCardMicrophoneButton" - [class.warn-btn]="!isAudioActive" + [class.warn-btn]="isAudioMuted" > - mic - mic_off + mic + mic_off
diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/user-settings/user-settings.component.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/user-settings/user-settings.component.ts index 6116a67b..59d1dc64 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/components/user-settings/user-settings.component.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/components/user-settings/user-settings.component.ts @@ -7,8 +7,7 @@ import { Publisher, PublisherProperties } from 'openvidu-browser'; import { ILogger } from '../../models/logger.model'; import { CustomDevice } from '../../models/device.model'; -import { Storage } from '../../models/storage.model'; -import { ScreenType } from '../../models/video-type.model'; +import { ScreenType, VideoType } from '../../models/video-type.model'; import { NicknameMatcher } from '../../matchers/nickname.matcher'; @@ -36,8 +35,8 @@ export class UserSettingsComponent implements OnInit, OnDestroy { microphones: CustomDevice[]; cameraSelected: CustomDevice; microphoneSelected: CustomDevice; - isVideoActive = true; - isAudioActive = true; + isVideoMuted: boolean; + isAudioMuted: boolean; screenShareEnabled: boolean; localParticipant: ParticipantAbstractModel; columns: number; @@ -55,7 +54,7 @@ export class UserSettingsComponent implements OnInit, OnDestroy { private actionService: ActionService, private deviceSrv: DeviceService, private loggerSrv: LoggerService, - private openViduopenviduService: OpenViduService, + private openviduService: OpenViduService, private participantService: ParticipantService, private storageSrv: StorageService ) { @@ -68,10 +67,11 @@ export class UserSettingsComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.subscribeToLocalParticipantEvents(); - this.openViduopenviduService.initialize(); await this.deviceSrv.initializeDevices(); - const nickname = this.storageSrv.get(Storage.USER_NICKNAME) || this.generateRandomNickname(); + + this.subscribeToLocalParticipantEvents(); + this.openviduService.initialize(); + const nickname = this.storageSrv.getNickname() || this.generateRandomNickname(); this.nicknameFormControl.setValue(nickname); this.columns = window.innerWidth > 900 ? 2 : 1; this.setDevicesInfo(); @@ -79,7 +79,6 @@ export class UserSettingsComponent implements OnInit, OnDestroy { await this.initwebcamPublisher(); } this.isLoading = false; - } ngOnDestroy() { @@ -95,63 +94,57 @@ export class UserSettingsComponent implements OnInit, OnDestroy { async onCameraSelected(event: any) { const videoSource = event?.value; - if (!!videoSource) { - // Is New deviceId different from the old one? - if (this.deviceSrv.needUpdateVideoTrack(videoSource)) { - const mirror = this.deviceSrv.cameraNeedsMirror(videoSource); - await this.openViduopenviduService.republishTrack(videoSource, null, mirror); - this.deviceSrv.setCameraSelected(videoSource); - this.cameraSelected = this.deviceSrv.getCameraSelected(); - } - // Publish Webcam video - this.openViduopenviduService.publishVideo(this.participantService.getMyCameraPublisher(), true); - this.isVideoActive = true; + // Is New deviceId different from the old one? + if (this.deviceSrv.needUpdateVideoTrack(videoSource)) { + const mirror = this.deviceSrv.cameraNeedsMirror(videoSource); + const pp: PublisherProperties = { videoSource, audioSource: false, mirror }; - } else { - // Videosource is 'null' because of the user has selected 'None' or muted the camera - // Unpublish webcam - this.openViduopenviduService.publishVideo(this.participantService.getMyCameraPublisher(), false); - //TODO: save 'None' device in storage - // this.deviceSrv.setCameraSelected(videoSource); - // this.cameraSelected = this.deviceSrv.getCameraSelected(); - this.isVideoActive = false; + await this.openviduService.replaceTrack(VideoType.CAMERA, pp); + this.cameraSelected = videoSource; + this.deviceSrv.setCameraSelected(this.cameraSelected); + } + if (this.isVideoMuted) { + // Publish Webcam video + this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), true); + this.isVideoMuted = false; } } async onMicrophoneSelected(event: any) { const audioSource = event?.value; - - if (!!audioSource) { - // Is New deviceId different than older? - if (this.deviceSrv.needUpdateAudioTrack(audioSource)) { - const mirror = this.deviceSrv.cameraNeedsMirror(this.cameraSelected.device); - await this.openViduopenviduService.republishTrack(null, audioSource, mirror); - this.deviceSrv.setMicSelected(audioSource); - this.microphoneSelected = this.deviceSrv.getMicrophoneSelected(); - } - // Publish microphone - this.publishAudio(true); - this.isAudioActive = true; - return; + // Is New deviceId different than older? + if (this.deviceSrv.needUpdateAudioTrack(audioSource)) { + const pp: PublisherProperties = { audioSource, videoSource: false }; + await this.openviduService.replaceTrack(VideoType.CAMERA, pp); + this.microphoneSelected = audioSource; + this.deviceSrv.setMicSelected(this.microphoneSelected); + } + if (this.isAudioMuted) { + // Enable microphone + this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), true); + this.isAudioMuted = true; } - // Unpublish microhpone - this.publishAudio(false); - this.isAudioActive = false; } toggleCam() { - this.isVideoActive = !this.isVideoActive; - this.openViduopenviduService.publishVideo(this.participantService.getMyCameraPublisher(), this.isVideoActive); + + const publish = this.isVideoMuted; + this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), publish); if (this.participantService.areBothEnabled()) { + // Cam will not published, disable webcam with screensharing active this.participantService.disableWebcamUser(); - this.openViduopenviduService.publishAudio(this.participantService.getMyScreenPublisher(), this.isAudioActive); + this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publish); } else if (this.participantService.isOnlyMyScreenEnabled()) { + // Cam will be published, enable webcam this.participantService.enableWebcamUser(); } + + this.isVideoMuted = !this.isVideoMuted; + this.storageSrv.setVideoMuted(this.isVideoMuted); } - toggleScreenShare() { + async toggleScreenShare() { // Disabling screenShare if (this.participantService.areBothEnabled()) { this.participantService.disableScreenUser(); @@ -161,7 +154,7 @@ export class UserSettingsComponent implements OnInit, OnDestroy { // Enabling screenShare if (this.participantService.isOnlyMyCameraEnabled()) { const willThereBeWebcam = this.participantService.isMyCameraEnabled() && this.participantService.hasCameraVideoActive(); - const hasAudio = willThereBeWebcam ? false : this.hasAudioDevices && this.isAudioActive; + const hasAudio = willThereBeWebcam ? false : this.hasAudioDevices && this.isAudioMuted; const properties: PublisherProperties = { videoSource: ScreenType.SCREEN, audioSource: this.hasAudioDevices ? undefined : null, @@ -169,7 +162,7 @@ export class UserSettingsComponent implements OnInit, OnDestroy { publishAudio: hasAudio, mirror: false }; - const screenPublisher = this.openViduopenviduService.initPublisher(undefined, properties); + const screenPublisher = await this.openviduService.initPublisher(undefined, properties); screenPublisher.on('accessAllowed', (event) => { screenPublisher.stream @@ -199,8 +192,10 @@ export class UserSettingsComponent implements OnInit, OnDestroy { } toggleMic() { - this.isAudioActive = !this.isAudioActive; - this.publishAudio(this.isAudioActive); + const publish = this.isAudioMuted; + this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), publish); + this.isAudioMuted = !this.isAudioMuted; + this.storageSrv.setAudioMuted(this.isAudioMuted); } eventKeyPress(event) { @@ -217,7 +212,8 @@ export class UserSettingsComponent implements OnInit, OnDestroy { if (this.nicknameFormControl.valid) { const nickname = this.nicknameFormControl.value; this.participantService.setNickname(this.participantService.getMyCameraConnectionId(), nickname); - this.storageSrv.set(Storage.USER_NICKNAME, nickname); + this.storageSrv.setNickname(nickname); + this.participantService.updateParticipantMediaStatus(); return this.onJoinClicked.emit(); } this.scrollToBottom(); @@ -235,9 +231,8 @@ export class UserSettingsComponent implements OnInit, OnDestroy { this.cameraSelected = this.deviceSrv.getCameraSelected(); this.microphoneSelected = this.deviceSrv.getMicrophoneSelected(); - this.isVideoActive = this.hasVideoDevices && this.cameraSelected.label !== 'None'; - this.isAudioActive = this.hasAudioDevices && this.microphoneSelected.label !== 'None'; - + this.isVideoMuted = this.deviceSrv.isVideoMuted(); + this.isAudioMuted = this.deviceSrv.isAudioMuted(); } private scrollToBottom(): void { @@ -246,12 +241,6 @@ export class UserSettingsComponent implements OnInit, OnDestroy { } catch (err) {} } - private publishAudio(audio: boolean) { - this.participantService.isMyCameraEnabled() - ? this.openViduopenviduService.publishAudio(this.participantService.getMyCameraPublisher(), audio) - : this.openViduopenviduService.publishAudio(this.participantService.getMyScreenPublisher(), audio); - } - private subscribeToLocalParticipantEvents() { this.oVUsersSubscription = this.participantService.localParticipantObs.subscribe((p) => { this.localParticipant = p; @@ -262,9 +251,8 @@ export class UserSettingsComponent implements OnInit, OnDestroy { } private async initwebcamPublisher() { - const publisher = await this.openViduopenviduService.initDefaultPublisher(undefined); + const publisher = await this.openviduService.initDefaultPublisher(undefined); if (publisher) { - // this.handlePublisherSuccess(publisher); this.handlePublisherError(publisher); } diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/models/storage.model.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/models/storage.model.ts index dbd18d2d..e166d697 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/models/storage.model.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/models/storage.model.ts @@ -1,5 +1,7 @@ export enum Storage{ USER_NICKNAME = 'openviduCallNickname', VIDEO_DEVICE = 'openviduCallVideoDevice', - AUDIO_DEVICE = 'openviduCallAudioDevice' + AUDIO_DEVICE = 'openviduCallAudioDevice', + AUDIO_MUTED = 'openviduCallAudioMuted', + VIDEO_MUTED = 'openviduCallVideoMuted' } \ No newline at end of file diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/device/device.service.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/device/device.service.ts index f231a9e4..b28fcd22 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/device/device.service.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/device/device.service.ts @@ -3,7 +3,6 @@ import { Device, OpenVidu } from 'openvidu-browser'; import { CameraType, DeviceType, CustomDevice } from '../../models/device.model'; import { ILogger } from '../../models/logger.model'; -import { Storage } from '../../models/storage.model'; import { LoggerService } from '../logger/logger.service'; import { PlatformService } from '../platform/platform.service'; @@ -23,6 +22,11 @@ export class DeviceService { private videoDevicesDisabled: boolean; private audioDevicesDisabled: boolean; + // Initialized with Storage.VIDEO_MUTED info saved on storage + private _isVideoMuted: boolean; + // Initialized with Storage.AUDIO_MUTED info saved on storage + private _isAudioMuted: boolean; + constructor(private loggerSrv: LoggerService, private platformSrv: PlatformService, private storageSrv: StorageService) { this.log = this.loggerSrv.get('DevicesService'); this.OV = new OpenVidu(); @@ -33,18 +37,21 @@ export class DeviceService { } async initializeDevices() { - // Requesting media permissions. Sometimes, browser doens't launch the media permissions modal. - const mediaStream = await this.OV.getUserMedia({audioSource: undefined, videoSource: undefined }); + // Forcing media permissions request. + // Sometimes, browser doens't launch the media permissions modal. + const mediaStream = await this.OV.getUserMedia({ audioSource: undefined, videoSource: undefined }); + mediaStream?.getAudioTracks().forEach((track) => track.stop()); + mediaStream?.getVideoTracks().forEach((track) => track.stop()); this.devices = await this.OV.getDevices(); const customDevices = this.initializeCustomDevices(this.devices); this.cameras = customDevices.cameras; this.microphones = customDevices.microphones; - mediaStream?.getAudioTracks().forEach((track) => track.stop()); - mediaStream?.getVideoTracks().forEach((track) => track.stop()); + this._isVideoMuted = this.storageSrv.isVideoMuted(); + this._isAudioMuted = this.storageSrv.isAudioMuted(); - this.log.d('Media devices',customDevices); + this.log.d('Media devices', customDevices); } private initializeCustomDevices(defaultVDevices: Device[]) { @@ -52,8 +59,8 @@ export class DeviceService { const defaultMicrophones = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT); const defaultCameras = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT); const customDevices: { cameras: CustomDevice[]; microphones: CustomDevice[] } = { - cameras: [{ label: 'None', device: null, type: null }], - microphones: [{ label: 'None', device: null, type: null }] + cameras: [], + microphones: [] }; if (this.hasAudioDeviceAvailable) { @@ -61,12 +68,12 @@ export class DeviceService { customDevices.microphones.push({ label: device.label, device: device.deviceId }); }); - // Settings microphone selected + // Setting microphone selected const storageMicrophone = this.getMicrophoneFromStogare(); if (!!storageMicrophone) { this.microphoneSelected = storageMicrophone; } else if (customDevices.microphones.length > 0) { - this.microphoneSelected = customDevices.microphones.find((d) => d.label !== 'None'); + this.microphoneSelected = customDevices.microphones[0]; } } @@ -96,12 +103,20 @@ export class DeviceService { if (!!storageCamera) { this.cameraSelected = storageCamera; } else if (customDevices.cameras.length > 0) { - this.cameraSelected = customDevices.cameras.find((d) => d.label !== 'None'); + this.cameraSelected = customDevices.cameras[0]; } } return customDevices; } + isVideoMuted(): boolean { + return this.hasVideoDeviceAvailable() && this._isVideoMuted; + } + + isAudioMuted(): boolean { + return this.hasAudioDeviceAvailable() && this._isAudioMuted; + } + getCameraSelected(): CustomDevice { return this.cameraSelected; } @@ -137,11 +152,11 @@ export class DeviceService { } hasVideoDeviceAvailable(): boolean { - return !this.videoDevicesDisabled && !this.cameras.every((device) => device.label === 'None'); + return !this.videoDevicesDisabled && this.cameras.length > 0; } hasAudioDeviceAvailable(): boolean { - return !this.audioDevicesDisabled && !this.microphones.every((device) => device.label === 'None'); + return !this.audioDevicesDisabled && this.microphones.length > 0; } cameraNeedsMirror(deviceField: string): boolean { @@ -180,24 +195,24 @@ export class DeviceService { } private getMicrophoneFromStogare(): CustomDevice { - let storageDevice = this.storageSrv.get(Storage.AUDIO_DEVICE); - if (!!storageDevice) { + const storageDevice: CustomDevice = this.storageSrv.getAudioDevice(); + if (!!storageDevice && this.microphones.some((device) => device.device === storageDevice.device)) { return storageDevice; } } private getCameraFromStorage() { - let storageDevice = this.storageSrv.get(Storage.VIDEO_DEVICE); - if (!!storageDevice) { + const storageDevice: CustomDevice = this.storageSrv.getVideoDevice(); + if (!!storageDevice && this.cameras.some((device) => device.device === storageDevice.device)) { return storageDevice; } } private saveCameraToStorage(cam: CustomDevice) { - this.storageSrv.set(Storage.VIDEO_DEVICE, cam); + this.storageSrv.setVideoDevice(cam); } private saveMicrophoneToStorage(mic: CustomDevice) { - this.storageSrv.set(Storage.AUDIO_DEVICE, mic); + this.storageSrv.setAudioDevice(mic); } } diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts index 92dc00b1..a6922f77 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/openvidu/openvidu.service.ts @@ -22,8 +22,8 @@ export class OpenViduService { protected screenSession: Session = null; protected videoSource = undefined; protected audioSource = undefined; - protected screenMediaStream: MediaStream = null; - protected webcamMediaStream: MediaStream = null; + // protected screenMediaStream: MediaStream = null; + // protected webcamMediaStream: MediaStream = null; protected log: ILogger; constructor( @@ -49,6 +49,10 @@ export class OpenViduService { } } + getSession(): Session { + return this.getWebcamSession(); + } + getWebcamSession(): Session { return this.webcamSession; } @@ -90,60 +94,44 @@ export class OpenViduService { } } - 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; - } - } - } - 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()); + // 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 { - await this.deviceService.initializeDevices(); const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable(); const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable(); - const isVideoActive = hasVideoDevices && this.deviceService.getCameraSelected().label !== 'None'; - const isAudioActive = hasAudioDevices && this.deviceService.getMicrophoneSelected().label !== 'None'; + const isVideoActive = !this.deviceService.isVideoMuted(); + const isAudioActive = !this.deviceService.isAudioMuted(); let videoSource = null; let audioSource = null; - if(isVideoActive){ + if (hasVideoDevices) { // Video is active, assign the device selected - videoSource = this.deviceService.getCameraSelected().device - } else if(!isVideoActive && hasVideoDevices) { + videoSource = this.deviceService.getCameraSelected().device; + } else if (!isVideoActive && hasVideoDevices) { // Video is muted, assign the default device - videoSource = undefined; + // videoSource = undefined; } - if(isAudioActive){ + if (hasAudioDevices) { // Audio is active, assign the device selected - audioSource = this.deviceService.getMicrophoneSelected().device - } else if(!isAudioActive && hasAudioDevices) { + audioSource = this.deviceService.getMicrophoneSelected().device; + } else if (!isAudioActive && hasAudioDevices) { // Audio is muted, assign the default device - audioSource = undefined; + // 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; @@ -154,8 +142,8 @@ export class OpenViduService { publishAudio: isAudioActive, mirror }; - if (isVideoActive || isAudioActive) { - const publisher = this.initPublisher(targetElement, properties); + if (hasVideoDevices || hasAudioDevices) { + const publisher = await this.initPublisher(targetElement, properties); this.participantService.setMyCameraPublisher(publisher); return publisher; } else { @@ -163,10 +151,10 @@ export class OpenViduService { } } - initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties): Publisher { + async initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties): Promise { this.log.d('Initializing publisher with properties: ', properties); - const publisher = this.OV.initPublisher(targetElement, properties); + const publisher = await this.OV.initPublisherAsync(targetElement, properties); // this.participantService.setMyCameraPublisher(publisher); publisher.once('streamPlaying', () => { (publisher.videos[0].video).parentElement.classList.remove('custom-class'); @@ -174,21 +162,21 @@ export class OpenViduService { return publisher; } - //TODO: This method is used by replaceTrack. Check if it's neecessary - private destroyPublisher(publisher: Publisher): void { - if (!!publisher) { - // publisher.off('streamAudioVolumeChange'); - 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); - } - } - } + //TODO: This method is used by republishTrack. Check if it's neecessary + // private destroyPublisher(publisher: Publisher): void { + // if (!!publisher) { + // // publisher.off('streamAudioVolumeChange'); + // 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); + // } + // } + // } async publish(publisher: Publisher): Promise { if (!!publisher) { @@ -221,7 +209,7 @@ export class OpenViduService { if (!!publisher) { publisher.publishVideo(value); // Send event to subscribers because of video has changed and view must update - this.participantService.updateUsersStatus(); + this.participantService.updateParticipantMediaStatus(); } } @@ -229,44 +217,43 @@ export class OpenViduService { if (!!publisher) { publisher.publishAudio(value); // Send event to subscribers because of video has changed and view must update - this.participantService.updateUsersStatus(); + this.participantService.updateParticipantMediaStatus(); } } - republishTrack(videoSource: string, audioSource: string, mirror: boolean = true): Promise { - return new Promise((resolve, reject) => { - if (!!videoSource) { - this.log.d('Replacing video track ' + videoSource); - this.videoSource = videoSource; - // this.stopVideoTracks(this.webcamUser.getStreamManager().stream.getMediaStream()); - } - if (!!audioSource) { - this.log.d('Replacing audio track ' + audioSource); - this.audioSource = audioSource; - // this.stopAudioTracks(this.webcamUser.getStreamManager().stream.getMediaStream()); - } - this.destroyPublisher(this.participantService.getMyCameraPublisher()); - const properties: PublisherProperties = { - videoSource: this.videoSource, - audioSource: this.audioSource, - publishVideo: this.participantService.hasCameraVideoActive(), - publishAudio: this.participantService.hasCameraAudioActive(), - mirror - }; + // Used replaceTrack instead + // republishTrack(videoSource: string, audioSource: string, mirror: boolean = true): Promise { + // 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 = this.initPublisher(undefined, properties); - this.participantService.setMyCameraPublisher(publisher); + // const publisher = await this.initPublisher(undefined, properties); + // this.participantService.setMyCameraPublisher(publisher); - publisher.once('streamPlaying', () => { - this.participantService.setMyCameraPublisher(publisher); - resolve(); - }); + // publisher.once('streamPlaying', () => { + // this.participantService.setMyCameraPublisher(publisher); + // resolve(); + // }); - publisher.once('accessDenied', () => { - reject(); - }); - }); - } + // publisher.once('accessDenied', () => { + // reject(); + // }); + // }); + // } sendSignal(type: Signal, connections?: Connection[], data?: any): void { const signalOptions: SignalOptions = { @@ -282,28 +269,38 @@ export class OpenViduService { } } - async replaceTrack(currentPublisher: Publisher, newProperties: PublisherProperties) { + async replaceTrack(videoType: VideoType, props: PublisherProperties) { try { - if (!!currentPublisher) { - if (currentPublisher === this.participantService.getMyCameraPublisher()) { - // This branch has been disabled because echo problems replacing audio track. - // this.stopAudioTracks(this.webcamMediaStream); - // this.stopVideoTracks(this.webcamMediaStream); - // this.webcamMediaStream = await this.OV.getUserMedia(newProperties); - // const videoTrack: MediaStreamTrack = this.webcamMediaStream.getVideoTracks()[0]; - // const audioTrack: MediaStreamTrack = this.webcamMediaStream.getAudioTracks()[0]; - // if(!!videoTrack){ - // await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack); - // } - // if(!!audioTrack) { - // await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack); - // } - } else if (currentPublisher === this.participantService.getMyScreenPublisher()) { - const newScreenMediaStream = await this.OVScreen.getUserMedia(newProperties); - this.stopTracks(this.screenMediaStream); - await this.participantService.getMyScreenPublisher().replaceTrack(newScreenMediaStream.getVideoTracks()[0]); - this.screenMediaStream = newScreenMediaStream; + this.log.d(`Replacing ${videoType} track`, props); + if (videoType === VideoType.CAMERA) { + const isReplacingAudio = !!props.audioSource; + const isReplacingVideo = !!props.videoSource; + + if (this.platformService.isFirefox()) { + // 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 + if (isReplacingVideo) { + this.participantService.getMyCameraPublisher().stream.getMediaStream().getVideoTracks()[0].stop(); + } else if (isReplacingAudio) { + this.participantService.getMyCameraPublisher().stream.getMediaStream().getAudioTracks()[0].stop(); + } } + + const track = await this.OV.getUserMedia(props); + if (isReplacingAudio) { + // Replace audio track + const audioTrack: MediaStreamTrack = track.getAudioTracks()[0]; + await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack); + } else if (isReplacingVideo) { + // Replace video track + const videoTrack: MediaStreamTrack = track.getVideoTracks()[0]; + await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack); + } + } else if (videoType === VideoType.SCREEN) { + const newScreenMediaStream = await this.OVScreen.getUserMedia(props); + // this.stopTracks(this.screenMediaStream); + // this.screenMediaStream = newScreenMediaStream; + await this.participantService.getMyScreenPublisher().replaceTrack(newScreenMediaStream.getVideoTracks()[0]); } } catch (error) { this.log.e('Error replacing track ', error); @@ -331,11 +328,25 @@ export class OpenViduService { return remoteCameraConnections; } - 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()); + 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()); + // } + // } } diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/participant/participant.service.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/participant/participant.service.ts index 2d1b976f..cd679ad3 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/participant/participant.service.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/participant/participant.service.ts @@ -113,7 +113,7 @@ export class ParticipantService { this._screensharing.next(false); } - updateUsersStatus() { + updateParticipantMediaStatus() { this._cameraVideoActive.next(this.localParticipant.isCameraVideoActive()); if (this.isMyCameraEnabled()) { this._cameraAudioActive.next(this.localParticipant.isCameraAudioActive()); diff --git a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/storage/storage.service.ts b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/storage/storage.service.ts index 4cf30e03..c348589f 100644 --- a/openvidu-components-angular/projects/openvidu-angular/src/lib/services/storage/storage.service.ts +++ b/openvidu-components-angular/projects/openvidu-angular/src/lib/services/storage/storage.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { ILogger } from '../../models/logger.model'; import { LoggerService } from '../logger/logger.service'; +import { Storage } from '../../models/storage.model'; @Injectable({ providedIn: 'root' @@ -13,12 +14,49 @@ export class StorageService { this.log = this.loggerSrv.get('StorageService'); } - public set(key: string, item: any) { + getNickname(): string { + return this.get(Storage.USER_NICKNAME); + } + + setNickname(name: string){ + this.set(Storage.USER_NICKNAME, name); + } + getVideoDevice() { + return this.get(Storage.VIDEO_DEVICE); + } + + setVideoDevice(device: any) { + this.set(Storage.VIDEO_DEVICE, device); + } + + getAudioDevice() { + return this.get(Storage.AUDIO_DEVICE); + } + + setAudioDevice(device: any) { + this.set(Storage.AUDIO_DEVICE, device); + } + isVideoMuted(): boolean { + return this.get(Storage.VIDEO_MUTED) === 'true'; + } + setVideoMuted(muted: boolean) { + this.set(Storage.VIDEO_MUTED, `${muted}`); + } + + isAudioMuted(): boolean { + return this.get(Storage.AUDIO_MUTED) === 'true'; + } + + setAudioMuted(muted: boolean) { + this.set(Storage.AUDIO_MUTED, `${muted}`); + } + + private set(key: string, item: any) { const value = JSON.stringify({ item: item }); // this.log.d('Storing on localStorage "' + key + '" with value "' + value + '"'); this.storage.setItem(key, value); } - public get(key: string): any { + private get(key: string): any { const value = JSON.parse(this.storage.getItem(key)); return value?.item ? value.item : null; }