From b2701311fbf5fea17edfd41347b384643d505860 Mon Sep 17 00:00:00 2001 From: CSantosM <4a.santos@gmail.com> Date: Fri, 20 Feb 2026 16:42:25 +0100 Subject: [PATCH] ov-components: Uses device constraints for media tracks Ensures correct audio and video device selection by applying `exact` or `ideal` constraints when creating or restarting media tracks. This improves device handling and avoids potential issues when switching between different audio or video devices. --- .../lib/services/openvidu/openvidu.service.ts | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) 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 1a32f07f0..efeb4d4c3 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 @@ -427,28 +427,28 @@ export class OpenViduService { if (videoDeviceId === true) { if (this.deviceService.hasVideoDeviceAvailable()) { const selectedCamera = this.deviceService.getCameraSelected(); - options.video = { deviceId: selectedCamera?.device || 'default' }; + options.video = { deviceId: this.toDeviceConstraint(selectedCamera?.device) }; } else { options.video = false; } } else if (videoDeviceId === false) { options.video = false; } else { - (options.video as VideoCaptureOptions).deviceId = videoDeviceId; + (options.video as VideoCaptureOptions).deviceId = this.toDeviceConstraint(videoDeviceId); } // Audio device if (audioDeviceId === true) { if (this.deviceService.hasAudioDeviceAvailable()) { const selectedMic = this.deviceService.getMicrophoneSelected(); - (options.audio as AudioCaptureOptions).deviceId = selectedMic?.device || 'default'; + (options.audio as AudioCaptureOptions).deviceId = this.toDeviceConstraint(selectedMic?.device); } else { options.audio = false; } } else if (audioDeviceId === false) { options.audio = false; } else { - (options.audio as AudioCaptureOptions).deviceId = audioDeviceId; + (options.audio as AudioCaptureOptions).deviceId = this.toDeviceConstraint(audioDeviceId); } let newLocalTracks: LocalTrack[] = []; @@ -518,6 +518,13 @@ export class OpenViduService { return tracks; } + private toDeviceConstraint(deviceId?: string): ConstrainDOMString { + if (!deviceId || deviceId === 'default') { + return { ideal: 'default' }; + } + return { exact: deviceId }; + } + /** * @internal * As the Room is not created yet, we need to handle the media tracks with a temporary array of tracks. @@ -588,13 +595,13 @@ export class OpenViduService { */ async switchCamera(deviceId: string): Promise { const existingTrack = this.localTracks.find((t) => t.kind === Track.Kind.Video) as LocalVideoTrack | undefined; - + const options: VideoCaptureOptions = { deviceId: this.toDeviceConstraint(deviceId) }; if (existingTrack) { try { // restartTrack replaces the underlying MediaStreamTrack in-place. // LiveKit's setMediaStreamTrack will call processor.restart(newTrack) automatically // if a background processor is attached, preserving the active effect. - await existingTrack.restartTrack({ deviceId }); + await existingTrack.restartTrack(options); if (!this.deviceService.isCameraEnabled()) { await existingTrack.mute(); } @@ -608,7 +615,7 @@ export class OpenViduService { // No existing track (edge case: camera was unavailable/unpublished) → create a fresh one try { - const newVideoTracks = await createLocalTracks({ video: { deviceId } }); + const newVideoTracks = await createLocalTracks({ video: options }); const videoTrack = newVideoTracks.find((t) => t.kind === Track.Kind.Video) as LocalVideoTrack | undefined; if (videoTrack) { if (!this.deviceService.isCameraEnabled()) { @@ -679,15 +686,16 @@ export class OpenViduService { */ async switchMicrophone(deviceId: string): Promise { const existingTrack = this.localTracks.find((t) => t.kind === Track.Kind.Audio) as LocalAudioTrack | undefined; + const options: AudioCaptureOptions = { + deviceId: this.toDeviceConstraint(deviceId), + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true + }; if (existingTrack) { try { - await existingTrack.restartTrack({ - deviceId, - echoCancellation: true, - noiseSuppression: true, - autoGainControl: true - }); + await existingTrack.restartTrack(options); if (!this.deviceService.isMicrophoneEnabled()) { await existingTrack.mute(); } @@ -701,14 +709,7 @@ export class OpenViduService { // No existing track (edge case) → create a fresh one try { - const newAudioTracks = await createLocalTracks({ - audio: { - deviceId, - echoCancellation: true, - noiseSuppression: true, - autoGainControl: true - } - }); + const newAudioTracks = await createLocalTracks(options as CreateLocalTracksOptions); const audioTrack = newAudioTracks.find((t) => t.kind === Track.Kind.Audio); if (audioTrack) { if (!this.deviceService.isMicrophoneEnabled()) {