feat(native): record encoding (#14188)

fix #13784 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Start/stop system or meeting recordings with Ogg/Opus artifacts and
native start/stop APIs; workspace backup recovery.

* **Refactor**
* Simplified recording lifecycle and UI flows; native runtime now
orchestrates recording/processing and reporting.

* **Bug Fixes**
* Stronger path validation, safer import/export dialogs, consistent
error handling/logging, and retry-safe recording processing.

* **Chores**
* Added cross-platform native audio capture and Ogg/Opus encoding
support.

* **Tests**
* New unit, integration, and e2e tests for recording, path guards,
dialogs, and workspace recovery.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-03-22 02:50:14 +08:00
committed by GitHub
parent 6a93566422
commit bcf2a51d41
44 changed files with 2921 additions and 1143 deletions
+30 -11
View File
@@ -14,6 +14,7 @@ import {
import { createLocalWorkspace } from '@affine-test/kit/utils/workspace';
import { expect } from '@playwright/test';
import fs from 'fs-extra';
import type { ElectronApplication } from 'playwright';
declare global {
interface Window {
@@ -21,6 +22,32 @@ declare global {
}
}
async function mockNextSaveDialog(
electronApp: ElectronApplication,
filePath: string
) {
await electronApp.evaluate(({ dialog }, mockedFilePath) => {
const original = dialog.showSaveDialog.bind(dialog);
dialog.showSaveDialog = async () => {
dialog.showSaveDialog = original;
return { canceled: false, filePath: mockedFilePath };
};
}, filePath);
}
async function mockNextOpenDialog(
electronApp: ElectronApplication,
filePath: string
) {
await electronApp.evaluate(({ dialog }, mockedFilePath) => {
const original = dialog.showOpenDialog.bind(dialog);
dialog.showOpenDialog = async () => {
dialog.showOpenDialog = original;
return { canceled: false, filePaths: [mockedFilePath] };
};
}, filePath);
}
test('check workspace has a DB file', async ({ appInfo, workspace }) => {
const w = await workspace.current();
const dbPath = path.join(
@@ -34,7 +61,7 @@ test('check workspace has a DB file', async ({ appInfo, workspace }) => {
expect(await fs.exists(dbPath)).toBe(true);
});
test('export then add', async ({ page, appInfo, workspace }) => {
test('export then add', async ({ electronApp, page, appInfo, workspace }) => {
await clickNewPageButton(page);
const w = await workspace.current();
@@ -60,11 +87,7 @@ test('export then add', async ({ page, appInfo, workspace }) => {
const tmpPath = path.join(appInfo.sessionData, w.meta.id + '-tmp.db');
// export db file to tmp folder
await page.evaluate(tmpPath => {
return window.__apis?.dialog.setFakeDialogResult({
filePath: tmpPath,
});
}, tmpPath);
await mockNextSaveDialog(electronApp, tmpPath);
await page.getByTestId('workspace-setting:storage').click();
await page.getByTestId('export-affine-backup').click();
@@ -78,11 +101,7 @@ test('export then add', async ({ page, appInfo, workspace }) => {
// in the codebase
await clickSideBarCurrentWorkspaceBanner(page);
await page.evaluate(tmpPath => {
return window.__apis?.dialog.setFakeDialogResult({
filePath: tmpPath,
});
}, tmpPath);
await mockNextOpenDialog(electronApp, tmpPath);
// load the db file
await page.getByTestId('add-workspace').click();