Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions e2e/tools/video-recorder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import { test, expect } from '@playwright/test';

// Drive the real getUserMedia + MediaRecorder flow with Chromium's synthetic
// camera/mic so no hardware is needed.
// Drive the real getUserMedia + canvas + MediaRecorder pipeline with Chromium's
// synthetic camera/mic so no hardware is needed.
test.use({
permissions: ['camera', 'microphone'],
launchOptions: {
args: ['--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream'],
},
});

test('records from the (fake) webcam and offers a download', async ({ page }) => {
test('records the effects canvas from the (fake) webcam and offers a download', async ({ page }) => {
await page.goto('/tools/video-recorder');
await page.waitForLoadState('networkidle').catch(() => {});

// Start the camera; the Record button appears once the stream is live.
const start = page.getByRole('button', { name: 'Start camera' });
const record = page.getByRole('button', { name: 'Record', exact: true });
await expect(async () => {
await start.click();
await expect(record).toBeVisible({ timeout: 3000 });
}).toPass({ timeout: 30_000 });

// Record a short clip.
// Apply a colour filter — exercises the canvas-composited pipeline (no model).
const effectSelect = page.locator('select:has(option[value="greenscreen"])');
await effectSelect.selectOption('filter');

await record.click();
await expect(page.getByRole('button', { name: 'Stop' })).toBeVisible();
await page.waitForTimeout(1200);
await page.getByRole('button', { name: 'Stop' }).click();

// Stopping yields a downloadable result.
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('button', { name: 'Record again' })).toBeVisible();
});
13 changes: 12 additions & 1 deletion scripts/stage-models.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,18 @@ async function stageMediapipe() {
writeFileSync(model, Buffer.from(await res.arrayBuffer()));
console.log('Downloaded face-detection model.');
}
console.log(`Staged Face-Blur assets → ${DEST}`);

// Selfie segmenter for the Video Recorder's real-time background blur/replace.
const seg = `${DEST}/selfie_segmenter.tflite`;
if (!existsSync(seg)) {
const url =
'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite';
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to download selfie segmenter: ${res.status}`);
writeFileSync(seg, Buffer.from(await res.arrayBuffer()));
console.log('Downloaded selfie-segmenter model.');
}
console.log(`Staged Face-Blur + segmenter assets → ${DEST}`);
}

function stageUpscaler() {
Expand Down
Loading
Loading