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.
pull/860/head
CSantosM 2026-02-20 16:42:25 +01:00
parent 4617dfd797
commit b2701311fb
1 changed files with 22 additions and 21 deletions

View File

@ -427,28 +427,28 @@ export class OpenViduService {
if (videoDeviceId === true) { if (videoDeviceId === true) {
if (this.deviceService.hasVideoDeviceAvailable()) { if (this.deviceService.hasVideoDeviceAvailable()) {
const selectedCamera = this.deviceService.getCameraSelected(); const selectedCamera = this.deviceService.getCameraSelected();
options.video = { deviceId: selectedCamera?.device || 'default' }; options.video = { deviceId: this.toDeviceConstraint(selectedCamera?.device) };
} else { } else {
options.video = false; options.video = false;
} }
} else if (videoDeviceId === false) { } else if (videoDeviceId === false) {
options.video = false; options.video = false;
} else { } else {
(options.video as VideoCaptureOptions).deviceId = videoDeviceId; (options.video as VideoCaptureOptions).deviceId = this.toDeviceConstraint(videoDeviceId);
} }
// Audio device // Audio device
if (audioDeviceId === true) { if (audioDeviceId === true) {
if (this.deviceService.hasAudioDeviceAvailable()) { if (this.deviceService.hasAudioDeviceAvailable()) {
const selectedMic = this.deviceService.getMicrophoneSelected(); const selectedMic = this.deviceService.getMicrophoneSelected();
(options.audio as AudioCaptureOptions).deviceId = selectedMic?.device || 'default'; (options.audio as AudioCaptureOptions).deviceId = this.toDeviceConstraint(selectedMic?.device);
} else { } else {
options.audio = false; options.audio = false;
} }
} else if (audioDeviceId === false) { } else if (audioDeviceId === false) {
options.audio = false; options.audio = false;
} else { } else {
(options.audio as AudioCaptureOptions).deviceId = audioDeviceId; (options.audio as AudioCaptureOptions).deviceId = this.toDeviceConstraint(audioDeviceId);
} }
let newLocalTracks: LocalTrack[] = []; let newLocalTracks: LocalTrack[] = [];
@ -518,6 +518,13 @@ export class OpenViduService {
return tracks; return tracks;
} }
private toDeviceConstraint(deviceId?: string): ConstrainDOMString {
if (!deviceId || deviceId === 'default') {
return { ideal: 'default' };
}
return { exact: deviceId };
}
/** /**
* @internal * @internal
* As the Room is not created yet, we need to handle the media tracks with a temporary array of tracks. * 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<void> { async switchCamera(deviceId: string): Promise<void> {
const existingTrack = this.localTracks.find((t) => t.kind === Track.Kind.Video) as LocalVideoTrack | undefined; const existingTrack = this.localTracks.find((t) => t.kind === Track.Kind.Video) as LocalVideoTrack | undefined;
const options: VideoCaptureOptions = { deviceId: this.toDeviceConstraint(deviceId) };
if (existingTrack) { if (existingTrack) {
try { try {
// restartTrack replaces the underlying MediaStreamTrack in-place. // restartTrack replaces the underlying MediaStreamTrack in-place.
// LiveKit's setMediaStreamTrack will call processor.restart(newTrack) automatically // LiveKit's setMediaStreamTrack will call processor.restart(newTrack) automatically
// if a background processor is attached, preserving the active effect. // if a background processor is attached, preserving the active effect.
await existingTrack.restartTrack({ deviceId }); await existingTrack.restartTrack(options);
if (!this.deviceService.isCameraEnabled()) { if (!this.deviceService.isCameraEnabled()) {
await existingTrack.mute(); await existingTrack.mute();
} }
@ -608,7 +615,7 @@ export class OpenViduService {
// No existing track (edge case: camera was unavailable/unpublished) → create a fresh one // No existing track (edge case: camera was unavailable/unpublished) → create a fresh one
try { 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; const videoTrack = newVideoTracks.find((t) => t.kind === Track.Kind.Video) as LocalVideoTrack | undefined;
if (videoTrack) { if (videoTrack) {
if (!this.deviceService.isCameraEnabled()) { if (!this.deviceService.isCameraEnabled()) {
@ -679,15 +686,16 @@ export class OpenViduService {
*/ */
async switchMicrophone(deviceId: string): Promise<void> { async switchMicrophone(deviceId: string): Promise<void> {
const existingTrack = this.localTracks.find((t) => t.kind === Track.Kind.Audio) as LocalAudioTrack | undefined; const existingTrack = this.localTracks.find((t) => t.kind === Track.Kind.Audio) as LocalAudioTrack | undefined;
const options: AudioCaptureOptions = {
if (existingTrack) { deviceId: this.toDeviceConstraint(deviceId),
try {
await existingTrack.restartTrack({
deviceId,
echoCancellation: true, echoCancellation: true,
noiseSuppression: true, noiseSuppression: true,
autoGainControl: true autoGainControl: true
}); };
if (existingTrack) {
try {
await existingTrack.restartTrack(options);
if (!this.deviceService.isMicrophoneEnabled()) { if (!this.deviceService.isMicrophoneEnabled()) {
await existingTrack.mute(); await existingTrack.mute();
} }
@ -701,14 +709,7 @@ export class OpenViduService {
// No existing track (edge case) → create a fresh one // No existing track (edge case) → create a fresh one
try { try {
const newAudioTracks = await createLocalTracks({ const newAudioTracks = await createLocalTracks(options as CreateLocalTracksOptions);
audio: {
deviceId,
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
const audioTrack = newAudioTracks.find((t) => t.kind === Track.Kind.Audio); const audioTrack = newAudioTracks.find((t) => t.kind === Track.Kind.Audio);
if (audioTrack) { if (audioTrack) {
if (!this.deviceService.isMicrophoneEnabled()) { if (!this.deviceService.isMicrophoneEnabled()) {