Claude-code / routes /proxy.js
Tafita1206's picture
Upload 2 files
f160d59 verified
Raw
History Blame Contribute Delete
4.92 kB
/**
* Proxy HLS direct β€” SANS FFmpeg
* - Récupère le M3U8 depuis la source active
* - Réécrit les URLs des segments pour passer par notre serveur
* - Proxy chaque segment Γ  la demande
* - Le switch de source est immΓ©diat : chaque requΓͺte playlist lit la source courante
*/
const express = require('express');
const router = express.Router();
const axios = require('axios');
const scheduler = require('../core/scheduler');
const HEADERS = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36',
'Accept': '*/*',
'Connection': 'keep-alive'
};
const CORS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*'
};
// RΓ©soudre une URL relative par rapport Γ  une base
const resolveUrl = (url, base) => {
if (!url) return '';
if (url.startsWith('http')) return url;
try { return new URL(url, base).href; }
catch { return url; }
};
// Encoder une URL en base64url pour la passer en paramètre
const encode = (url) => Buffer.from(url).toString('base64url');
const decode = (b64) => Buffer.from(b64, 'base64url').toString('utf8');
// ── GET /stream/live/playlist.m3u8 ──────────────────────────────────
// AppelΓ© Γ  chaque refresh HLS.js β†’ toujours lit la SOURCE ACTIVE du moment
router.get('/live/playlist.m3u8', async (req, res) => {
const sourceUrl = scheduler.getCurrentUrl();
if (!sourceUrl) {
return res.set(CORS)
.type('application/vnd.apple.mpegurl')
.status(503)
.send('#EXTM3U\n#EXT-X-VERSION:3\n# Aucune emission programmee');
}
try {
const response = await axios.get(sourceUrl, {
timeout: 12000,
headers: HEADERS,
responseType: 'text'
});
const m3u8 = response.data;
const baseUrl = sourceUrl.substring(0, sourceUrl.lastIndexOf('/') + 1);
// Réécrire le M3U8 :
// - Toutes les URLs de segments β†’ /stream/seg?u=BASE64URL
// - Les sous-playlists aussi (multi-quality)
const rewritten = m3u8.split('\n').map(line => {
const t = line.trim();
if (!t || t.startsWith('#')) return line;
const absolute = resolveUrl(t, baseUrl);
return `/stream/seg?u=${encode(absolute)}`;
}).join('\n');
res.set({
...CORS,
'Content-Type': 'application/vnd.apple.mpegurl',
'Cache-Control': 'no-cache, no-store, must-revalidate'
}).send(rewritten);
} catch (err) {
console.error('[proxy] M3U8 error:', err.message);
res.set(CORS)
.type('application/vnd.apple.mpegurl')
.status(502)
.send('#EXTM3U\n#EXT-X-VERSION:3\n# Source indisponible');
}
});
// ── GET /stream/seg?u=BASE64URL ──────────────────────────────────────
// Proxy transparent d'un segment TS ou d'une sous-playlist
router.get('/seg', async (req, res) => {
const { u } = req.query;
if (!u) return res.status(400).end();
let segUrl;
try { segUrl = decode(u); }
catch { return res.status(400).end(); }
// VΓ©rification de sΓ©curitΓ© : source active doit Γͺtre configurΓ©e
if (!scheduler.getCurrentUrl()) return res.status(403).end();
try {
const response = await axios.get(segUrl, {
timeout: 20000,
responseType: 'arraybuffer',
headers: HEADERS
});
const contentType = response.headers['content-type'] || 'video/MP2T';
// Si c'est une sous-playlist (multi-qualité), la réécrire aussi
if (contentType.includes('mpegurl') || segUrl.endsWith('.m3u8')) {
const text = Buffer.from(response.data).toString('utf8');
const baseUrl = segUrl.substring(0, segUrl.lastIndexOf('/') + 1);
const rewritten = text.split('\n').map(line => {
const t = line.trim();
if (!t || t.startsWith('#')) return line;
const absolute = resolveUrl(t, baseUrl);
return `/stream/seg?u=${encode(absolute)}`;
}).join('\n');
return res.set({ ...CORS, 'Content-Type': 'application/vnd.apple.mpegurl', 'Cache-Control': 'no-cache' })
.send(rewritten);
}
res.set({
...CORS,
'Content-Type': contentType,
'Cache-Control': 'no-cache',
'Content-Length': response.data.byteLength
}).send(Buffer.from(response.data));
} catch (err) {
console.error('[proxy] Segment error:', err.message);
res.status(502).end();
}
});
// ── GET /stream/live/info ────────────────────────────────────────────
router.get('/live/info', (req, res) => {
const info = scheduler.getCurrentInfo();
if (!info) return res.set(CORS).json({ active: false });
res.set(CORS).json({
active: true,
name: info.name,
streamUrl: '/stream/live/playlist.m3u8',
isManual: scheduler.isManual()
});
});
module.exports = router;