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

166 lines 13.5 kB
import { EventEmitter } from 'events'; import { PassThrough } from 'stream'; import ffmpeg, { setFfmpegPath } from 'fluent-ffmpeg'; import { VIDEO_WRITE_STATUS } from './StreamTypes'; /** * @ignore */ export default class FFMPEGStreamWriter extends EventEmitter { constructor(savePath, options) { super(); this.streamBatchSize = 300; this.screencastFrames = []; this.duration = '00:00:00:00'; this.status = VIDEO_WRITE_STATUS.NOT_STARTED; this.videoMediatorStream = new PassThrough(); if (options) { this.options = options; } this.configureVideoFile(savePath); console.log(`Successfully created new FFMPEGStreamWriter object with the following options: ${JSON.stringify(options)} and the following savePath: ${JSON.stringify(savePath)}`); } get videoFrameSize() { const { width, height } = this.options.videoFrame; return width !== null && height !== null ? `${width}x${height}` : '100%'; } getFFMPEGPath() { if (this.options.ffmpeg_Path) { return this.options.ffmpeg_Path; } try { // eslint-disable-next-line @typescript-eslint/no-var-requires const ffmpeg = require('@ffmpeg-installer/ffmpeg'); if (ffmpeg.path) { return ffmpeg.path; } return null; } catch (e) { return null; } } configureFFMPEGPath() { const ffmpegPath = this.getFFMPEGPath(); if (!ffmpegPath) { throw new Error('Missing path for FFmpeg, \n Set the FFMPEG_PATH env variable'); } setFfmpegPath(ffmpegPath); console.log(`Successfully set FFMPEG path to: ${ffmpegPath}`); } configureVideoFile(savePath) { this.configureFFMPEGPath(); this.writerPromise = new Promise((resolve) => { ffmpeg({ source: this.videoMediatorStream, priority: 20 }) .videoCodec('libvpx-vp9') .size(this.videoFrameSize) .aspect(this.options.aspectRatio || '4:3') .inputFormat('image2pipe') .inputFPS(this.options.fps) .outputFPS(this.options.fps) .outputOptions('-b:v 2M') // .outputOptions('-pix_fmt +') .on('progress', (progressDetails) => { this.duration = progressDetails.timemark; }) .on('error', (e) => { this.handleWriteStreamError(e.message); resolve(false); }) .on('end', () => resolve(true)) .save(savePath); }); } handleWriteStreamError(errorMessage) { this.emit('videoStreamWriterError', errorMessage); if (this.status !== VIDEO_WRITE_STATUS.IN_PROGRESS && errorMessage.includes('pipe:0: End of file')) { return; } return console.error(`Error unable to capture video stream: ${errorMessage}`); } findVacantIndexInScreencastFrames(timestamp) { console.log(`Searching for a vacant index in the frame array...`); if (this.screencastFrames.length === 0) { return 0; } let i; let frame; for (i = this.screencastFrames.length - 1; i >= 0; i--) { frame = this.screencastFrames[i]; if (timestamp > frame.timestamp) { break; } } console.log(`Successfully found a vacant index in the frames array: [${i + 1}]`); return i + 1; } insert(frame) { console.log(`About to insert new frame to frame array. Current array length is: [${this.screencastFrames.length}]`); if (this.screencastFrames.length === this.streamBatchSize) { console.log(`Writing the first half of the frames to ffmpeg`); const numberOfFramesToSplice = Math.floor(this.streamBatchSize / 2); // const numberOfFramesToSplice = this.streamBatchSize; const framesToProcess = this.screencastFrames.splice(0, numberOfFramesToSplice); this.processFramesAndWrite(framesToProcess); } const insertionIndex = this.findVacantIndexInScreencastFrames(frame.timestamp); if (insertionIndex === this.screencastFrames.length) { // if end of frames array this.screencastFrames.push(frame); console.log(`Successfully pushed new frame to END of frames array at index [${insertionIndex}]`); } else { this.screencastFrames.splice(insertionIndex, 0, frame); console.log(`Successfully pushed new frame to MIDDLE of frames array at index [${insertionIndex}]`); } } trimFrames(frameList) { if (!this.lastProcessedFrame) { this.lastProcessedFrame = frameList[0]; } return frameList.map((currentFrame) => { const duration = currentFrame.timestamp - this.lastProcessedFrame.timestamp; this.lastProcessedFrame = currentFrame; return { ...currentFrame, duration }; }); } processFramesAndWrite(frames) { const processedFrames = this.trimFrames(frames); processedFrames.forEach(({ blob }) => { this.write(blob); }); console.log(`Successfully finished writing all frames to ffmpeg`); } write(data) { this.status = VIDEO_WRITE_STATUS.IN_PROGRESS; this.videoMediatorStream.write(data); // const NUMBER_OF_FPS = Math.max( // Math.floor(durationSeconds * this.options.fps), // 1 // ); // // for (let i = 0; i < NUMBER_OF_FPS; i++) { // this.videoMediatorStream.write(data); // } } drainFrames() { this.processFramesAndWrite(this.screencastFrames); this.screencastFrames = []; if (!this.lastProcessedFrame) return; this.write(this.lastProcessedFrame.blob); } stop() { if (this.status === VIDEO_WRITE_STATUS.COMPLETED) { return this.writerPromise; } this.drainFrames(); this.videoMediatorStream.end(); this.status = VIDEO_WRITE_STATUS.COMPLETED; return this.writerPromise; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRkZNUEVHU3RyZWFtV3JpdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9GRk1QRUdTdHJlYW1Xcml0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBRXJDLE9BQU8sTUFBTSxFQUFFLEVBQUUsYUFBYSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXRELE9BQU8sRUFFTCxrQkFBa0IsRUFFbkIsTUFBTSxlQUFlLENBQUM7QUFFdkI7O0dBRUc7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLGtCQUFtQixTQUFRLFlBQVk7SUFZMUQsWUFBWSxRQUFnQixFQUFFLE9BQXNCO1FBQ2xELEtBQUssRUFBRSxDQUFDO1FBWk8sb0JBQWUsR0FBRyxHQUFHLENBQUM7UUFDL0IscUJBQWdCLEdBQUcsRUFBRSxDQUFDO1FBRXZCLGFBQVEsR0FBRyxhQUFhLENBQUM7UUFFeEIsV0FBTSxHQUFHLGtCQUFrQixDQUFDLFdBQVcsQ0FBQztRQUd4Qyx3QkFBbUIsR0FBZ0IsSUFBSSxXQUFXLEVBQUUsQ0FBQztRQU0zRCxJQUFJLE9BQU8sRUFBRTtZQUNYLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1NBQ3hCO1FBRUQsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ2xDLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0ZBQWtGLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLGdDQUFnQyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNuTCxDQUFDO0lBRUQsSUFBWSxjQUFjO1FBQ3hCLE1BQU0sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7UUFFbEQsT0FBTyxL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