mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-22 19:53:48 +08:00
fix(electron): streaming audio encoding not working (#12231)
fix AF-2612 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved handling of recording status updates to prevent unwanted side effects after closing the recording popup. - Enhanced error handling and logging for internal communication, ensuring errors are logged and do not disrupt the user experience. - Refined control flow for stream encoding to avoid unnecessary errors when encoders are closed. - **Refactor** - Updated popup window readiness and lifecycle management for more reliable behavior and clearer logging. - Adjusted recording state management to allow more precise control over status updates and emissions. - **Style** - Added additional logging for popup and recording operations to aid in troubleshooting and transparency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -148,6 +148,7 @@ export function Recording() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let removed = false;
|
||||||
let currentStreamEncoder: OpusStreamEncoder | undefined;
|
let currentStreamEncoder: OpusStreamEncoder | undefined;
|
||||||
|
|
||||||
apis?.recording
|
apis?.recording
|
||||||
@@ -161,6 +162,9 @@ export function Recording() {
|
|||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
||||||
const handleRecordingStatusChanged = async (status: Status) => {
|
const handleRecordingStatusChanged = async (status: Status) => {
|
||||||
|
if (removed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (status?.status === 'new') {
|
if (status?.status === 'new') {
|
||||||
track.popup.$.recordingBar.toggleRecordingBar({
|
track.popup.$.recordingBar.toggleRecordingBar({
|
||||||
type: 'Meeting record',
|
type: 'Meeting record',
|
||||||
@@ -196,6 +200,7 @@ export function Recording() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
removed = true;
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
currentStreamEncoder?.close();
|
currentStreamEncoder?.close();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,7 +80,12 @@ export const registerHandlers = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ipcMain.handle(AFFINE_API_CHANNEL_NAME, async (e, ...args: any[]) => {
|
ipcMain.handle(AFFINE_API_CHANNEL_NAME, async (e, ...args: any[]) => {
|
||||||
return handleIpcMessage(e, ...args);
|
try {
|
||||||
|
return await handleIpcMessage(e, ...args);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`error in ipc handler when calling ${args[0]}`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(AFFINE_API_CHANNEL_NAME, (e, ...args: any[]) => {
|
ipcMain.on(AFFINE_API_CHANNEL_NAME, (e, ...args: any[]) => {
|
||||||
|
|||||||
@@ -520,10 +520,13 @@ export function newRecording(
|
|||||||
export function startRecording(
|
export function startRecording(
|
||||||
appGroup?: AppGroupInfo | number
|
appGroup?: AppGroupInfo | number
|
||||||
): RecordingStatus | null {
|
): RecordingStatus | null {
|
||||||
const state = recordingStateMachine.dispatch({
|
const state = recordingStateMachine.dispatch(
|
||||||
type: 'START_RECORDING',
|
{
|
||||||
appGroup: normalizeAppGroupInfo(appGroup),
|
type: 'START_RECORDING',
|
||||||
});
|
appGroup: normalizeAppGroupInfo(appGroup),
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (state?.status === 'recording') {
|
if (state?.status === 'recording') {
|
||||||
createRecording(state);
|
createRecording(state);
|
||||||
@@ -542,6 +545,8 @@ export function startRecording(
|
|||||||
}
|
}
|
||||||
}, MAX_DURATION_FOR_TRANSCRIPTION);
|
}, MAX_DURATION_FOR_TRANSCRIPTION);
|
||||||
|
|
||||||
|
recordingStateMachine.status$.next(state);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,6 +659,8 @@ export async function getRawAudioBuffers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function readyRecording(id: number, buffer: Buffer) {
|
export async function readyRecording(id: number, buffer: Buffer) {
|
||||||
|
logger.info('readyRecording', id);
|
||||||
|
|
||||||
const recordingStatus = recordingStatus$.value;
|
const recordingStatus = recordingStatus$.value;
|
||||||
const recording = recordings.get(id);
|
const recording = recordings.get(id);
|
||||||
if (!recordingStatus || recordingStatus.id !== id || !recording) {
|
if (!recordingStatus || recordingStatus.id !== id || !recording) {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export class RecordingStateMachine {
|
|||||||
* @param event The event to dispatch
|
* @param event The event to dispatch
|
||||||
* @returns The new recording status after the event is processed
|
* @returns The new recording status after the event is processed
|
||||||
*/
|
*/
|
||||||
dispatch(event: RecordingEvent): RecordingStatus | null {
|
dispatch(event: RecordingEvent, emit = true): RecordingStatus | null {
|
||||||
const currentStatus = this.recordingStatus$.value;
|
const currentStatus = this.recordingStatus$.value;
|
||||||
let newStatus: RecordingStatus | null = null;
|
let newStatus: RecordingStatus | null = null;
|
||||||
|
|
||||||
@@ -105,7 +105,9 @@ export class RecordingStateMachine {
|
|||||||
return currentStatus;
|
return currentStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recordingStatus$.next(newStatus);
|
if (emit) {
|
||||||
|
this.recordingStatus$.next(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
return newStatus;
|
return newStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,7 @@ abstract class PopupWindow {
|
|||||||
|
|
||||||
abstract windowOptions: Partial<BrowserWindowConstructorOptions>;
|
abstract windowOptions: Partial<BrowserWindowConstructorOptions>;
|
||||||
|
|
||||||
resolveReady: () => void = () => {};
|
ready = Promise.withResolvers<void>();
|
||||||
ready = new Promise<void>(resolve => {
|
|
||||||
this.resolveReady = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
private readonly showing$ = new BehaviorSubject<boolean>(false);
|
private readonly showing$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
@@ -109,12 +106,12 @@ abstract class PopupWindow {
|
|||||||
visibleOnFullScreen: true,
|
visibleOnFullScreen: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
browserWindow.loadURL(popupViewUrl).catch(err => logger.error(err));
|
logger.info('loading popup', this.name, popupViewUrl);
|
||||||
browserWindow.on('ready-to-show', () => {
|
browserWindow.webContents.on('did-finish-load', () => {
|
||||||
browserWindow.webContents.on('did-finish-load', () => {
|
this.ready.resolve();
|
||||||
this.resolveReady();
|
logger.info('popup ready', this.name);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
browserWindow.loadURL(popupViewUrl).catch(err => logger.error(err));
|
||||||
return browserWindow;
|
return browserWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ abstract class PopupWindow {
|
|||||||
const workArea = getCurrentDisplay(browserWindow).workArea;
|
const workArea = getCurrentDisplay(browserWindow).workArea;
|
||||||
const popupSize = browserWindow.getSize();
|
const popupSize = browserWindow.getSize();
|
||||||
|
|
||||||
await this.ready;
|
await this.ready.promise;
|
||||||
|
|
||||||
this.showing$.next(true);
|
this.showing$.next(true);
|
||||||
|
|
||||||
@@ -141,6 +138,8 @@ abstract class PopupWindow {
|
|||||||
// Set initial position
|
// Set initial position
|
||||||
browserWindow.setPosition(startX, y);
|
browserWindow.setPosition(startX, y);
|
||||||
|
|
||||||
|
logger.info('showing popup', this.name);
|
||||||
|
|
||||||
// First fade in, then slide
|
// First fade in, then slide
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Slide in animation
|
// Slide in animation
|
||||||
@@ -169,6 +168,7 @@ abstract class PopupWindow {
|
|||||||
if (!this.browserWindow) {
|
if (!this.browserWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.info('hiding popup', this.name);
|
||||||
this.showing$.next(false);
|
this.showing$.next(false);
|
||||||
await animate(this.browserWindow.getOpacity(), 0, opacity => {
|
await animate(this.browserWindow.getOpacity(), 0, opacity => {
|
||||||
this.browserWindow?.setOpacity(opacity);
|
this.browserWindow?.setOpacity(opacity);
|
||||||
|
|||||||
@@ -423,9 +423,12 @@ export const createStreamEncoder = (
|
|||||||
let isClosed = false;
|
let isClosed = false;
|
||||||
|
|
||||||
const next = async () => {
|
const next = async () => {
|
||||||
if (!apis || isClosed) {
|
if (!apis) {
|
||||||
throw new Error('Electron API is not available');
|
throw new Error('Electron API is not available');
|
||||||
}
|
}
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { buffer, nextCursor } = await apis.recording.getRawAudioBuffers(
|
const { buffer, nextCursor } = await apis.recording.getRawAudioBuffers(
|
||||||
recordingId,
|
recordingId,
|
||||||
cursor
|
cursor
|
||||||
|
|||||||
Reference in New Issue
Block a user