This commit is contained in:
Dongho Kim
2026-06-22 17:56:12 +02:00
parent c92115253c
commit 456ff2db27
+46 -2
View File
@@ -325,7 +325,8 @@ const filterMasterPlaylist = async (m3u8Data, m3u8Url) => {
}; };
// Helper: processes an M3U8 playlist by converting any relative URLs to absolute, // Helper: processes an M3U8 playlist by converting any relative URLs to absolute,
// proxying sub-playlists (.m3u8), and proxying decryption keys. // proxying sub-playlists (.m3u8), proxying decryption keys, and proxying segment files (.m4s, .m4v)
// to bypass FFmpeg's strict extension security checks.
const processM3U8 = (m3u8Data, m3u8Url, baseUrl) => { const processM3U8 = (m3u8Data, m3u8Url, baseUrl) => {
const lines = m3u8Data.split('\n'); const lines = m3u8Data.split('\n');
const processedLines = lines.map(line => { const processedLines = lines.map(line => {
@@ -352,13 +353,17 @@ const processM3U8 = (m3u8Data, m3u8Url, baseUrl) => {
} }
if (uri.includes('.m3u8') && !uri.includes('key?url=')) { if (uri.includes('.m3u8') && !uri.includes('key?url=')) {
uri = `${baseUrl}/m3u8?url=${encodeURIComponent(uri)}`; uri = `${baseUrl}/m3u8?url=${encodeURIComponent(uri)}`;
} else if (uri.includes('.m4s') || uri.includes('.m4v')) {
// Proxy fragmented MP4 files to bypass FFmpeg's strict extension checks
const filename = uri.split('/').pop().split('?')[0].replace(/\.(m4s|m4v)$/, '.mp4');
uri = `${baseUrl}/segment/${filename}?url=${encodeURIComponent(uri)}`;
} }
return `URI="${uri}"`; return `URI="${uri}"`;
}); });
return processed; return processed;
} }
// 3. Rewrite main segment/variant playlist URLs to be absolute (and proxy variant playlists) // 3. Rewrite main segment/variant playlist URLs to be absolute (and proxy variant playlists/segments)
if (!processed.startsWith('#')) { if (!processed.startsWith('#')) {
let uri = processed; let uri = processed;
if (!uri.startsWith('http')) { if (!uri.startsWith('http')) {
@@ -366,6 +371,10 @@ const processM3U8 = (m3u8Data, m3u8Url, baseUrl) => {
} }
if (uri.includes('.m3u8')) { if (uri.includes('.m3u8')) {
uri = `${baseUrl}/m3u8?url=${encodeURIComponent(uri)}`; uri = `${baseUrl}/m3u8?url=${encodeURIComponent(uri)}`;
} else if (uri.includes('.m4s') || uri.includes('.m4v')) {
// Proxy fragmented MP4 files to bypass FFmpeg's strict extension checks
const filename = uri.split('/').pop().split('?')[0].replace(/\.(m4s|m4v)$/, '.mp4');
uri = `${baseUrl}/segment/${filename}?url=${encodeURIComponent(uri)}`;
} }
return uri; return uri;
} }
@@ -433,6 +442,41 @@ app.get('/m3u8', async (req, res) => {
} }
}); });
app.get('/segment/:filename', async (req, res) => {
const segmentUrl = req.query.url;
if (!segmentUrl) return res.status(400).send('Missing url parameter');
try {
const response = await axios.get(segmentUrl, {
headers: buildHeaders(),
responseType: 'stream',
timeout: 10000
});
if (response.headers['content-type']) {
res.header('Content-Type', response.headers['content-type']);
} else {
res.header('Content-Type', 'video/mp4');
}
if (response.headers['content-length']) {
res.header('Content-Length', response.headers['content-length']);
}
response.data.pipe(res);
req.on('close', () => {
if (response.data && typeof response.data.destroy === 'function') {
response.data.destroy();
}
});
} catch (error) {
console.error(`Error proxying segment: ${error.message}`);
if (!res.headersSent) {
res.status(500).send('Error proxying segment');
}
}
});
app.get('/key', async (req, res) => { app.get('/key', async (req, res) => {
const keyUrl = req.query.url; const keyUrl = req.query.url;
if (!keyUrl) return res.status(400).send('Missing url parameter'); if (!keyUrl) return res.status(400).send('Missing url parameter');