UNPKG

wibbitz-puppeteer-screen-recorder

Version:

A puppeteer Plugin that uses the native chrome devtool protocol for capturing video frame by frame. Also supports an option to follow pages that are opened by the current page object

157 lines 13 kB
import { EventEmitter } from 'events'; /** * @ignore */ export class CDPScreencastRecorder extends EventEmitter { constructor(page, options) { super(); this.CDPSessionsStack = []; this.isStreamingEnded = false; this._isReadyToAdvanceFrame = false; this.page = page; this.options = options; console.log('Created new CDPScreencastRecorder object'); } get isReadyToAdvanceFrame() { return this._isReadyToAdvanceFrame; } set isReadyToAdvanceFrame(newValue) { this._isReadyToAdvanceFrame = newValue; } shouldFollowPopupWindow() { return this.options.followNewTab; } static async createNewPageCDPSession(page) { try { return await page.target().createCDPSession(); } catch (error) { console.log('Failed to create CDP Session', error); return null; } } getCurrentCDPSession() { return this.CDPSessionsStack[this.CDPSessionsStack.length - 1]; } addListenerOnTabOpens(page) { page.on('popup', () => this.registerTabListener(page)); } removeListenerOnTabClose(page) { page.off('popup', () => this.registerTabListener(page)); } async registerTabListener(newPage) { await this.startCDPSession(newPage); newPage.once('close', async () => await this.endSession()); } async startScreenCast(shouldDeleteCDPSessionOnFailure = false) { const currentCDPSession = this.getCurrentCDPSession(); console.log(`Obtained the following CDPSession as the current one from the CDP session stack: ${JSON.stringify(currentCDPSession)}`); try { await currentCDPSession.send('Page.startScreencast', { everyNthFrame: 1, format: 'png', quality: 100 }); console.log(`Sent a 'Page.startScreencast' event from this CDP session to underlying CDP itself`); } catch (e) { if (shouldDeleteCDPSessionOnFailure) { console.log('Ending the current CDPSession because sending a Page.startScreencast event to this CDPSession failed: ' + e); await this.endSession(); } } } async stopScreenCastOfLastCDPSession() { const currentCDPSession = this.getCurrentCDPSession(); if (!currentCDPSession) { return; } console.log('Sending a \'Page.stopScreencast\' event to the current CDP session'); await currentCDPSession.send('Page.stopScreencast'); } async startCDPSession(page) { const pageCDPSession = await CDPScreencastRecorder.createNewPageCDPSession(page); console.log(`Successfully created new pageCDPSession: ${JSON.stringify(pageCDPSession)}`); if (!pageCDPSession) { console.log(`Newly created pageCDPSession is null`); return; } await this.stopScreenCastOfLastCDPSession(); console.log('Pushing the newly created CDP session to the CDP session stack'); this.CDPSessionsStack.push(pageCDPSession); await this.setupScreencastFrameHandlers(pageCDPSession); await this.startScreenCast(true); } async setupScreencastFrameHandlers(session) { this.isFrameAckReceived = new Promise((resolve) => { session.on('Page.screencastFrame', async ({ metadata, data, sessionId }) => { if (!metadata.timestamp || this.isStreamingEnded) { console.log(`Received 'Page.screencastFrame' event with timestamp: [${metadata.timestamp}] and isStreamingEnded=${this.isStreamingEnded} and something's wrong with them`); resolve(); } console.log(`Sending a 'Page.screencastFrameAck' acknowledgment event back to the underlying CDP`); const ackPromise = session.send('Page.screencastFrameAck', { sessionId: sessionId }); try { await ackPromise; console.log(`Acknowledgement successfully sent to underlying CDP`); } catch (error) { console.error('Error in sending acknowledgment for `Page.screencastFrameAck` back to underlying CDP', error.message); resolve(); } if (!this.isReadyToAdvanceFrame) { /** * control is in the hands of the screen recorder library, meaning we can * send a 'pageScreenFrame' event back to the PuppeteerScreenRecorder * layer so it can add it to frames sent to ffmpeg. */ console.log(`CDP captured a new frame at unix timestamp: ${metadata.timestamp}`); console.log(`Sending a 'pageScreenFrame' event to the PuppeteerScreenRecorder object that holds this CDP session`); this.emit('pageScreenFrame', { blob: Buffer.from(data, 'base64'), timestamp: metadata.timestamp }); console.log(`Setting isReadyToAdvanceFrame to true`); this.isReadyToAdvanceFrame = true; // handing over control to the caller of this library so it can advance the video frame } resolve(); }); }); console.log(`Successfully setup the 'Page.screencastFrame' (underlying CDP completion message) handler on the CDP session`); } async endSession() { this.CDPSessionsStack.pop(); await this.startScreenCast(); } async start() { await this.startCDPSession(this.page); if (this.shouldFollowPopupWindow) { this.addListenerOnTabOpens(this.page); } } async stop() { if (this.isStreamingEnded) { return this.isStreamingEnded; } if (this.shouldFollowPopupWindow) { this.removeListenerOnTabClose(this.page); } await Promise.race([ this.isFrameAckReceived, new Promise((resolve) => setTimeout(resolve, 1000)) ]); this.isStreamingEnded = true; try { for (const currentSession of this.CDPSessionsStack) { await currentSession.detach(); } } catch (e) { console.warn('Error detaching session', e.message); } return true; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ0RQU2NyZWVuY2FzdFJlY29yZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9DRFBTY3JlZW5jYXN0UmVjb3JkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQVl0Qzs7R0FFRztBQUNILE1BQU0sT0FBTyxxQkFBc0IsU0FBUSxZQUFZO0lBUXJELFlBQVksSUFBVSxFQUFFLE9BQXVDO1FBQzdELEtBQUssRUFBRSxDQUFDO1FBTkYscUJBQWdCLEdBQWtCLEVBQUUsQ0FBQztRQUNyQyxxQkFBZ0IsR0FBRyxLQUFLLENBQUM7UUFFekIsMkJBQXNCLEdBQUcsS0FBSyxDQUFDO1FBSXJDLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ2pCLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLE9BQU8sQ0FBQyxHQUFHLENBQUMsMENBQTBDLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBRUQsSUFBVyxxQkFBcUI7UUFDOUIsT0FBTyxJQUFJLENBQUMsc0JBQXNCLENBQUM7SUFDckMsQ0FBQztJQUVELElBQVcscUJBQXFCLENBQUMsUUFBaUI7UUFDaEQsSUFBSSxDQUFDLHNCQUFzQixHQUFHLFFBQVEsQ0FBQztJQUN6QyxDQUFDO0lBRU8sdUJBQXVCO1FBQzdCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUM7SUFDbkMsQ0FBQztJQUVPLE1BQU0sQ0FBQyxLQUFLLENBQUMsdUJBQXVCLENBQUMsSUFBVTtRQUNyRCxJQUFJO1lBQ0YsT0FBTyxNQUFNLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1NBQy9DO1FBQUMsT0FBTyxLQUFLLEVBQUU7WUFDZCxPQUFPLENBQUMsR0FBRyxDQUFDLDhCQUE4QixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ25ELE9BQU8sSUFBSSxDQUFDO1NBQ2I7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