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(); });