118 lines
3.7 KiB
JavaScript
118 lines
3.7 KiB
JavaScript
import { join } from 'path';
|
|
import { spawn } from 'child_process';
|
|
import fs from 'node:fs';
|
|
|
|
export const getFilename = (appContext, roomName) => {
|
|
const name = `${roomName}_${new Date().toISOString()}.ts`
|
|
return join(appContext.env.FUTUREPORN_WORKDIR, 'recordings', name);
|
|
}
|
|
|
|
|
|
export const assertDirectory = (directoryPath) => {
|
|
if (fs.statSync(directoryPath, { throwIfNoEntry: false }) === undefined) fs.mkdirSync(directoryPath);
|
|
}
|
|
|
|
export const checkFFmpeg = async (appContext) => {
|
|
return new Promise((resolve, reject) => {
|
|
const childProcess = spawn('ffmpeg', ['-version']);
|
|
|
|
childProcess.on('error', (err) => {
|
|
appContext.logger.log({
|
|
level: 'error',
|
|
message: `ffmpeg -version failed, which likely means ffmpeg is not installed or not on $PATH`,
|
|
});
|
|
throw new Error('ffmpeg is missing')
|
|
});
|
|
|
|
childProcess.on('exit', (code) => {
|
|
if (code !== 0) reject(`'ffmpeg -version' exited with code ${code}`)
|
|
if (code === 0) {
|
|
appContext.logger.log({ level: 'info', message: `ffmpeg PRESENT.` });
|
|
resolve()
|
|
}
|
|
});
|
|
})
|
|
};
|
|
|
|
export const assertDependencyDirectory = (appContext) => {
|
|
// Extract the directory path from the filename
|
|
const directoryPath = join(appContext.env.FUTUREPORN_WORKDIR, 'recordings');
|
|
console.log(`asserting ${directoryPath} exists`)
|
|
|
|
// Check if the directory exists, and create it if it doesn't
|
|
if (!fs.existsSync(directoryPath)) {
|
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
console.log(`Created directory: ${directoryPath}`);
|
|
}
|
|
}
|
|
|
|
export const record = async (appContext, playlistUrl, roomName) => {
|
|
if (appContext === undefined) throw new Error('appContext undef');
|
|
if (playlistUrl === undefined) throw new Error('playlistUrl undef');
|
|
if (roomName === undefined) throw new Error('roomName undef');
|
|
|
|
const filename = getFilename(appContext, roomName);
|
|
console.log(`downloading to ${filename}`)
|
|
|
|
// example: `ffmpeg -headers "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0"
|
|
// -i ${chunkPlaylist}
|
|
// -c:v copy
|
|
// -c:a copy
|
|
// -movflags faststart
|
|
// -y
|
|
// -f mpegts
|
|
// ./my-recording.ts`
|
|
const ffmpegProcess = spawn('ffmpeg', [
|
|
'-headers', `"User-Agent: ${appContext.env.DOWNLOADER_UA}"`,
|
|
'-i', playlistUrl,
|
|
'-c:v', 'copy',
|
|
'-c:a', 'copy',
|
|
'-movflags', 'faststart',
|
|
'-y',
|
|
'-f', 'mpegts',
|
|
filename
|
|
], {
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
ffmpegProcess.once('exit', (code) => {
|
|
resolve(code)
|
|
})
|
|
})
|
|
|
|
// ffmpegProcess.on('data', (data) => {
|
|
// console.log(data.toString());
|
|
// });
|
|
|
|
|
|
// Optional: Handle other events such as 'error', 'close', etc.
|
|
// @todo this needs to be handled outside this function
|
|
// otherwise this function is not testable
|
|
// ffmpegProcess.on('exit', (code, signal) => {
|
|
// // Retry the download using exponential backoff if the process exits for any reason
|
|
// console.log(`ffmpeg exited with code ${code} and signal ${signal}`)
|
|
// retryDownload(appContext, playlistUrl, roomName);
|
|
// });
|
|
|
|
// return ffmpegProcess;
|
|
}
|
|
|
|
|
|
const calculateExponentialBackoffDelay = (attemptNumber) => {
|
|
return Math.pow(2, attemptNumber) * 1000;
|
|
};
|
|
|
|
const retryDownload = (appContext, playlistUrl, roomName, attemptNumber = 1, maxAttempts = 3) => {
|
|
const delay = calculateExponentialBackoffDelay(attemptNumber);
|
|
|
|
appContext.logger.log({ level: 'debug', message: `Retrying download in ${delay / 1000} seconds...` });
|
|
|
|
setTimeout(() => {
|
|
console.log('Retrying download...');
|
|
record(appContext, playlistUrl, roomName, attemptNumber + 1);
|
|
}, delay);
|
|
};
|