Fix device selection persistence bug
This commit is contained in:
parent
bad065859c
commit
a3b972f6e7
4 changed files with 157 additions and 67 deletions
|
@ -3,20 +3,27 @@
|
||||||
|
|
||||||
import type { AudioDevice } from '@signalapp/ringrtc';
|
import type { AudioDevice } from '@signalapp/ringrtc';
|
||||||
|
|
||||||
export function findBestMatchingAudioDeviceIndex({
|
export function findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available,
|
available,
|
||||||
preferred,
|
preferred,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
available: ReadonlyArray<AudioDevice>;
|
available: ReadonlyArray<AudioDevice>;
|
||||||
preferred: undefined | AudioDevice;
|
preferred: undefined | AudioDevice;
|
||||||
}>): undefined | number {
|
}>,
|
||||||
|
isWindows: boolean
|
||||||
|
): undefined | number {
|
||||||
if (!preferred) {
|
if (!preferred) {
|
||||||
return available.length > 0 ? 0 : undefined;
|
return available.length > 0 ? 0 : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On Linux and Mac, the default device is at index 0.
|
||||||
|
// On Windows, there are two default devices, as presented by RingRTC:
|
||||||
|
// * The default communications device (for voice calls, at index 0)
|
||||||
|
// * the default device (for, e.g., media, at index 1)
|
||||||
if (
|
if (
|
||||||
preferred.index === 0 ||
|
preferred.index === 0 ||
|
||||||
(preferred.index === 1 && available.length >= 2)
|
(isWindows && preferred.index === 1 && available.length >= 2)
|
||||||
) {
|
) {
|
||||||
return preferred.index;
|
return preferred.index;
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@ import { sendCallLinkUpdateSync } from '../util/sendCallLinkUpdateSync';
|
||||||
import { createIdenticon } from '../util/createIdenticon';
|
import { createIdenticon } from '../util/createIdenticon';
|
||||||
import { getColorForCallLink } from '../util/getColorForCallLink';
|
import { getColorForCallLink } from '../util/getColorForCallLink';
|
||||||
import { getUseRingrtcAdm } from '../util/ringrtc/ringrtcAdm';
|
import { getUseRingrtcAdm } from '../util/ringrtc/ringrtcAdm';
|
||||||
|
import OS from '../util/os/osMain';
|
||||||
|
|
||||||
const { wasGroupCallRingPreviouslyCanceled } = DataReader;
|
const { wasGroupCallRingPreviouslyCanceled } = DataReader;
|
||||||
const {
|
const {
|
||||||
|
@ -2330,20 +2331,26 @@ export class CallingClass {
|
||||||
await this.getAvailableIODevices();
|
await this.getAvailableIODevices();
|
||||||
|
|
||||||
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
|
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
|
||||||
const selectedMicIndex = findBestMatchingAudioDeviceIndex({
|
const selectedMicIndex = findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: availableMicrophones,
|
available: availableMicrophones,
|
||||||
preferred: preferredMicrophone,
|
preferred: preferredMicrophone,
|
||||||
});
|
},
|
||||||
|
OS.isWindows()
|
||||||
|
);
|
||||||
const selectedMicrophone =
|
const selectedMicrophone =
|
||||||
selectedMicIndex !== undefined
|
selectedMicIndex !== undefined
|
||||||
? availableMicrophones[selectedMicIndex]
|
? availableMicrophones[selectedMicIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
|
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
|
||||||
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex({
|
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: availableSpeakers,
|
available: availableSpeakers,
|
||||||
preferred: preferredSpeaker,
|
preferred: preferredSpeaker,
|
||||||
});
|
},
|
||||||
|
OS.isWindows()
|
||||||
|
);
|
||||||
const selectedSpeaker =
|
const selectedSpeaker =
|
||||||
selectedSpeakerIndex !== undefined
|
selectedSpeakerIndex !== undefined
|
||||||
? availableSpeakers[selectedSpeakerIndex]
|
? availableSpeakers[selectedSpeakerIndex]
|
||||||
|
|
|
@ -14,10 +14,13 @@ describe('"find best matching device" helpers', () => {
|
||||||
{ name: 'Big Microphone', index: 1, uniqueId: 'abc123' },
|
{ name: 'Big Microphone', index: 1, uniqueId: 'abc123' },
|
||||||
].forEach(preferred => {
|
].forEach(preferred => {
|
||||||
assert.isUndefined(
|
assert.isUndefined(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [],
|
available: [],
|
||||||
preferred,
|
preferred,
|
||||||
})
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,14 +29,17 @@ describe('"find best matching device" helpers', () => {
|
||||||
const itReturnsTheFirstAvailableDeviceIfNoneIsPreferred = () => {
|
const itReturnsTheFirstAvailableDeviceIfNoneIsPreferred = () => {
|
||||||
it('returns the first available device if none is preferred', () => {
|
it('returns the first available device if none is preferred', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [
|
available: [
|
||||||
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
],
|
],
|
||||||
preferred: undefined,
|
preferred: undefined,
|
||||||
}),
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -41,28 +47,34 @@ describe('"find best matching device" helpers', () => {
|
||||||
|
|
||||||
const testUniqueIdMatch = () => {
|
const testUniqueIdMatch = () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [
|
available: [
|
||||||
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
],
|
],
|
||||||
preferred: { name: 'Ignored', index: 99, uniqueId: 'device-C' },
|
preferred: { name: 'Ignored', index: 99, uniqueId: 'device-C' },
|
||||||
}),
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const testNameMatch = () => {
|
const testNameMatch = () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [
|
available: [
|
||||||
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
],
|
],
|
||||||
preferred: { name: 'C', index: 99, uniqueId: 'ignored' },
|
preferred: { name: 'C', index: 99, uniqueId: 'ignored' },
|
||||||
}),
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -71,14 +83,17 @@ describe('"find best matching device" helpers', () => {
|
||||||
() => {
|
() => {
|
||||||
it('returns the first available device if the preferred device is not found', () => {
|
it('returns the first available device if the preferred device is not found', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [
|
available: [
|
||||||
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
],
|
],
|
||||||
preferred: { name: 'X', index: 123, uniqueId: 'Y' },
|
preferred: { name: 'X', index: 123, uniqueId: 'Y' },
|
||||||
}),
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -89,28 +104,64 @@ describe('"find best matching device" helpers', () => {
|
||||||
|
|
||||||
itReturnsTheFirstAvailableDeviceIfNoneIsPreferred();
|
itReturnsTheFirstAvailableDeviceIfNoneIsPreferred();
|
||||||
|
|
||||||
[0, 1].forEach(index => {
|
it('returns 0 if that was the previous preferred index (and a device is available)', () => {
|
||||||
it(`returns ${index} if that was the previous preferred index (and a device is available)`, () => {
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [
|
available: [
|
||||||
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
],
|
],
|
||||||
preferred: { name: 'C', index, uniqueId: 'device-C' },
|
preferred: { name: 'C', index: 0, uniqueId: 'device-C' },
|
||||||
}),
|
},
|
||||||
index
|
false
|
||||||
|
),
|
||||||
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('(windows) returns 1 if that was the previous preferred index (and a device is available)', () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
|
available: [
|
||||||
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
|
],
|
||||||
|
preferred: { name: 'C', index: 1, uniqueId: 'device-C' },
|
||||||
|
},
|
||||||
|
true
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('(non-windows) returns 2 if the preferred device was at index 1 and is now at index 2', () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
|
available: [
|
||||||
|
{ name: 'A', index: 123, uniqueId: 'device-A' },
|
||||||
|
{ name: 'B', index: 456, uniqueId: 'device-B' },
|
||||||
|
{ name: 'C', index: 789, uniqueId: 'device-C' },
|
||||||
|
],
|
||||||
|
preferred: { name: 'C', index: 1, uniqueId: 'device-C' },
|
||||||
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
|
2
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 0 if the previous preferred index was 1 but there's only 1 audio device", () => {
|
it("returns 0 if the previous preferred index was 1 but there's only 1 audio device", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
findBestMatchingAudioDeviceIndex({
|
findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
available: [{ name: 'A', index: 123, uniqueId: 'device-A' }],
|
available: [{ name: 'A', index: 123, uniqueId: 'device-A' }],
|
||||||
preferred: { name: 'C', index: 1, uniqueId: 'device-C' },
|
preferred: { name: 'C', index: 1, uniqueId: 'device-C' },
|
||||||
}),
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import { awaitObject } from '../../util/awaitObject';
|
import { awaitObject } from '../../util/awaitObject';
|
||||||
import { DurationInSeconds } from '../../util/durations';
|
import { DurationInSeconds } from '../../util/durations';
|
||||||
import { createSetting, createCallback } from '../../util/preload';
|
import { createSetting, createCallback } from '../../util/preload';
|
||||||
|
import { findBestMatchingAudioDeviceIndex } from '../../calling/findBestMatchingDevice';
|
||||||
|
|
||||||
function doneRendering() {
|
function doneRendering() {
|
||||||
ipcRenderer.send('settings-done-rendering');
|
ipcRenderer.send('settings-done-rendering');
|
||||||
|
@ -239,6 +240,30 @@ async function renderPreferences() {
|
||||||
const preferredSystemLocales =
|
const preferredSystemLocales =
|
||||||
MinimalSignalContext.getPreferredSystemLocales();
|
MinimalSignalContext.getPreferredSystemLocales();
|
||||||
|
|
||||||
|
const selectedMicIndex = findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
|
available: availableMicrophones,
|
||||||
|
preferred: selectedMicrophone,
|
||||||
|
},
|
||||||
|
OS.isWindows()
|
||||||
|
);
|
||||||
|
const recomputedSelectedMicrophone =
|
||||||
|
selectedMicIndex !== undefined
|
||||||
|
? availableMicrophones[selectedMicIndex]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex(
|
||||||
|
{
|
||||||
|
available: availableSpeakers,
|
||||||
|
preferred: selectedSpeaker,
|
||||||
|
},
|
||||||
|
OS.isWindows()
|
||||||
|
);
|
||||||
|
const recomputedSelectedSpeaker =
|
||||||
|
selectedSpeakerIndex !== undefined
|
||||||
|
? availableSpeakers[selectedSpeakerIndex]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
// Settings
|
// Settings
|
||||||
availableCameras,
|
availableCameras,
|
||||||
|
@ -279,8 +304,8 @@ async function renderPreferences() {
|
||||||
preferredSystemLocales,
|
preferredSystemLocales,
|
||||||
resolvedLocale,
|
resolvedLocale,
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
selectedMicrophone,
|
selectedMicrophone: recomputedSelectedMicrophone,
|
||||||
selectedSpeaker,
|
selectedSpeaker: recomputedSelectedSpeaker,
|
||||||
sentMediaQualitySetting,
|
sentMediaQualitySetting,
|
||||||
themeSetting,
|
themeSetting,
|
||||||
universalExpireTimer: DurationInSeconds.fromSeconds(universalExpireTimer),
|
universalExpireTimer: DurationInSeconds.fromSeconds(universalExpireTimer),
|
||||||
|
|
Loading…
Reference in a new issue