Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-63274] Use device label instead of id to save defaults #979

Merged
merged 1 commit into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions e2e/tests/start_call.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,21 @@ test.describe('setting audio input device', () => {
await page.locator('#calls-widget-audio-input-button').click();
await expect(page.locator('#calls-widget-audio-inputs-menu')).toBeVisible();

let currentAudioInputDevice = await page.evaluate(() => {
const currentAudioInputDeviceID = await page.evaluate(() => {
return window.callsClient.currentInputAudioDevice?.deviceId;
});
if (currentAudioInputDevice) {
if (currentAudioInputDeviceID) {
test.fail();
return;
}

await page.locator('#calls-widget-audio-inputs-menu button:has-text("Fake Audio Input 1")').click();
await expect(page.locator('#calls-widget-audio-inputs-menu')).toBeHidden();

currentAudioInputDevice = await page.evaluate(() => {
return window.callsClient.currentAudioInputDevice?.deviceId;
const currentAudioInputDevice = await page.evaluate(() => {
return window.callsClient.currentAudioInputDevice;
});
if (!currentAudioInputDevice) {
if (currentAudioInputDevice.label !== 'Fake Audio Input 1') {
test.fail();
return;
}
Expand All @@ -272,18 +272,18 @@ test.describe('setting audio input device', () => {
const currentAudioInputDevice2 = await page.evaluate(() => {
return window.callsClient.currentAudioInputDevice?.deviceId;
});
if (currentAudioInputDevice2 !== currentAudioInputDevice) {
if (currentAudioInputDevice2 !== currentAudioInputDevice.deviceId) {
test.fail();
return;
}

await devPage.leaveCall();

await page.reload();
const deviceID = await page.evaluate(() => {
return window.localStorage.getItem('calls_default_audio_input');
const device = await page.evaluate(() => {
return JSON.parse(window.localStorage.getItem('calls_default_audio_input')!);
});
if (!deviceID) {
if (!device || !device.deviceId || !device.label) {
test.fail();
}
});
Expand Down Expand Up @@ -316,21 +316,21 @@ test.describe('setting audio output device', () => {
await page.locator('#calls-widget-audio-output-button').click();
await expect(page.locator('#calls-widget-audio-outputs-menu')).toBeVisible();

let currentAudioOutputDevice = await page.evaluate(() => {
const currentAudioOutputDeviceID = await page.evaluate(() => {
return window.callsClient.currentAudioOutputDevice?.deviceId;
});
if (currentAudioOutputDevice) {
if (currentAudioOutputDeviceID) {
test.fail();
return;
}

await page.locator('#calls-widget-audio-outputs-menu button:has-text("Fake Audio Output 1")').click();
await expect(page.locator('#calls-widget-audio-outputs-menu')).toBeHidden();

currentAudioOutputDevice = await page.evaluate(() => {
return window.callsClient.currentAudioOutputDevice?.deviceId;
const currentAudioOutputDevice = await page.evaluate(() => {
return window.callsClient.currentAudioOutputDevice;
});
if (!currentAudioOutputDevice) {
if (currentAudioOutputDevice.label !== 'Fake Audio Output 1') {
test.fail();
return;
}
Expand All @@ -342,18 +342,18 @@ test.describe('setting audio output device', () => {
const currentAudioOutputDevice2 = await page.evaluate(() => {
return window.callsClient.currentAudioOutputDevice?.deviceId;
});
if (currentAudioOutputDevice2 !== currentAudioOutputDevice) {
if (currentAudioOutputDevice2 !== currentAudioOutputDevice.deviceId) {
test.fail();
return;
}

await devPage.leaveCall();

await page.reload();
const deviceID = await page.evaluate(() => {
return window.localStorage.getItem('calls_default_audio_output');
const device = await page.evaluate(() => {
return JSON.parse(window.localStorage.getItem('calls_default_audio_output')!);
});
if (!deviceID) {
if (!device || !device.deviceId || !device.label) {
test.fail();
}
});
Expand Down
62 changes: 51 additions & 11 deletions webapp/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,51 @@ export default class CallsClient extends EventEmitter {
};
}

const defaultInputID = window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY);
const defaultOutputID = window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY);
if (defaultInputID && !this.currentAudioInputDevice) {
const devices = this.audioDevices.inputs.filter((dev) => {
return dev.deviceId === defaultInputID;
let defaultInputDevice: {deviceId: string; label?: string} = {
deviceId: '',
};
const defaultAudioInputData = window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY);
if (defaultAudioInputData) {
try {
defaultInputDevice = JSON.parse(defaultAudioInputData);
} catch {
// Backwards compatibility case when we used to store the device id directly (before MM-63274).
defaultInputDevice = {
deviceId: defaultAudioInputData,
};
}
}

let defaultOutputDevice: {deviceId: string; label?: string} = {
deviceId: '',
};
const defaultAudioOutputData = window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY);
if (defaultAudioOutputData) {
try {
defaultOutputDevice = JSON.parse(defaultAudioOutputData);
} catch {
// Backwards compatibility case when we used to store the device id directly (before MM-63274).
defaultOutputDevice = {
deviceId: defaultAudioOutputData,
};
}
}

if (defaultInputDevice.deviceId && !this.currentAudioInputDevice) {
let devices = this.audioDevices.inputs.filter((dev) => {
return dev.deviceId === defaultInputDevice.deviceId || dev.label === defaultInputDevice.label;
});

if (devices.length > 1) {
// If there are multiple devices with the same label, we select the default device by ID.
logInfo('multiple audio input devices found with the same label, checking by id', devices);
devices = devices.filter((dev) => dev.deviceId === defaultInputDevice.deviceId);
}

if (devices && devices.length === 1) {
logDebug(`found default audio input device to use: ${devices[0].label}`);
audioOptions.deviceId = {
exact: defaultInputID,
exact: devices[0].deviceId,
};
this.currentAudioInputDevice = devices[0];
} else {
Expand All @@ -126,11 +160,17 @@ export default class CallsClient extends EventEmitter {
}
}

if (defaultOutputID) {
const devices = this.audioDevices.outputs.filter((dev) => {
return dev.deviceId === defaultOutputID;
if (defaultOutputDevice.deviceId) {
let devices = this.audioDevices.outputs.filter((dev) => {
return dev.deviceId === defaultOutputDevice.deviceId || dev.label === defaultOutputDevice.label;
});

if (devices.length > 1) {
// If there are multiple devices with the same label, we select the default device by ID.
logInfo('multiple audio output devices found with the same label, checking by id', devices);
devices = devices.filter((dev) => dev.deviceId === defaultInputDevice.deviceId);
}

if (devices && devices.length === 1) {
logDebug(`found default audio output device to use: ${devices[0].label}`);
this.currentAudioOutputDevice = devices[0];
Expand Down Expand Up @@ -423,7 +463,7 @@ export default class CallsClient extends EventEmitter {
return;
}

window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY, device.deviceId);
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY, JSON.stringify(device));
this.currentAudioInputDevice = device;

// We emit this event so it's easier to keep state in sync between widget and pop out.
Expand Down Expand Up @@ -473,7 +513,7 @@ export default class CallsClient extends EventEmitter {
if (!this.peer) {
return;
}
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY, device.deviceId);
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY, JSON.stringify(device));
this.currentAudioOutputDevice = device;

// We emit this event so it's easier to keep state in sync between widget and pop out.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import ReactSelect from 'react-select';
import {
STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY,
STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY,
} from 'src/constants';
import {logErr} from 'src/log';
import styled from 'styled-components';

Expand Down Expand Up @@ -41,19 +45,39 @@ const AudioDevicesSelection = forwardRef<AudioDevicesSelectionHandle, AudioDevic
return selectedOption;
}

const defaultDeviceID = deviceType === 'inputs' ?
window.localStorage.getItem('calls_default_audio_input') :
window.localStorage.getItem('calls_default_audio_output');
const defaultDeviceData = deviceType === 'inputs' ?
window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY) :
window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY);

for (const device of devices) {
if (device.deviceId === defaultDeviceID) {
return {
label: device.label,
value: device.deviceId,
let defaultDevice: {deviceId: string; label?: string} = {
deviceId: '',
};

if (defaultDeviceData) {
try {
defaultDevice = JSON.parse(defaultDeviceData);
} catch {
// Backwards compatibility case when we used to store the device id directly (before MM-63274).
defaultDevice = {
deviceId: defaultDeviceData,
};
}
}

let selected = devices.filter((dev) => {
return dev.deviceId === defaultDevice.deviceId || dev.label === defaultDevice.label;
});
if (selected.length > 1) {
// If there are multiple devices with the same label, we select the default device by ID.
selected = selected.filter((dev) => dev.deviceId === defaultDevice.deviceId);
}
if (selected.length > 0) {
return {
label: selected[0].label,
value: selected[0].deviceId,
};
}

return {
label: devices[0]?.label ?? '',
value: devices[0]?.deviceId ?? '',
Expand Down Expand Up @@ -122,10 +146,17 @@ export default function AudioDevicesSettingsSection() {

const handleSave = () => {
if (audioInputsRef.current) {
window.localStorage.setItem('calls_default_audio_input', audioInputsRef.current.getOption().value);
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY, JSON.stringify({
deviceId: audioInputsRef.current.getOption().value,
label: audioInputsRef.current.getOption().label,
}));
}
if (audioOutputsRef.current) {
window.localStorage.setItem('calls_default_audio_output', audioOutputsRef.current.getOption().value);
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY, JSON.stringify({
deviceId: audioOutputsRef.current.getOption().value,
label: audioOutputsRef.current.getOption().label,
},
));
}
setActive(false);
};
Expand Down
Loading