feat(electron): adapts to default audio input/output device changes (#11815)

Enable automatic adoption of new default audio devices upon detection.

1. add `AggregateDeviceManager` to watch for audio device changes, creating & maintaining `AggregateDevice` instances and cleaning up
2. use a workaround of `FastFixedIn` to deal with resampling delay issue (this is low quality and have some artifacts in the resampled audio)

fix AF-2536
This commit is contained in:
pengx17
2025-04-21 08:35:32 +00:00
parent f65c8f8fa6
commit 0442c0e2b6
9 changed files with 890 additions and 289 deletions
@@ -287,7 +287,7 @@ export function createRecording(status: RecordingStatus) {
app: status.app,
appGroup: status.appGroup,
file,
stream,
session: stream,
};
recordings.set(status.id, recording);
@@ -308,8 +308,8 @@ export async function getRecording(id: number) {
app: recording.app,
startTime: recording.startTime,
filepath: rawFilePath,
sampleRate: recording.stream.sampleRate,
numberOfChannels: recording.stream.channels,
sampleRate: recording.session.sampleRate,
numberOfChannels: recording.session.channels,
};
}
@@ -342,7 +342,7 @@ function setupRecordingListeners() {
} else if (status?.status === 'stopped') {
const recording = recordings.get(status.id);
if (recording) {
recording.stream.stop();
recording.session.stop();
}
} else if (
status?.status === 'create-block-success' ||
@@ -564,7 +564,7 @@ export async function stopRecording(id: number) {
return;
}
const { file, stream } = recording;
const { file, session: stream } = recording;
// First stop the audio stream to prevent more data coming in
try {
@@ -742,8 +742,8 @@ export function serializeRecordingStatus(
startTime: status.startTime,
filepath:
status.filepath ?? (recording ? String(recording.file.path) : undefined),
sampleRate: recording?.stream.sampleRate,
numberOfChannels: recording?.stream.channels,
sampleRate: recording?.session.sampleRate,
numberOfChannels: recording?.session.channels,
};
}
@@ -1,6 +1,6 @@
import type { WriteStream } from 'node:fs';
import type { AudioTapStream, TappableApplication } from '@affine/native';
import type { AudioCaptureSession, TappableApplication } from '@affine/native';
export interface TappableAppInfo {
rawInstance: TappableApplication;
@@ -27,7 +27,7 @@ export interface Recording {
appGroup?: AppGroupInfo;
// the buffered file that is being recorded streamed to
file: WriteStream;
stream: AudioTapStream;
session: AudioCaptureSession;
startTime: number;
filepath?: string; // the filepath of the recording (only available when status is ready)
}