@aurora-agency/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
330 lines • 26.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const os_1 = __importDefault(require("os"));
const path_1 = require("path");
const stream_1 = require("stream");
const fluent_ffmpeg_1 = __importStar(require("fluent-ffmpeg"));
const pageVideoStreamTypes_1 = require("./pageVideoStreamTypes");
const SUPPORTED_FILE_FORMATS = [
pageVideoStreamTypes_1.SupportedFileFormats.MP4,
pageVideoStreamTypes_1.SupportedFileFormats.AVI,
pageVideoStreamTypes_1.SupportedFileFormats.MOV,
pageVideoStreamTypes_1.SupportedFileFormats.WEBM,
];
class PageVideoStreamWriter extends events_1.EventEmitter {
constructor(destinationSource, options) {
super();
this.screenLimit = 10;
this.screenCastFrames = [];
this.duration = '00:00:00:00';
this.frameGain = 0;
this.frameLoss = 0;
this.status = pageVideoStreamTypes_1.VIDEO_WRITE_STATUS.NOT_STARTED;
this.videoMediatorStream = new stream_1.PassThrough();
this.destinationExt = null;
if (options) {
this.options = options;
}
const isWritable = this.isWritableStream(destinationSource);
this.configureFFmPegPath();
if (!isWritable && typeof destinationSource === 'string') {
this.destinationExt = this.getDestinationPathExtension(destinationSource);
}
if (this.options.rtmpUrl) {
this.configureRtmpStream(this.options.rtmpUrl);
}
else if (isWritable) {
this.configureVideoWritableStream(destinationSource);
}
else {
this.configureVideoFile(destinationSource);
}
}
get videoFrameSize() {
const { width, height } = this.options.videoFrame;
return width !== null && height !== null ? `${width}x${height}` : '100%';
}
get autopad() {
const autopad = this.options.autopad;
return !autopad ? { activation: false } : { activation: true, color: autopad.color };
}
getFfmpegPath() {
if (this.options.ffmpeg_Path)
return this.options.ffmpeg_Path;
try {
const ffmpeg = require('@ffmpeg-installer/ffmpeg');
return ffmpeg.path || null;
}
catch (_a) {
return null;
}
}
getDestinationPathExtension(destinationFile) {
const fileExtension = (0, path_1.extname)(destinationFile);
return fileExtension.includes('.')
? fileExtension.replace('.', '')
: fileExtension;
}
configureFFmPegPath() {
const ffmpegPath = this.getFfmpegPath();
if (!ffmpegPath) {
throw new Error('FFmpeg path is missing, \n Set the FFMPEG_PATH env variable');
}
(0, fluent_ffmpeg_1.setFfmpegPath)(ffmpegPath);
}
isWritableStream(destinationSource) {
return destinationSource && typeof destinationSource !== 'string';
}
configureRtmpStream(rtmpUrl) {
this.writerPromise = new Promise((resolve) => {
var _a;
const outputStream = this.getDestinationStream();
outputStream
.format('flv')
.videoCodec(this.options.videoCodec || 'libx264')
.size(this.videoFrameSize)
.aspect(this.options.aspectRatio || '4:3')
.autopad(this.autopad.activation, (_a = this.autopad) === null || _a === void 0 ? void 0 : _a.color)
.on('progress', (progress) => {
this.duration = progress.timemark;
})
.on('error', (e) => {
this.handleWriteStreamError(e.message);
resolve(false);
})
.on('stderr', (e) => {
this.handleWriteStreamError(e);
resolve(false);
})
.on('end', () => resolve(true))
.output(rtmpUrl)
.run();
});
}
configureVideoFile(destinationPath) {
const fileExt = this.destinationExt;
if (!SUPPORTED_FILE_FORMATS.includes(fileExt)) {
throw new Error('File format is not supported');
}
this.writerPromise = new Promise((resolve) => {
var _a;
const outputStream = this.getDestinationStream();
outputStream
.on('error', (e) => {
this.handleWriteStreamError(e.message);
resolve(false);
})
.on('stderr', (e) => {
this.handleWriteStreamError(e);
resolve(false);
})
.on('end', () => resolve(true));
if (fileExt === pageVideoStreamTypes_1.SupportedFileFormats.WEBM) {
outputStream
.videoCodec('libvpx-vp9')
.videoBitrate(this.options.videoBitrate || 1000, true)
.format('webm')
.outputOptions([
'-deadline', 'realtime',
'-cpu-used', '4',
`-crf`, `${(_a = this.options.videoCrf) !== null && _a !== void 0 ? _a : 32}`,
]);
}
else {
outputStream.format(fileExt);
}
outputStream.save(destinationPath);
});
}
configureVideoWritableStream(writableStream) {
var _a;
const isWebm = ((_a = this.options.videoCodec) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('vp9')) || false;
this.writerPromise = new Promise((resolve) => {
var _a;
const outputStream = this.getDestinationStream();
outputStream
.on('error', (e) => {
writableStream.emit('error', e);
resolve(false);
})
.on('stderr', (e) => {
writableStream.emit('error', { message: e });
resolve(false);
})
.on('end', () => {
writableStream.end();
resolve(true);
});
if (isWebm) {
outputStream
.format('webm')
.videoCodec('libvpx-vp9')
.videoBitrate(this.options.videoBitrate || 1000, true)
.outputOptions([
'-deadline', 'realtime',
'-cpu-used', '4',
`-crf`, `${(_a = this.options.videoCrf) !== null && _a !== void 0 ? _a : 32}`,
]);
}
else {
outputStream
.format('mp4')
.addOutputOptions('-movflags +frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov');
}
outputStream.pipe(writableStream);
});
}
getOutputOption() {
var _a, _b;
const cpu = Math.max(1, os_1.default.cpus().length - 1);
const videoOutputOptions = (_a = this.options.videOutputOptions) !== null && _a !== void 0 ? _a : [];
const outputOptions = [
`-crf ${(_b = this.options.videoCrf) !== null && _b !== void 0 ? _b : 23}`,
`-preset ${this.options.videoPreset || 'ultrafast'}`,
`-pix_fmt ${this.options.videoPixelFormat || 'yuv420p'}`,
`-minrate ${this.options.videoBitrate || 1000}`,
`-maxrate ${this.options.videoBitrate || 1000}`,
'-framerate 1',
`-threads ${cpu}`,
'-loglevel error',
...videoOutputOptions,
];
return outputOptions;
}
addVideoMetadata(outputStream) {
var _a;
const metadataOptions = (_a = this.options.metadata) !== null && _a !== void 0 ? _a : [];
for (const metadata of metadataOptions) {
outputStream.outputOptions('-metadata', metadata);
}
}
getDestinationStream() {
var _a;
const outputStream = (0, fluent_ffmpeg_1.default)({
source: this.videoMediatorStream,
priority: 20,
})
.videoCodec(this.options.videoCodec || 'libx264')
.size(this.videoFrameSize)
.aspect(this.options.aspectRatio || '4:3')
.autopad(this.autopad.activation, (_a = this.autopad) === null || _a === void 0 ? void 0 : _a.color)
.inputFormat('image2pipe')
.inputFPS(this.options.fps)
.outputOptions(this.getOutputOption())
.on('progress', (progressDetails) => {
this.duration = progressDetails.timemark;
});
this.addVideoMetadata(outputStream);
if (this.options.recordDurationLimit) {
outputStream.duration(this.options.recordDurationLimit);
}
return outputStream;
}
handleWriteStreamError(errorMessage) {
this.emit('videoStreamWriterError', errorMessage);
if (this.status !== pageVideoStreamTypes_1.VIDEO_WRITE_STATUS.IN_PROGRESS &&
errorMessage.includes('pipe:0: End of file'))
return;
console.error(`Error unable to capture video stream: ${errorMessage}`);
}
findSlot(timestamp) {
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;
}
return i + 1;
}
insert(frame) {
if (this.screenCastFrames.length === this.screenLimit) {
const numberOfFramesToSplice = Math.floor(this.screenLimit / 2);
const framesToProcess = this.screenCastFrames.splice(0, numberOfFramesToSplice);
this.processFrameBeforeWrite(framesToProcess, this.screenCastFrames[0].timestamp);
}
const insertionIndex = this.findSlot(frame.timestamp);
if (insertionIndex === this.screenCastFrames.length) {
this.screenCastFrames.push(frame);
}
else {
this.screenCastFrames.splice(insertionIndex, 0, frame);
}
}
trimFrame(fameList, chunkEndTime) {
return fameList.map((currentFrame, index) => {
const endTime = index !== fameList.length - 1 ? fameList[index + 1].timestamp : chunkEndTime;
return Object.assign(Object.assign({}, currentFrame), { duration: endTime - currentFrame.timestamp });
});
}
processFrameBeforeWrite(frames, chunkEndTime) {
const processedFrames = this.trimFrame(frames, chunkEndTime);
processedFrames.forEach(({ blob, duration }) => {
this.write(blob, duration);
});
}
write(data, durationSeconds = 1) {
this.status = pageVideoStreamTypes_1.VIDEO_WRITE_STATUS.IN_PROGRESS;
const totalFrames = durationSeconds * this.options.fps;
const floored = Math.floor(totalFrames);
let numberOfFPS = Math.max(floored, 1);
if (floored === 0)
this.frameGain += 1 - totalFrames;
else
this.frameLoss += totalFrames - floored;
while (this.frameLoss > 1) {
this.frameLoss--;
numberOfFPS++;
}
while (this.frameGain > 1) {
this.frameGain--;
numberOfFPS--;
}
for (let i = 0; i < numberOfFPS; i++) {
this.videoMediatorStream.write(data);
}
}
drainFrames(stoppedTime) {
this.processFrameBeforeWrite(this.screenCastFrames, stoppedTime);
this.screenCastFrames = [];
}
stop(stoppedTime = Date.now() / 1000) {
if (this.status === pageVideoStreamTypes_1.VIDEO_WRITE_STATUS.COMPLETED)
return this.writerPromise;
this.drainFrames(stoppedTime);
this.videoMediatorStream.end();
this.status = pageVideoStreamTypes_1.VIDEO_WRITE_STATUS.COMPLETED;
return this.writerPromise;
}
}
exports.default = PageVideoStreamWriter;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFnZVZpZGVvU3RyZWFtV3JpdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9wYWdlVmlkZW9TdHJlYW1Xcml0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLG1DQUFzQztBQUN0Qyw0Q0FBb0I7QUFDcEIsK0JBQStCO0FBQy9CLG1DQUErQztBQUUvQywrREFBc0Q7QUFFdEQsaUVBS2dDO0FBRWhDLE1BQU0sc0JBQXNCLEdBQUc7SUFDN0IsMkNBQW9CLENBQUMsR0FBRztJQUN4QiwyQ0FBb0IsQ0FBQyxHQUFHO0lBQ3hCLDJDQUFvQixDQUFDLEdBQUc7SUFDeEIsMkNBQW9CLENBQUMsSUFBSTtDQUMxQixDQUFDO0FBRUYsTUFBcUIscUJBQXNCLFNBQVEscUJBQVk7SUFjN0QsWUFBWSxpQkFBb0MsRUFBRSxPQUFzQjtRQUN0RSxLQUFLLEVBQUUsQ0FBQztRQWRPLGdCQUFXLEdBQUcsRUFBRSxDQUFDO1FBQzFCLHFCQUFnQixHQUFHLEVBQUUsQ0FBQztRQUN2QixhQUFRLEdBQUcsYUFBYSxDQUFDO1FBQ3pCLGNBQVMsR0FBRyxDQUFDLENBQUM7UUFDZCxjQUFTLEdBQUcsQ0FBQyxDQUFDO1FBRWIsV0FBTSxHQUFHLHlDQUFrQixDQUFDLFdBQVcsQ0FBQztRQUd4Qyx3QkFBbUIsR0FBZ0IsSUFBSSxvQkFBVyxFQUFFLENBQUM7UUFFckQsbUJBQWMsR0FBZ0MsSUFBSSxDQUFDO1FBS3pELElBQUksT0FBTyxFQUFFO1lBQ1gsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7U0FDeEI7UUFFRCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUM1RCxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUUzQixJQUFJLENBQUMsVUFBVSxJQUFJLE9BQU8saUJBQWlCLEtBQUssUUFBUSxFQUFFO1lBQ3hE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