228 lines
6.7 KiB
JavaScript
228 lines
6.7 KiB
JavaScript
const express = require('express');
|
|
const { spawn } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const fetch = require('node-fetch');
|
|
|
|
const app = express();
|
|
const port = 8080;
|
|
const tmp_folder = '/tmp/ramdisk/';
|
|
|
|
let channelsPath = path.resolve(__dirname, 'channels.json');
|
|
let channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
|
|
|
|
const activeStreams = {}; // Store active stream processes with unique IDs
|
|
|
|
fs.watch(channelsPath, (eventType, filename) => {
|
|
if (eventType === 'change') {
|
|
console.log(`====================================`);
|
|
console.log(`channels.json was updated, reloading...`);
|
|
try {
|
|
channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
|
|
// Log every channel loaded
|
|
for (const channel in channels) {
|
|
console.log(`Channel loaded: ${channel}`);
|
|
}
|
|
console.log(`Channels reloaded`);
|
|
console.log(`====================================`);
|
|
} catch (error) {
|
|
console.log(error.message);
|
|
}
|
|
}
|
|
});
|
|
|
|
async function startStreamlink(channel, clientID) {
|
|
const useragent = channel.useragent;
|
|
const authorization = channel.authorization;
|
|
const referer = channel.referer;
|
|
const proxy = channel.proxy;
|
|
const key1 = channel.key1;
|
|
const key2 = channel.key2;
|
|
const key3 = channel.key3;
|
|
const key4 = channel.key4;
|
|
const key5 = channel.key5;
|
|
const audios = channel.audios;
|
|
const resolution = channel.resolution;
|
|
const delay = channel.delay;
|
|
|
|
let url = channel.url;
|
|
let baseurl;
|
|
|
|
if (url.includes("telefonica.com")) {
|
|
const now = new Date();
|
|
|
|
const startTime = new Date(now.getTime() - 45000).toISOString().slice(0, -5) + "Z";
|
|
const endTime = new Date(now.getTime() + (9 * 60 * 60 * 1000)).toISOString().slice(0, -5) + "Z";
|
|
|
|
baseurl = url + `&start_time=${startTime}&end_time=${endTime}`;
|
|
|
|
url = `http://proxy_movistar:8080/` + url + `&start_time=${startTime}&end_time=${endTime}`;
|
|
}
|
|
|
|
let args = [
|
|
url,
|
|
'--use-shaka-packager',
|
|
'--log-level', 'OFF',
|
|
'--no-log',
|
|
// '--live-real-time-merge', 'true',
|
|
// '--mp4-real-time-decryption', 'true',
|
|
// '--live-wait-time', '1',
|
|
'--thread-count', '3',
|
|
'--concurrent-download',
|
|
'--live-pipe-mux',
|
|
'--live-keep-segments', 'false',
|
|
'--check-segments-count', 'false',
|
|
// '--live-take-count', '10',
|
|
'--tmp-dir', tmp_folder + `${channel.name}_${clientID}`, // Unique folder for each client
|
|
'--del-after-done', 'true'
|
|
];
|
|
|
|
if (useragent) {
|
|
args.splice(1, 0, '--header', `User-Agent: ${useragent}`);
|
|
} else {
|
|
args.splice(1, 0, '--header', `User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299`);
|
|
}
|
|
if (authorization) {
|
|
args.splice(1, 0, '--header', `Authorization: ${authorization}`);
|
|
}
|
|
if (referer) {
|
|
args.splice(1, 0, '--header', `Referer: ${referer}`);
|
|
}
|
|
if (proxy) {
|
|
args.splice(1, 0, '--custom-proxy', proxy);
|
|
}
|
|
if (key5) {
|
|
args.splice(1, 0, '--key', key5);
|
|
}
|
|
if (key4) {
|
|
args.splice(1, 0, '--key', key4);
|
|
}
|
|
if (key3) {
|
|
args.splice(1, 0, '--key', key3);
|
|
}
|
|
if (key2) {
|
|
args.splice(1, 0, '--key', key2);
|
|
}
|
|
if (key1) {
|
|
args.splice(1, 0, '--key', key1);
|
|
}
|
|
if (audios) {
|
|
args.splice(1, 0, '-sa', audios);
|
|
} else {
|
|
args.splice(1, 0, '-sa', 'best2');
|
|
}
|
|
if (resolution) {
|
|
args.splice(1, 0, '-sv', 'res=' + resolution);
|
|
} else {
|
|
args.splice(1, 0, '-sv', 'best');
|
|
}
|
|
|
|
if (delay) {
|
|
args.splice(1, 0, '--live-pipe-options', '-itsoffset ' + delay + ' -re -loglevel error {INPUTS} -c copy -f mpegts -fflags +genpts -shortest pipe:1');
|
|
}
|
|
|
|
if (baseurl) {
|
|
args.splice(1, 0, '--base-url', baseurl);
|
|
}
|
|
|
|
console.log(`Args: ${args}`);
|
|
|
|
const streamlinkProcess = spawn('N_m3u8DL-RE', args);
|
|
|
|
const processID = `${channel.name}_${clientID}`;
|
|
activeStreams[processID] = streamlinkProcess;
|
|
|
|
return streamlinkProcess;
|
|
}
|
|
|
|
app.get('/stream/:channelName', async (req, res) => {
|
|
const channelName = req.params.channelName;
|
|
const clientID = req.ip;
|
|
|
|
const channel = channels[channelName];
|
|
console.log(`Received request for /stream/${channelName} from Client IP: ${clientID}`);
|
|
channels[channelName]['name'] = channelName;
|
|
|
|
if (!channel) {
|
|
res.status(404).send('Channel not found');
|
|
return;
|
|
}
|
|
|
|
console.log(`Starting Streamlink command for channel: ${channelName} (Client IP: ${clientID})`);
|
|
|
|
try {
|
|
let streamlinkProcess = await startStreamlink(channel, clientID);
|
|
|
|
res.setHeader('Content-Type', 'video/MP2T');
|
|
streamlinkProcess.stdout.pipe(res);
|
|
|
|
streamlinkProcess.stderr.on('data', data => {
|
|
console.error(`Streamlink: ${data}`);
|
|
});
|
|
|
|
streamlinkProcess.on('close', code => {
|
|
console.log(`Streamlink process exited with code ${code} for Client IP: ${clientID}`);
|
|
res.end();
|
|
const processID = `${channelName}_${clientID}`;
|
|
delete activeStreams[processID];
|
|
const tempFolderPath = tmp_folder + `${channel.name}_${clientID}`;
|
|
if (fs.existsSync(tempFolderPath)) {
|
|
fs.rmSync(tempFolderPath, { recursive: true });
|
|
}
|
|
logActiveConnections();
|
|
});
|
|
|
|
res.on('close', () => {
|
|
console.log(`Response closed, killing channel ${channel.name} for Client IP: ${clientID}`);
|
|
const processID = `${channelName}_${clientID}`;
|
|
if (activeStreams[processID]) {
|
|
activeStreams[processID].kill();
|
|
delete activeStreams[processID];
|
|
}
|
|
const tempFolderPath = tmp_folder + `${channel.name}_${clientID}`;
|
|
if (fs.existsSync(tempFolderPath)) {
|
|
fs.rmSync(tempFolderPath, { recursive: true });
|
|
}
|
|
logActiveConnections();
|
|
});
|
|
|
|
req.on('abort', () => {
|
|
console.log(`Request aborted, killing Streamlink process for Client IP: ${clientID}`);
|
|
const processID = `${channelName}_${clientID}`;
|
|
if (activeStreams[processID]) {
|
|
activeStreams[processID].kill();
|
|
delete activeStreams[processID];
|
|
}
|
|
const tempFolderPath = tmp_folder + `${channel.name}_${clientID}`;
|
|
if (fs.existsSync(tempFolderPath)) {
|
|
fs.rmSync(tempFolderPath, { recursive: true });
|
|
}
|
|
logActiveConnections();
|
|
});
|
|
|
|
logActiveConnections();
|
|
} catch (error) {
|
|
console.error(`Streamlink failed: ${error}`);
|
|
res.status(500).send('Streamlink failed');
|
|
}
|
|
});
|
|
|
|
function logActiveConnections() {
|
|
const activeConnections = Object.keys(activeStreams).length;
|
|
console.log(`Active connections: ${activeConnections}`);
|
|
}
|
|
|
|
app.listen(port, "0.0.0.0", () => {
|
|
console.log(`Server listening on port ${port}`);
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
for (const processID in activeStreams) {
|
|
if (activeStreams.hasOwnProperty(processID)) {
|
|
activeStreams[processID].kill();
|
|
delete activeStreams[processID];
|
|
}
|
|
}
|
|
process.exit();
|
|
});
|