Skip to content
This repository was archived by the owner on Jul 2, 2021. It is now read-only.

Refactor #8

Merged
merged 10 commits into from
Mar 3, 2020
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ npm i playwright playwright-video

```js
const { chromium, devices } = require('playwright');
const { VideoCapture } = require('playwright-video');
const { PageVideoCapture } = require('playwright-video');

(async () => {
const iPhone = devices['iPhone 6'];
Expand All @@ -33,7 +33,7 @@ const { VideoCapture } = require('playwright-video');

const page = await context.newPage();

await VideoCapture.start({
await PageVideoCapture.start({
browser,
page,
savePath: '/tmp/video.mp4',
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"tslib": "^1.11.1"
},
"devDependencies": {
"@types/debug": "^4.1.5",
"@types/fluent-ffmpeg": "^2.1.14",
"@types/fs-extra": "^8.1.0",
"@types/jest": "^25.1.3",
Expand Down
85 changes: 85 additions & 0 deletions src/PageVideoCapture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Debug from 'debug';
import { Page } from 'playwright-core';
import { CRBrowser } from 'playwright-core/lib/chromium/crBrowser';
import { ScreencastFrameCollector } from './ScreencastFrameCollector';
import { VideoFrameBuilder } from './VideoFrameBuilder';
import { VideoWriter } from './VideoWriter';

const debug = Debug('playwright-video:PageVideoCapture');

interface ConstructorArgs {
collector: ScreencastFrameCollector;
page: Page;
writer: VideoWriter;
}

interface CreateArgs {
browser: CRBrowser;
page: Page;
savePath: string;
}

export class PageVideoCapture {
public static async start({
browser,
page,
savePath,
}: CreateArgs): Promise<PageVideoCapture> {
debug('start');

const collector = await ScreencastFrameCollector.create({ browser, page });
const writer = await VideoWriter.create(savePath);

const capture = new PageVideoCapture({ collector, page, writer });
await collector.start();

return capture;
}

private _collector: ScreencastFrameCollector;
private _frameBuilder: VideoFrameBuilder = new VideoFrameBuilder();
// public for tests
public _stopped = false;
private _writer: VideoWriter;

protected constructor({ collector, page, writer }: ConstructorArgs) {
this._collector = collector;
this._writer = writer;

this._writer.on('ffmpegerror', () => {
debug('stop due to ffmpeg error');
this.stop();
});
page.on('close', () => this.stop());

this._listenForFrames();
}

private _listenForFrames(): void {
this._collector.on('screencastframe', screencastFrame => {
debug(`received frame: ${screencastFrame.timestamp}`);
const videoFrames = this._frameBuilder.buildVideoFrames(screencastFrame);

this._writer.write(videoFrames);
});
}

private _writeLastFrame(): void {
debug('write last frame');

const videoFrames = this._frameBuilder.buildVideoFrames();
this._writer.write(videoFrames);
}

public async stop(): Promise<void> {
if (this._stopped) return;

debug('stop');
this._stopped = true;

await this._collector.stop();
this._writeLastFrame();

return this._writer.stop();
}
}
86 changes: 86 additions & 0 deletions src/ScreencastFrameCollector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Debug from 'debug';
import { EventEmitter } from 'events';
import { Page } from 'playwright-core';
import { CRBrowser } from 'playwright-core/lib/chromium/crBrowser';
import { CRSession } from 'playwright-core/lib/chromium/crConnection';
import { ensureBrowserType } from './utils';

const debug = Debug('playwright-video:FrameCollector');

interface ConstructorArgs {
browser: CRBrowser;
page: Page;
}

export class ScreencastFrameCollector extends EventEmitter {
public static async create(
args: ConstructorArgs,
): Promise<ScreencastFrameCollector> {
ensureBrowserType(args.browser);

const collector = new ScreencastFrameCollector(args);

await collector._buildClient(args.browser);
collector._listenForFrames();

return collector;
}

// public for tests
public _client: CRSession;
private _page: Page;
private _stopped = false;

protected constructor({ page }: ConstructorArgs) {
super();
this._page = page;
}

private async _buildClient(browser: CRBrowser): Promise<void> {
this._client = await browser.pageTarget(this._page).createCDPSession();
}

private _listenForFrames(): void {
this._client.on('Page.screencastFrame', payload => {
debug(`received frame with timestamp ${payload.metadata.timestamp}`);

this._client.send('Page.screencastFrameAck', {
sessionId: payload.sessionId,
});

if (!payload.metadata.timestamp) {
debug('skip frame without timestamp');
return;
}

this.emit('screencastframe', {
data: Buffer.from(payload.data, 'base64'),
received: Date.now(),
timestamp: payload.metadata.timestamp,
});
});
}

public async start(): Promise<void> {
debug('start');

await this._client.send('Page.startScreencast', {
everyNthFrame: 1,
});
}

public async stop(): Promise<void> {
if (this._stopped) return;

debug('stop');
this._stopped = true;
// Screencast API takes time to send frames
// Wait 1s for frames to arrive
// TODO figure out a better pattern for this
await new Promise(resolve => setTimeout(resolve, 1000));

if (this._client._connection) {
await this._client.detach();
}
}
}
163 changes: 0 additions & 163 deletions src/VideoCapture.ts

This file was deleted.

Loading