diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html index c95fb3771..9801bad49 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.html @@ -1,18 +1,18 @@
@if (compact) { - @if (hasAudioDevices) { + @if (hasAudioDevices()) {
} @else { - @if (hasAudioDevices) { + @if (hasAudioDevices()) {
mic_off - {{ !hasAudioDevices ? ('PREJOIN.NO_AUDIO_DEVICE' | translate) : 'Microphone disabled' }} + {{ !hasAudioDevices() ? ('PREJOIN.NO_AUDIO_DEVICE' | translate) : 'Microphone disabled' }}
@@ -84,14 +84,14 @@ - @for (microphone of microphones; track microphone.device) { + @for (microphone of microphones(); track microphone.device) { } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.ts index 5eb1639b5..594dcd868 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/audio-devices/audio-devices.component.ts @@ -1,8 +1,6 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Component, effect, EventEmitter, Input, OnInit, Output, Signal, WritableSignal } from '@angular/core'; import { CustomDevice } from '../../../models/device.model'; import { ILogger } from '../../../models/logger.model'; -import { ParticipantModel } from '../../../models/participant.model'; import { DeviceService } from '../../../services/device/device.service'; import { LoggerService } from '../../../services/logger/logger.service'; import { ParticipantService } from '../../../services/participant/participant.service'; @@ -17,19 +15,20 @@ import { StorageService } from '../../../services/storage/storage.service'; styleUrls: ['./audio-devices.component.scss'], standalone: false }) -export class AudioDevicesComponent implements OnInit, OnDestroy { +export class AudioDevicesComponent implements OnInit { @Input() compact: boolean = false; @Output() onAudioDeviceChanged = new EventEmitter(); @Output() onAudioEnabledChanged = new EventEmitter(); - microphoneStatusChanging: boolean; - hasAudioDevices: boolean; - isMicrophoneEnabled: boolean; - microphoneSelected: CustomDevice | undefined; - microphones: CustomDevice[] = []; - private localParticipantSubscription: Subscription; + microphoneStatusChanging: boolean = false; + isMicrophoneEnabled: boolean = false; private log: ILogger; + // Expose signals directly from service (reactive) + protected readonly microphones: WritableSignal; + protected readonly microphoneSelected: WritableSignal; + protected readonly hasAudioDevices: Signal; + constructor( private deviceSrv: DeviceService, private storageSrv: StorageService, @@ -37,24 +36,24 @@ export class AudioDevicesComponent implements OnInit, OnDestroy { private loggerSrv: LoggerService ) { this.log = this.loggerSrv.get('AudioDevicesComponent'); + this.microphones = this.deviceSrv.microphones; + this.microphoneSelected = this.deviceSrv.microphoneSelected; + this.hasAudioDevices = this.deviceSrv.hasAudioDevices; + + // Use effect instead of subscription for reactive updates + effect(() => { + const participant = this.participantService.localParticipantSignal(); + if (participant) { + this.isMicrophoneEnabled = participant.isMicrophoneEnabled; + this.storageSrv.setMicrophoneEnabled(this.isMicrophoneEnabled); + } + }); } async ngOnInit() { - this.subscribeToParticipantMediaProperties(); - this.hasAudioDevices = this.deviceSrv.hasAudioDeviceAvailable(); - if (this.hasAudioDevices) { - this.microphones = this.deviceSrv.getMicrophones(); - this.microphoneSelected = this.deviceSrv.getMicrophoneSelected(); - } - this.isMicrophoneEnabled = this.participantService.isMyMicrophoneEnabled(); } - ngOnDestroy() { - this.microphones = []; - if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe(); - } - async toggleMic(event: any) { event.stopPropagation(); this.microphoneStatusChanging = true; @@ -72,8 +71,7 @@ export class AudioDevicesComponent implements OnInit, OnDestroy { this.microphoneStatusChanging = true; await this.participantService.switchMicrophone(device.device); this.deviceSrv.setMicSelected(device.device); - this.microphoneSelected = this.deviceSrv.getMicrophoneSelected(); - this.onAudioDeviceChanged.emit(this.microphoneSelected); + this.onAudioDeviceChanged.emit(this.microphoneSelected()); } } catch (error) { this.log.e('Error switching microphone', error); @@ -89,17 +87,4 @@ export class AudioDevicesComponent implements OnInit, OnDestroy { compareObjectDevices(o1: CustomDevice, o2: CustomDevice): boolean { return o1.label === o2.label; } - - /** - * This subscription is necessary to update the microphone status when the user changes it from toolbar and - * the settings panel is opened. With this, the microphone status is updated in the settings panel. - */ - private subscribeToParticipantMediaProperties() { - this.localParticipantSubscription = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => { - if (p) { - this.isMicrophoneEnabled = p.isMicrophoneEnabled; - this.storageSrv.setMicrophoneEnabled(this.isMicrophoneEnabled); - } - }); - } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html index dd65e256a..7b1169f22 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.html @@ -1,18 +1,18 @@
@if (compact) { - @if (hasVideoDevices) { + @if (hasVideoDevices()) {
} @else { - @if (hasVideoDevices) { + @if (hasVideoDevices()) {
@@ -80,14 +80,14 @@ - @for (camera of cameras; track camera.device) { + @for (camera of cameras(); track camera.device) { } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts index a6f051f1c..ce669dbbf 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.ts @@ -1,8 +1,6 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Component, effect, EventEmitter, Input, OnInit, Output, Signal, WritableSignal } from '@angular/core'; import { CustomDevice } from '../../../models/device.model'; import { ILogger } from '../../../models/logger.model'; -import { ParticipantModel } from '../../../models/participant.model'; import { DeviceService } from '../../../services/device/device.service'; import { LoggerService } from '../../../services/logger/logger.service'; import { ParticipantService } from '../../../services/participant/participant.service'; @@ -17,18 +15,18 @@ import { StorageService } from '../../../services/storage/storage.service'; styleUrls: ['./video-devices.component.scss'], standalone: false }) -export class VideoDevicesComponent implements OnInit, OnDestroy { +export class VideoDevicesComponent implements OnInit { @Input() compact: boolean = false; @Output() onVideoDeviceChanged = new EventEmitter(); @Output() onVideoEnabledChanged = new EventEmitter(); @Output() onVideoDevicesLoaded = new EventEmitter(); - cameraStatusChanging: boolean; - isCameraEnabled: boolean; - cameraSelected: CustomDevice | undefined; - hasVideoDevices: boolean; - cameras: CustomDevice[] = []; - localParticipantSubscription: Subscription; + cameraStatusChanging: boolean = false; + isCameraEnabled: boolean = false; + + protected readonly cameras: WritableSignal; + protected readonly cameraSelected: WritableSignal; + protected readonly hasVideoDevices: Signal; private log: ILogger; @@ -39,26 +37,26 @@ export class VideoDevicesComponent implements OnInit, OnDestroy { private loggerSrv: LoggerService ) { this.log = this.loggerSrv.get('VideoDevicesComponent'); + this.cameras = this.deviceSrv.cameras; + this.cameraSelected = this.deviceSrv.cameraSelected; + this.hasVideoDevices = this.deviceSrv.hasVideoDevices; + + // Use effect instead of subscription for reactive updates + effect(() => { + const participant = this.participantService.localParticipantSignal(); + if (participant) { + this.isCameraEnabled = participant.isCameraEnabled; + this.storageSrv.setCameraEnabled(this.isCameraEnabled); + } + }); } async ngOnInit() { - this.subscribeToParticipantMediaProperties(); - - this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable(); - if (this.hasVideoDevices) { - this.cameras = this.deviceSrv.getCameras(); - this.cameraSelected = this.deviceSrv.getCameraSelected(); - } - - this.onVideoDevicesLoaded.emit(this.cameras); + // Emit initial device list (reactively) + this.onVideoDevicesLoaded.emit(this.cameras()); this.isCameraEnabled = this.participantService.isMyCameraEnabled(); } - async ngOnDestroy() { - this.cameras = []; - if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe(); - } - async toggleCam(event: any) { event.stopPropagation(); this.cameraStatusChanging = true; @@ -75,14 +73,10 @@ export class VideoDevicesComponent implements OnInit, OnDestroy { // Is New deviceId different from the old one? if (this.deviceSrv.needUpdateVideoTrack(device)) { - this.cameraStatusChanging = true; - await this.participantService.switchCamera(device.device); - this.deviceSrv.setCameraSelected(device.device); - this.cameraSelected = device; - this.onVideoDeviceChanged.emit(this.cameraSelected); + this.onVideoDeviceChanged.emit(this.cameraSelected()); } } catch (error) { this.log.e('Error switching camera', error); @@ -98,17 +92,4 @@ export class VideoDevicesComponent implements OnInit, OnDestroy { compareObjectDevices(o1: CustomDevice, o2: CustomDevice): boolean { return o1.label === o2.label; } - - /** - * This subscription is necessary to update the camera status when the user changes it from toolbar and - * the settings panel is opened. With this, the camera status is updated in the settings panel. - */ - private subscribeToParticipantMediaProperties() { - this.localParticipantSubscription = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => { - if (p) { - this.isCameraEnabled = p.isCameraEnabled; - this.storageSrv.setCameraEnabled(this.isCameraEnabled); - } - }); - } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts index c10393d79..00c688b9d 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/openvidu/openvidu.service.ts @@ -421,9 +421,12 @@ export class OpenViduService { // Video device if (videoDeviceId === true) { - options.video = this.deviceService.hasVideoDeviceAvailable() - ? { deviceId: this.deviceService.getCameraSelected()?.device || 'default' } - : false; + if (this.deviceService.hasVideoDeviceAvailable()) { + const selectedCamera = this.deviceService.getCameraSelected(); + options.video = { deviceId: selectedCamera?.device || 'default' }; + } else { + options.video = false; + } } else if (videoDeviceId === false) { options.video = false; } else { @@ -433,8 +436,8 @@ export class OpenViduService { // Audio device if (audioDeviceId === true) { if (this.deviceService.hasAudioDeviceAvailable()) { - audioDeviceId = this.deviceService.getMicrophoneSelected()?.device || 'default'; - (options.audio as AudioCaptureOptions).deviceId = audioDeviceId; + const selectedMic = this.deviceService.getMicrophoneSelected(); + (options.audio as AudioCaptureOptions).deviceId = selectedMic?.device || 'default'; } else { options.audio = false; }