diff --git a/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx b/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx index 46bd4c956..0da59afe9 100644 --- a/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx +++ b/packages/frontend/apps/electron-renderer/src/popup/recording/index.tsx @@ -148,6 +148,7 @@ export function Recording() { ); useEffect(() => { + let removed = false; let currentStreamEncoder: OpusStreamEncoder | undefined; apis?.recording @@ -161,6 +162,9 @@ export function Recording() { .catch(console.error); const handleRecordingStatusChanged = async (status: Status) => { + if (removed) { + return; + } if (status?.status === 'new') { track.popup.$.recordingBar.toggleRecordingBar({ type: 'Meeting record', @@ -196,6 +200,7 @@ export function Recording() { }); return () => { + removed = true; unsubscribe?.(); currentStreamEncoder?.close(); }; diff --git a/packages/frontend/apps/electron/src/main/handlers.ts b/packages/frontend/apps/electron/src/main/handlers.ts index 08a337862..bc63e4588 100644 --- a/packages/frontend/apps/electron/src/main/handlers.ts +++ b/packages/frontend/apps/electron/src/main/handlers.ts @@ -80,7 +80,12 @@ export const registerHandlers = () => { }; 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[]) => { diff --git a/packages/frontend/apps/electron/src/main/recording/feature.ts b/packages/frontend/apps/electron/src/main/recording/feature.ts index 901478548..d5926cd71 100644 --- a/packages/frontend/apps/electron/src/main/recording/feature.ts +++ b/packages/frontend/apps/electron/src/main/recording/feature.ts @@ -520,10 +520,13 @@ export function newRecording( export function startRecording( appGroup?: AppGroupInfo | number ): RecordingStatus | null { - const state = recordingStateMachine.dispatch({ - type: 'START_RECORDING', - appGroup: normalizeAppGroupInfo(appGroup), - }); + const state = recordingStateMachine.dispatch( + { + type: 'START_RECORDING', + appGroup: normalizeAppGroupInfo(appGroup), + }, + false + ); if (state?.status === 'recording') { createRecording(state); @@ -542,6 +545,8 @@ export function startRecording( } }, MAX_DURATION_FOR_TRANSCRIPTION); + recordingStateMachine.status$.next(state); + return state; } @@ -654,6 +659,8 @@ export async function getRawAudioBuffers( } export async function readyRecording(id: number, buffer: Buffer) { + logger.info('readyRecording', id); + const recordingStatus = recordingStatus$.value; const recording = recordings.get(id); if (!recordingStatus || recordingStatus.id !== id || !recording) { diff --git a/packages/frontend/apps/electron/src/main/recording/state-machine.ts b/packages/frontend/apps/electron/src/main/recording/state-machine.ts index 148c2cbb2..25a635c82 100644 --- a/packages/frontend/apps/electron/src/main/recording/state-machine.ts +++ b/packages/frontend/apps/electron/src/main/recording/state-machine.ts @@ -63,7 +63,7 @@ export class RecordingStateMachine { * @param event The event to dispatch * @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; let newStatus: RecordingStatus | null = null; @@ -105,7 +105,9 @@ export class RecordingStateMachine { return currentStatus; } - this.recordingStatus$.next(newStatus); + if (emit) { + this.recordingStatus$.next(newStatus); + } return newStatus; } diff --git a/packages/frontend/apps/electron/src/main/windows-manager/popup.ts b/packages/frontend/apps/electron/src/main/windows-manager/popup.ts index 6bf4f5726..c724a397b 100644 --- a/packages/frontend/apps/electron/src/main/windows-manager/popup.ts +++ b/packages/frontend/apps/electron/src/main/windows-manager/popup.ts @@ -61,10 +61,7 @@ abstract class PopupWindow { abstract windowOptions: Partial; - resolveReady: () => void = () => {}; - ready = new Promise(resolve => { - this.resolveReady = resolve; - }); + ready = Promise.withResolvers(); private readonly showing$ = new BehaviorSubject(false); @@ -109,12 +106,12 @@ abstract class PopupWindow { visibleOnFullScreen: true, }); - browserWindow.loadURL(popupViewUrl).catch(err => logger.error(err)); - browserWindow.on('ready-to-show', () => { - browserWindow.webContents.on('did-finish-load', () => { - this.resolveReady(); - }); + logger.info('loading popup', this.name, popupViewUrl); + browserWindow.webContents.on('did-finish-load', () => { + this.ready.resolve(); + logger.info('popup ready', this.name); }); + browserWindow.loadURL(popupViewUrl).catch(err => logger.error(err)); return browserWindow; } @@ -126,7 +123,7 @@ abstract class PopupWindow { const workArea = getCurrentDisplay(browserWindow).workArea; const popupSize = browserWindow.getSize(); - await this.ready; + await this.ready.promise; this.showing$.next(true); @@ -141,6 +138,8 @@ abstract class PopupWindow { // Set initial position browserWindow.setPosition(startX, y); + logger.info('showing popup', this.name); + // First fade in, then slide await Promise.all([ // Slide in animation @@ -169,6 +168,7 @@ abstract class PopupWindow { if (!this.browserWindow) { return; } + logger.info('hiding popup', this.name); this.showing$.next(false); await animate(this.browserWindow.getOpacity(), 0, opacity => { this.browserWindow?.setOpacity(opacity); diff --git a/packages/frontend/core/src/utils/webm-encoding.ts b/packages/frontend/core/src/utils/webm-encoding.ts index 5863fe172..cedc294ff 100644 --- a/packages/frontend/core/src/utils/webm-encoding.ts +++ b/packages/frontend/core/src/utils/webm-encoding.ts @@ -423,9 +423,12 @@ export const createStreamEncoder = ( let isClosed = false; const next = async () => { - if (!apis || isClosed) { + if (!apis) { throw new Error('Electron API is not available'); } + if (isClosed) { + return; + } const { buffer, nextCursor } = await apis.recording.getRawAudioBuffers( recordingId, cursor