diff --git a/.gitignore b/.gitignore index 08b35db..8c04ad1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,6 @@ node_modules/ # Coverage coverage -# Transpiled files -build/ - # VS Code .vscode !.vscode/tasks.js @@ -26,4 +23,4 @@ build/ .eslintcache # Misc -.DS_Store \ No newline at end of file +.DS_Store diff --git a/build/PageVideoCapture.d.ts b/build/PageVideoCapture.d.ts new file mode 100644 index 0000000..0cc0328 --- /dev/null +++ b/build/PageVideoCapture.d.ts @@ -0,0 +1,33 @@ +import { Page } from 'playwright-core'; +import { SortedFrameQueue } from './SortedFrameQueue'; +import { ScreencastFrameCollector } from './ScreencastFrameCollector'; +import { VideoWriter } from './VideoWriter'; +export interface CaptureOptions { + followPopups: boolean; + fps?: number; +} +interface ConstructorArgs { + collector: ScreencastFrameCollector; + queue: SortedFrameQueue; + page: Page; + writer: VideoWriter; +} +interface StartArgs { + page: Page; + savePath: string; + options?: CaptureOptions; +} +export declare class PageVideoCapture { + static start({ page, savePath, options, }: StartArgs): Promise; + _collector: ScreencastFrameCollector; + private _previousFrame?; + private _queue; + _stopped: boolean; + private _writer; + protected constructor({ collector, queue, page, writer }: ConstructorArgs); + private _listenForFrames; + private _writePreviousFrame; + private _writeFinalFrameUpToTimestamp; + stop(): Promise; +} +export {}; diff --git a/build/PageVideoCapture.js b/build/PageVideoCapture.js new file mode 100644 index 0000000..3ad4d49 --- /dev/null +++ b/build/PageVideoCapture.js @@ -0,0 +1,73 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PageVideoCapture = void 0; +const debug_1 = __importDefault(require("debug")); +const SortedFrameQueue_1 = require("./SortedFrameQueue"); +const ScreencastFrameCollector_1 = require("./ScreencastFrameCollector"); +const VideoWriter_1 = require("./VideoWriter"); +const debug = debug_1.default('pw-video:PageVideoCapture'); +class PageVideoCapture { + constructor({ collector, queue, page, writer }) { + // public for tests + this._stopped = false; + this._collector = collector; + this._queue = queue; + this._writer = writer; + this._writer.on('ffmpegerror', (error) => { + debug(`stop due to ffmpeg error "${error.trim()}"`); + this.stop(); + }); + page.on('close', () => this.stop()); + this._listenForFrames(); + } + static async start({ page, savePath, options, }) { + debug('start'); + const collector = await ScreencastFrameCollector_1.ScreencastFrameCollector.create(page, options); + const queue = new SortedFrameQueue_1.SortedFrameQueue(); + const writer = await VideoWriter_1.VideoWriter.create(savePath, options); + const capture = new PageVideoCapture({ collector, queue, page, writer }); + await collector.start(); + return capture; + } + _listenForFrames() { + this._collector.on('screencastframe', (screencastFrame) => { + debug(`collected frame from screencast: ${screencastFrame.timestamp}`); + this._queue.insert(screencastFrame); + }); + this._queue.on('sortedframes', (frames) => { + debug(`received ${frames.length} frames from queue`); + frames.forEach((frame) => this._writePreviousFrame(frame)); + }); + } + _writePreviousFrame(currentFrame) { + // write the previous frame based on the duration between it and the current frame + if (this._previousFrame) { + const durationSeconds = currentFrame.timestamp - this._previousFrame.timestamp; + this._writer.write(this._previousFrame.data, durationSeconds); + } + this._previousFrame = currentFrame; + } + _writeFinalFrameUpToTimestamp(stoppedTimestamp) { + if (!this._previousFrame) + return; + // write the final frame based on the duration between it and when the screencast was stopped + debug('write final frame'); + const durationSeconds = stoppedTimestamp - this._previousFrame.timestamp; + this._writer.write(this._previousFrame.data, durationSeconds); + } + async stop() { + if (this._stopped) + return; + debug('stop'); + this._stopped = true; + const stoppedTimestamp = await this._collector.stop(); + this._queue.drain(); + this._writeFinalFrameUpToTimestamp(stoppedTimestamp); + return this._writer.stop(); + } +} +exports.PageVideoCapture = PageVideoCapture; +//# sourceMappingURL=PageVideoCapture.js.map \ No newline at end of file diff --git a/build/PageVideoCapture.js.map b/build/PageVideoCapture.js.map new file mode 100644 index 0000000..e76f21b --- /dev/null +++ b/build/PageVideoCapture.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PageVideoCapture.js","sourceRoot":"","sources":["../src/PageVideoCapture.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/ScreencastFrameCollector.d.ts b/build/ScreencastFrameCollector.d.ts new file mode 100644 index 0000000..56bf07c --- /dev/null +++ b/build/ScreencastFrameCollector.d.ts @@ -0,0 +1,28 @@ +/// +import { EventEmitter } from 'events'; +import { CDPSession, Page } from 'playwright-core'; +import { CaptureOptions } from './PageVideoCapture'; +export interface ScreencastFrame { + data: Buffer; + received: number; + timestamp: number; +} +export declare class ScreencastFrameCollector extends EventEmitter { + static create(originalPage: Page, options?: CaptureOptions): Promise; + _clients: [CDPSession?]; + private _originalPage; + private _stoppedTimestamp; + private _endedPromise; + _followPopups: boolean; + protected constructor(page: Page, options: CaptureOptions); + private _popupFollower; + private _installPopupFollower; + private _uninstallPopupFollower; + private _buildClient; + private _getActiveClient; + private _listenForFrames; + private _activatePage; + private _deactivatePage; + start(): Promise; + stop(): Promise; +} diff --git a/build/ScreencastFrameCollector.js b/build/ScreencastFrameCollector.js new file mode 100644 index 0000000..4d43bab --- /dev/null +++ b/build/ScreencastFrameCollector.js @@ -0,0 +1,161 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ScreencastFrameCollector = void 0; +const debug_1 = __importDefault(require("debug")); +const events_1 = require("events"); +const utils_1 = require("./utils"); +const debug = debug_1.default('pw-video:ScreencastFrameCollector'); +class ScreencastFrameCollector extends events_1.EventEmitter { + constructor(page, options) { + super(); + this._originalPage = page; + this._clients = []; + this._followPopups = options ? options.followPopups : false; + this._popupFollower = this._popupFollower.bind(this); + } + static async create(originalPage, options) { + utils_1.ensurePageType(originalPage); + const collector = new ScreencastFrameCollector(originalPage, options); + return collector; + } + async _popupFollower(popup) { + await this._activatePage(popup); + // for tests + this.emit('popupFollowed'); + popup.once('close', async () => { + await this._deactivatePage(popup); + }); + } + _installPopupFollower(page) { + page.on('popup', this._popupFollower); + } + _uninstallPopupFollower(page) { + page.off('popup', this._popupFollower); + } + async _buildClient(page) { + const context = page.context(); + const client = await context.newCDPSession(page); + return client; + } + _getActiveClient() { + return this._clients[this._clients.length - 1]; + } + _listenForFrames(client) { + this._endedPromise = new Promise((resolve) => { + client.on('Page.screencastFrame', async (payload) => { + if (!payload.metadata.timestamp) { + debug('skipping frame without timestamp'); + return; + } + if (this._stoppedTimestamp && payload.metadata.timestamp > this._stoppedTimestamp) { + debug('all frames received'); + resolve(); + return; + } + debug(`received frame with timestamp ${payload.metadata.timestamp}`); + const ackPromise = client.send('Page.screencastFrameAck', { + sessionId: payload.sessionId, + }); + this.emit('screencastframe', { + data: Buffer.from(payload.data, 'base64'), + received: Date.now(), + timestamp: payload.metadata.timestamp, + }); + try { + // capture error so it does not propagate to the user + // most likely it is due to the active page closing + await ackPromise; + } + catch (e) { + debug('error sending screencastFrameAck %j', e.message); + } + }); + }); + } + async _activatePage(page) { + debug('activating page: ', page.url()); + let client; + try { + client = await this._buildClient(page); + } + catch (e) { + // capture error so it does not propagate to the user + // this is most likely due to the page not being open + // long enough to attach the CDP session + debug('error building client %j', e.message); + return; + } + const previousClient = this._getActiveClient(); + if (previousClient) { + await previousClient.send('Page.stopScreencast'); + } + this._clients.push(client); + this._listenForFrames(client); + try { + await client.send('Page.startScreencast', { + everyNthFrame: 1, + }); + } + catch (e) { + // capture error so it does not propagate to the user + // this is most likely due to the page not being open + // long enough to start recording after attaching the CDP session + this._deactivatePage(page); + debug('error activating page %j', e.message); + } + } + async _deactivatePage(page) { + debug('deactivating page: ', page.url()); + this._clients.pop(); + const previousClient = this._getActiveClient(); + try { + // capture error so it does not propagate to the user + // most likely it is due to the original page closing + await previousClient.send('Page.startScreencast', { + everyNthFrame: 1, + }); + } + catch (e) { + debug('error reactivating previous page %j', e.message); + } + } + async start() { + debug('start'); + await this._activatePage(this._originalPage); + if (this._followPopups) { + this._installPopupFollower(this._originalPage); + } + } + async stop() { + if (this._stoppedTimestamp) { + throw new Error('pw-video: Cannot call stop twice on the same capture.'); + } + if (this._followPopups) { + this._uninstallPopupFollower(this._originalPage); + } + this._stoppedTimestamp = Date.now() / 1000; + debug(`stopping screencast at ${this._stoppedTimestamp}`); + // Make sure stopping takes no longer than 1s in cases when the screencast API + // doesn't emit frames all the way up to the stopped timestamp. + await Promise.race([ + this._endedPromise, + new Promise((resolve) => setTimeout(resolve, 1000)), + ]); + try { + debug('detaching client'); + for (const client of this._clients) { + await client.detach(); + } + } + catch (e) { + debug('error detaching client', e.message); + } + debug('stopped'); + return this._stoppedTimestamp; + } +} +exports.ScreencastFrameCollector = ScreencastFrameCollector; +//# sourceMappingURL=ScreencastFrameCollector.js.map \ No newline at end of file diff --git a/build/ScreencastFrameCollector.js.map b/build/ScreencastFrameCollector.js.map new file mode 100644 index 0000000..215f87e --- /dev/null +++ b/build/ScreencastFrameCollector.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ScreencastFrameCollector.js","sourceRoot":"","sources":["../src/ScreencastFrameCollector.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/SortedFrameQueue.d.ts b/build/SortedFrameQueue.d.ts new file mode 100644 index 0000000..ee7f3b9 --- /dev/null +++ b/build/SortedFrameQueue.d.ts @@ -0,0 +1,12 @@ +/// +import { EventEmitter } from 'events'; +import { ScreencastFrame } from './ScreencastFrameCollector'; +export declare class SortedFrameQueue extends EventEmitter { + _frames: any[]; + private _size; + constructor(size?: number); + private _findInsertionIndex; + private _emitFrames; + insert(frame: ScreencastFrame): void; + drain(): void; +} diff --git a/build/SortedFrameQueue.js b/build/SortedFrameQueue.js new file mode 100644 index 0000000..5a6590a --- /dev/null +++ b/build/SortedFrameQueue.js @@ -0,0 +1,69 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SortedFrameQueue = void 0; +const debug_1 = __importDefault(require("debug")); +const events_1 = require("events"); +const debug = debug_1.default('pw-video:SortedFrameQueue'); +// Frames are sorted as they're inserted into the queue. This allows us +// to preserve frames that are sent out of order from CDP instead of discarding them. +// When the queue is full, half of the frames are emitted for processing. +// When we're done working with the queue, we can drain the remaining frames. +class SortedFrameQueue extends events_1.EventEmitter { + constructor(size) { + super(); + // public for tests + this._frames = []; + this._size = 40; + if (size) { + this._size = size; + } + } + _findInsertionIndex(timestamp) { + if (this._frames.length === 0) { + return 0; + } + let i; + let frame; + for (i = this._frames.length - 1; i >= 0; i--) { + frame = this._frames[i]; + if (timestamp > frame.timestamp) { + break; + } + } + return i + 1; + } + _emitFrames(frames) { + debug(`emitting ${frames.length} frames`); + this.emit('sortedframes', frames); + } + insert(frame) { + // If the queue is already full, send half of the frames for processing first + if (this._frames.length === this._size) { + const numberOfFramesToSplice = Math.floor(this._size / 2); + const framesToProcess = this._frames.splice(0, numberOfFramesToSplice); + this._emitFrames(framesToProcess); + } + const insertionIndex = this._findInsertionIndex(frame.timestamp); + if (insertionIndex === this._frames.length) { + debug(`inserting frame into queue at end: ${frame.timestamp}`); + // If this frame is in order, push it + this._frames.push(frame); + } + else { + debug(`inserting frame into queue at index ${insertionIndex}: ${frame.timestamp}`); + // If this frame is out of order, splice it in + this._frames.splice(insertionIndex, 0, frame); + } + } + drain() { + debug('draining queue'); + // Send all remaining frames for processing + this._emitFrames(this._frames); + this._frames = []; + } +} +exports.SortedFrameQueue = SortedFrameQueue; +//# sourceMappingURL=SortedFrameQueue.js.map \ No newline at end of file diff --git a/build/SortedFrameQueue.js.map b/build/SortedFrameQueue.js.map new file mode 100644 index 0000000..f592873 --- /dev/null +++ b/build/SortedFrameQueue.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SortedFrameQueue.js","sourceRoot":"","sources":["../src/SortedFrameQueue.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAC1B,mCAAsC;AAGtC,MAAM,KAAK,GAAG,eAAK,CAAC,2BAA2B,CAAC,CAAC;AAEjD,uEAAuE;AACvE,qFAAqF;AACrF,yEAAyE;AACzE,6EAA6E;AAE7E,MAAa,gBAAiB,SAAQ,qBAAY;IAKhD,YAAY,IAAa;QACvB,KAAK,EAAE,CAAC;QALV,mBAAmB;QACZ,YAAO,GAAG,EAAE,CAAC;QACZ,UAAK,GAAG,EAAE,CAAC;QAKjB,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;SACnB;IACH,CAAC;IAEO,mBAAmB,CAAC,SAAiB;QAC3C,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;YAC7B,OAAO,CAAC,CAAC;SACV;QAED,IAAI,CAAS,CAAC;QACd,IAAI,KAAsB,CAAC;QAE3B,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;YAC7C,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAExB,IAAI,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE;gBAC/B,MAAM;aACP;SACF;QAED,OAAO,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAEO,WAAW,CAAC,MAAyB;QAC3C,KAAK,CAAC,YAAY,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;QAE1C,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAEM,MAAM,CAAC,KAAsB;QAClC,6EAA6E;QAC7E,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE;YACtC,MAAM,sBAAsB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC1D,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC;YAEvE,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;SACnC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAEjE,IAAI,cAAc,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;YAC1C,KAAK,CAAC,sCAAsC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;YAC/D,qCAAqC;YACrC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SAC1B;aAAM;YACL,KAAK,CACH,uCAAuC,cAAc,KAAK,KAAK,CAAC,SAAS,EAAE,CAC5E,CAAC;YACF,8CAA8C;YAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;SAC/C;IACH,CAAC;IAEM,KAAK;QACV,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAExB,2CAA2C;QAC3C,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE/B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;CACF;AAtED,4CAsEC"} \ No newline at end of file diff --git a/build/VideoWriter.d.ts b/build/VideoWriter.d.ts new file mode 100644 index 0000000..4207a84 --- /dev/null +++ b/build/VideoWriter.d.ts @@ -0,0 +1,15 @@ +/// +import { EventEmitter } from 'events'; +import { CaptureOptions } from './PageVideoCapture'; +export declare class VideoWriter extends EventEmitter { + static create(savePath: string, options?: CaptureOptions): Promise; + private _endedPromise; + private _framesPerSecond; + private _receivedFrame; + private _stopped; + private _stream; + protected constructor(savePath: string, options?: CaptureOptions); + private _writeVideo; + stop(): Promise; + write(data: Buffer, durationSeconds?: number): void; +} diff --git a/build/VideoWriter.js b/build/VideoWriter.js new file mode 100644 index 0000000..5c07e1f --- /dev/null +++ b/build/VideoWriter.js @@ -0,0 +1,75 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VideoWriter = void 0; +const debug_1 = __importDefault(require("debug")); +const events_1 = require("events"); +const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg")); +const fs_extra_1 = require("fs-extra"); +const path_1 = require("path"); +const stream_1 = require("stream"); +const utils_1 = require("./utils"); +const debug = debug_1.default('pw-video:VideoWriter'); +class VideoWriter extends events_1.EventEmitter { + constructor(savePath, options) { + super(); + this._framesPerSecond = 25; + this._receivedFrame = false; + this._stopped = false; + this._stream = new stream_1.PassThrough(); + utils_1.ensureFfmpegPath(); + if (options && options.fps) { + this._framesPerSecond = options.fps; + } + this._writeVideo(savePath); + } + static async create(savePath, options) { + await fs_extra_1.ensureDir(path_1.dirname(savePath)); + return new VideoWriter(savePath, options); + } + _writeVideo(savePath) { + debug(`write video to ${savePath}`); + this._endedPromise = new Promise((resolve, reject) => { + fluent_ffmpeg_1.default({ source: this._stream, priority: 20 }) + .videoCodec('libx264') + .inputFormat('image2pipe') + .inputFPS(this._framesPerSecond) + .outputOptions('-preset ultrafast') + .outputOptions('-pix_fmt yuv420p') + .on('error', (e) => { + this.emit('ffmpegerror', e.message); + // do not reject as a result of not having frames + if (!this._receivedFrame && + e.message.includes('pipe:0: End of file')) { + resolve(); + return; + } + reject(`pw-video: error capturing video: ${e.message}`); + }) + .on('end', () => { + resolve(); + }) + .save(savePath); + }); + } + stop() { + if (this._stopped) { + return this._endedPromise; + } + this._stopped = true; + this._stream.end(); + return this._endedPromise; + } + write(data, durationSeconds = 1) { + this._receivedFrame = true; + const numFrames = Math.max(Math.round(durationSeconds * this._framesPerSecond), 1); + debug(`write ${numFrames} frames for duration ${durationSeconds}s`); + for (let i = 0; i < numFrames; i++) { + this._stream.write(data); + } + } +} +exports.VideoWriter = VideoWriter; +//# sourceMappingURL=VideoWriter.js.map \ No newline at end of file diff --git a/build/VideoWriter.js.map b/build/VideoWriter.js.map new file mode 100644 index 0000000..28249c0 --- /dev/null +++ b/build/VideoWriter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"VideoWriter.js","sourceRoot":"","sources":["../src/VideoWriter.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/index.d.ts b/build/index.d.ts new file mode 100644 index 0000000..dcacd92 --- /dev/null +++ b/build/index.d.ts @@ -0,0 +1,3 @@ +export { PageVideoCapture } from './PageVideoCapture'; +export { saveVideo } from './saveVideo'; +export { getFfmpegPath } from './utils'; diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..e31d1f5 --- /dev/null +++ b/build/index.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var PageVideoCapture_1 = require("./PageVideoCapture"); +Object.defineProperty(exports, "PageVideoCapture", { enumerable: true, get: function () { return PageVideoCapture_1.PageVideoCapture; } }); +var saveVideo_1 = require("./saveVideo"); +Object.defineProperty(exports, "saveVideo", { enumerable: true, get: function () { return saveVideo_1.saveVideo; } }); +var utils_1 = require("./utils"); +Object.defineProperty(exports, "getFfmpegPath", { enumerable: true, get: function () { return utils_1.getFfmpegPath; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/build/index.js.map b/build/index.js.map new file mode 100644 index 0000000..f1e5578 --- /dev/null +++ b/build/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAClB,iCAAwC;AAA/B,sGAAA,aAAa,OAAA"} \ No newline at end of file diff --git a/build/saveVideo.d.ts b/build/saveVideo.d.ts new file mode 100644 index 0000000..53f936c --- /dev/null +++ b/build/saveVideo.d.ts @@ -0,0 +1,3 @@ +import { Page } from 'playwright-core'; +import { CaptureOptions, PageVideoCapture } from './PageVideoCapture'; +export declare const saveVideo: (page: Page, savePath: string, options?: CaptureOptions) => Promise; diff --git a/build/saveVideo.js b/build/saveVideo.js new file mode 100644 index 0000000..5d50195 --- /dev/null +++ b/build/saveVideo.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.saveVideo = void 0; +const PageVideoCapture_1 = require("./PageVideoCapture"); +exports.saveVideo = (page, savePath, options) => { + return PageVideoCapture_1.PageVideoCapture.start({ page, savePath, options }); +}; +//# sourceMappingURL=saveVideo.js.map \ No newline at end of file diff --git a/build/saveVideo.js.map b/build/saveVideo.js.map new file mode 100644 index 0000000..16d9dbd --- /dev/null +++ b/build/saveVideo.js.map @@ -0,0 +1 @@ +{"version":3,"file":"saveVideo.js","sourceRoot":"","sources":["../src/saveVideo.ts"],"names":[],"mappings":";;;AACA,yDAAsE;AAEzD,QAAA,SAAS,GAAG,CACvB,IAAU,EACV,QAAgB,EAChB,OAAwB,EACG,EAAE;IAC7B,OAAO,mCAAgB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC"} \ No newline at end of file diff --git a/build/utils.d.ts b/build/utils.d.ts new file mode 100644 index 0000000..56e215f --- /dev/null +++ b/build/utils.d.ts @@ -0,0 +1,5 @@ +import { Page } from 'playwright-core'; +export declare const getFfmpegFromModule: () => string | null; +export declare const getFfmpegPath: () => string | null; +export declare const ensureFfmpegPath: () => void; +export declare const ensurePageType: (page: Page) => void; diff --git a/build/utils.js b/build/utils.js new file mode 100644 index 0000000..db5fd6d --- /dev/null +++ b/build/utils.js @@ -0,0 +1,34 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ensurePageType = exports.ensureFfmpegPath = exports.getFfmpegPath = exports.getFfmpegFromModule = void 0; +const fluent_ffmpeg_1 = require("fluent-ffmpeg"); +exports.getFfmpegFromModule = () => { + try { + const ffmpeg = require('@ffmpeg-installer/ffmpeg'); // eslint-disable-line @typescript-eslint/no-var-requires + if (ffmpeg.path) { + return ffmpeg.path; + } + } + catch (e) { } // eslint-disable-line no-empty + return null; +}; +exports.getFfmpegPath = () => { + if (process.env.FFMPEG_PATH) { + return process.env.FFMPEG_PATH; + } + return exports.getFfmpegFromModule(); +}; +exports.ensureFfmpegPath = () => { + const ffmpegPath = exports.getFfmpegPath(); + if (!ffmpegPath) { + throw new Error('pw-video: FFmpeg path not set. Set the FFMPEG_PATH env variable or install @ffmpeg-installer/ffmpeg as a dependency.'); + } + fluent_ffmpeg_1.setFfmpegPath(ffmpegPath); +}; +exports.ensurePageType = (page) => { + const context = page.context(); + if (!context.newCDPSession) { + throw new Error('pw-video: page context must be chromium'); + } +}; +//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/build/utils.js.map b/build/utils.js.map new file mode 100644 index 0000000..37674c4 --- /dev/null +++ b/build/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,iDAAqE;AAGxD,QAAA,mBAAmB,GAAG,GAAkB,EAAE;IACrD,IAAI;QACF,MAAM,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,yDAAyD;QAC7G,IAAI,MAAM,CAAC,IAAI,EAAE;YACf,OAAO,MAAM,CAAC,IAAI,CAAC;SACpB;KACF;IAAC,OAAO,CAAC,EAAE,GAAE,CAAC,+BAA+B;IAE9C,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEW,QAAA,aAAa,GAAG,GAAkB,EAAE;IAC/C,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE;QAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;KAChC;IAED,OAAO,2BAAmB,EAAE,CAAC;AAC/B,CAAC,CAAC;AAEW,QAAA,gBAAgB,GAAG,GAAS,EAAE;IACzC,MAAM,UAAU,GAAG,qBAAa,EAAE,CAAC;IACnC,IAAI,CAAC,UAAU,EAAE;QACf,MAAM,IAAI,KAAK,CACb,sHAAsH,CACvH,CAAC;KACH;IAED,6BAAmB,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC,CAAC;AAEW,QAAA,cAAc,GAAG,CAAC,IAAU,EAAQ,EAAE;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAE/B,IAAI,CAAE,OAAkC,CAAC,aAAa,EAAE;QACtD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;KAC5D;AACH,CAAC,CAAC"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8294988..1ffc7ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8278,9 +8278,9 @@ } }, "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" }, "tsutils": { "version": "3.17.1", diff --git a/package.json b/package.json index 33c918a..dbff262 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "fluent-ffmpeg": "^2.1.2", "fs-extra": "^9.0.1", "playwright-core": "^1.2.0", - "tslib": "^2.0.0" + "tslib": "^2.0.1" }, "devDependencies": { "@ffmpeg-installer/ffmpeg": "^1.0.20",