const express = require('express');
const axios = require('axios');
const app = express();
const PORT = process.env.PORT || 3000;
// Proxy endpoint for the playlist
app.get('/api/stream', async (req, res) => {
const { v, start, Policy, 'Key-Pair-Id': KeyPairId, Signature, quality } =
req.query;
// Check for required parameters
if (!v || !start || !Policy || !KeyPairId || !Signature) {
return res.status(400).json({ error: 'Missing required parameters.' });
}
const originalApiUrl = `https://live-to-rec.vercel.app/?v=$
{encodeURIComponent(v)}&start=${encodeURIComponent(start)}&Policy=$
{encodeURIComponent(Policy)}&Key-Pair-Id=$
{encodeURIComponent(KeyPairId)}&Signature=$
{encodeURIComponent(Signature)}&quality=${quality || ''}`;
try {
const response = await axios.get(originalApiUrl);
const playlist = response.data;
const rewrittenPlaylist = playlist.replace(/(https:\/\/[^\s]+)/g,
(originalUrl) => {
const extractedQuality = originalUrl.match(/quality=(\d+)/)?.[1] || '';
return `/api/segment?v=${encodeURIComponent(v)}&start=${start}&Policy=$
{encodeURIComponent(Policy)}&Key-Pair-Id=$
{encodeURIComponent(KeyPairId)}&Signature=$
{encodeURIComponent(Signature)}&quality=${extractedQuality}`;
});
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
res.send(rewrittenPlaylist);
} catch (error) {
console.error('Error fetching the playlist:', error.message);
res.status(500).json({ error: 'Failed to fetch the playlist.' });
}
});
// Proxy endpoint for video segments
app.get('/api/segment', async (req, res) => {
const { v, start, Policy, 'Key-Pair-Id': KeyPairId, Signature, quality } =
req.query;
// Check for required parameters
if (!v || !start || !Policy || !KeyPairId || !Signature || !quality) {
return res.status(400).json({ error: 'Missing required parameters.' });
}
const originalApiUrl = `https://live-to-rec.vercel.app/?v=$
{encodeURIComponent(v)}&start=${encodeURIComponent(start)}&Policy=$
{encodeURIComponent(Policy)}&Key-Pair-Id=$
{encodeURIComponent(KeyPairId)}&Signature=$
{encodeURIComponent(Signature)}&quality=${quality}`;
try {
const response = await axios.get(originalApiUrl, { responseType:
'stream' });
res.setHeader('Content-Type', response.headers['content-type']);
response.data.pipe(res);
} catch (error) {
console.error('Error fetching the segment:', error.message);
res.status(500).json({ error: 'Failed to fetch the segment.' });
}
});
// Video player webpage
app.get('/player', (req, res) => {
const { v, start, Policy, 'Key-Pair-Id': KeyPairId, Signature } = req.query;
// Check for required parameters
if (!v || !start || !Policy || !KeyPairId || !Signature) {
return res.status(400).send('Missing required parameters.');
}
const streamUrl = `/api/stream?v=${encodeURIComponent(v)}&start=$
{encodeURIComponent(start)}&Policy=${encodeURIComponent(Policy)}&Key-Pair-Id=$
{encodeURIComponent(KeyPairId)}&Signature=${encodeURIComponent(Signature)}`;
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Player</title>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="https://cdn.plyr.io/3.6.8/plyr.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script disable-devtool-auto="true" src="https://cdn.jsdelivr.net/npm/disable-
devtool" clear-log="true" disable-select="true" disable-copy="true" disable-
cut="true" disable-paste="true"></script>
<link rel="stylesheet" href="https://nexttoppers.github.io/plyr.css" />
<script src="https://cdn.tailwindcss.com/"></script>
<link rel="stylesheet" href="https://static.pw.live/fonts/reddit/font.css">
<link rel="stylesheet" href="https://nexttoppers.github.io/plyr1.css" />
<script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<style>
.container {
margin: 95px auto;
width: 720px;
}
video {
width: 100%;
}
/* Remove default blue overlay on focus */
.plyr__controls button:focus,
.plyr__control:focus-visible,
.plyr__control:focus {
outline: none !important;
box-shadow: none !important;
}
</style>
</head>
<body>
<div class='container'>
<span class="dots"></span>
<video controls crossorigin playsinline></video>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const controls = ['play-large', 'rewind', 'play', 'fast-forward',
'progress', 'current-time', 'duration', 'mute', 'volume', 'settings',
'fullscreen'];
const source = '${streamUrl}';
const video = document.querySelector('video');
const defaultOptions = {
controls,
captions: { active: true, update: false, language: 'auto' }
};
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(source);
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
const availableQualities = hls.levels.map((l) => l.height);
defaultOptions.quality = {
default: availableQualities[0],
options: availableQualities,
forced: true,
onChange: (e) => updateQuality(e),
};
const player = new Plyr(video, defaultOptions);
});
hls.attachMedia(video);
window.hls = hls;
} else {
const player = new Plyr(video, defaultOptions);
}
function updateQuality(newQuality) {
window.hls.levels.forEach((level, levelIndex) => {
if (level.height === newQuality) {
window.hls.currentLevel = levelIndex;
}
});
}
});
</script>
</body>
</html>
`;
res.send(html);
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});