460 lines
17 KiB
JavaScript
460 lines
17 KiB
JavaScript
const express = require('express');
|
|
const { spawn } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { exec } = require('child_process');
|
|
|
|
const app = express();
|
|
const PORT = 8787;
|
|
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Function to get Ngrok URL from config.json
|
|
function getNgrokUrl() {
|
|
try {
|
|
const configPath = path.join(__dirname, 'config.json');
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
return config.ngrokUrl;
|
|
} catch (error) {
|
|
console.error('Error reading config file: ', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Get Ngrok URL
|
|
const ngrokUrl = getNgrokUrl();
|
|
|
|
if (!ngrokUrl) {
|
|
console.error('Ngrok URL is not defined. Please check the config.json file.');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Serve HLS files
|
|
const hlsDirectory = path.join(__dirname, 'hls');
|
|
app.use('/hls', express.static(hlsDirectory));
|
|
|
|
if (!fs.existsSync(hlsDirectory)) {
|
|
fs.mkdirSync(hlsDirectory);
|
|
}
|
|
|
|
let ffmpegPid = null;
|
|
|
|
// Function to delete HLS files
|
|
function deleteHLSFiles() {
|
|
fs.readdir(hlsDirectory, (err, files) => {
|
|
if (err) {
|
|
console.error('Error reading HLS directory for deletion:', err);
|
|
return;
|
|
}
|
|
|
|
const deletePromises = files.map(file => {
|
|
return new Promise((resolve, reject) => {
|
|
fs.unlink(path.join(hlsDirectory, file), err => {
|
|
if (err) {
|
|
console.error('Error deleting HLS file:', err);
|
|
return reject(err);
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
|
|
Promise.all(deletePromises)
|
|
.then(() => {
|
|
console.log('HLS files deleted successfully.');
|
|
})
|
|
.catch(err => {
|
|
console.error('Error deleting HLS files:', err);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Serve the home page with a form
|
|
app.get('/', (req, res) => {
|
|
// Check if HLS files exist
|
|
fs.readdir(hlsDirectory, (err, files) => {
|
|
const hlsFilesExist = files && files.length > 0;
|
|
const stopButtonEnabled = ffmpegPid !== null || hlsFilesExist;
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><<< bypassV >>></title> <!-- Updated Title -->
|
|
<style>
|
|
/* Base Styles */
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
box-sizing: border-box;
|
|
background-color: black; /* Set background to black */
|
|
color: white; /* Ensure text is visible on black background */
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
}
|
|
h1 {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
h6 {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 100%;
|
|
max-width: 400px;
|
|
margin: 0 auto 20px auto;
|
|
}
|
|
input[type="text"] {
|
|
width: 100%;
|
|
padding: 10px;
|
|
margin-bottom: 10px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
box-sizing: border-box;
|
|
font-size: 16px;
|
|
background-color: #333; /* Dark input background */
|
|
color: white; /* Input text color */
|
|
border: 1px solid #555; /* Darker border */
|
|
}
|
|
input[type="text"]::placeholder {
|
|
color: #bbb; /* Placeholder color */
|
|
}
|
|
button[type="submit"] {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background-color: #28a745;
|
|
color: white;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
max-width: 200px;
|
|
margin-bottom: 10px;
|
|
transition: background-color 0.3s;
|
|
font-size: 16px;
|
|
}
|
|
button[type="submit"]:hover {
|
|
background-color: #218838;
|
|
}
|
|
#stop-streaming {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background-color: #dc3545;
|
|
color: white;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
max-width: 200px;
|
|
margin-top: 10px;
|
|
transition: background-color 0.3s;
|
|
font-size: 16px;
|
|
}
|
|
#stop-streaming:disabled {
|
|
background-color: #6c757d;
|
|
cursor: not-allowed;
|
|
}
|
|
#stop-streaming:hover:not(:disabled) {
|
|
background-color: #c82333;
|
|
}
|
|
|
|
/* Media Queries */
|
|
@media (min-width: 600px) {
|
|
form {
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
}
|
|
input[type="text"] {
|
|
margin-right: 10px;
|
|
margin-bottom: 0;
|
|
}
|
|
button[type="submit"], #stop-streaming {
|
|
width: auto;
|
|
margin-top: 0;
|
|
}
|
|
#stop-streaming {
|
|
margin-left: 10px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1><<< bypassV >>></h1>
|
|
<h6>[restriction bypass v1.1]</h6>
|
|
<form action="/stream" method="POST">
|
|
<input type="text" name="videoUrl" placeholder="YouTube Video URL" required style="width: 300px;">
|
|
<button type="submit">Stream Video</button>
|
|
</form>
|
|
<div style="display: flex; justify-content: center; width: 100%; max-width: 400px;">
|
|
<button id="stop-streaming" ${stopButtonEnabled ? '' : 'disabled'}>Stop Streaming</button>
|
|
</div>
|
|
<script>
|
|
document.getElementById('stop-streaming').addEventListener('click', function() {
|
|
fetch('/stop', { method: 'POST' })
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
alert(data);
|
|
location.reload(); // Refresh the page after stopping
|
|
})
|
|
.catch(error => {
|
|
console.error('Error stopping streaming:', error);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`);
|
|
});
|
|
});
|
|
|
|
// Helper function to convert duration to seconds
|
|
function durationToSeconds(duration) {
|
|
const parts = duration.split(':').map(part => parseInt(part, 10));
|
|
if (parts.length === 3) {
|
|
return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
} else if (parts.length === 2) {
|
|
return parts[0] * 60 + parts[1];
|
|
} else if (parts.length === 1) {
|
|
return parts[0];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Handle the stream request
|
|
app.post('/stream', (req, res) => {
|
|
const videoUrl = req.body.videoUrl;
|
|
|
|
if (!videoUrl) {
|
|
return res.status(400).send('Error: No video URL provided.');
|
|
}
|
|
|
|
const ytdlpCommand = `yt-dlp --get-duration "${videoUrl}" && yt-dlp -f best -g "${videoUrl}"`;
|
|
exec(ytdlpCommand, (error, stdout, stderr) => {
|
|
if (error) {
|
|
console.error(`Error fetching video URL: ${error.message}`);
|
|
console.error(`yt-dlp stderr: ${stderr}`);
|
|
return res.status(500).send('Error fetching video URL.');
|
|
}
|
|
|
|
const output = stdout.trim().split('\n');
|
|
const durationStr = output[0];
|
|
const directVideoUrl = output[1];
|
|
|
|
if (!directVideoUrl) {
|
|
console.error('No direct video URL found.');
|
|
return res.status(500).send('Error fetching video URL.');
|
|
}
|
|
|
|
const durationSeconds = durationToSeconds(durationStr);
|
|
if (durationSeconds === 0) {
|
|
console.error('Invalid video duration.');
|
|
return res.status(500).send('Error processing video duration.');
|
|
}
|
|
|
|
// Clear existing HLS files before starting a new stream
|
|
deleteHLSFiles();
|
|
|
|
const ffmpegCommand = [
|
|
'-i', directVideoUrl,
|
|
'-c:v', 'libx264',
|
|
'-preset', 'fast',
|
|
'-crf', '22',
|
|
'-g', '60',
|
|
'-hls_time', '2',
|
|
'-hls_list_size', '0',
|
|
'-f', 'hls',
|
|
path.join(hlsDirectory, 'stream.m3u8')
|
|
];
|
|
|
|
const ffmpegProcess = spawn('ffmpeg', ffmpegCommand);
|
|
ffmpegPid = ffmpegProcess.pid;
|
|
|
|
ffmpegProcess.stdout.on('data', (data) => {
|
|
console.log(`ffmpeg stdout: ${data}`);
|
|
});
|
|
|
|
ffmpegProcess.stderr.on('data', (data) => {
|
|
console.error(`ffmpeg stderr: ${data}`);
|
|
});
|
|
|
|
ffmpegProcess.on('exit', (code, signal) => {
|
|
console.log(`ffmpeg process exited with code ${code} and signal ${signal}`);
|
|
ffmpegPid = null; // Reset ffmpegPid when streaming is done
|
|
// HLS files remain for potential cleanup
|
|
});
|
|
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>bypassv - Streaming</title> <!-- Updated Title -->
|
|
<style>
|
|
/* Base Styles */
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
background-color: black; /* Set background to black */
|
|
color: white; /* Ensure text is visible on black background */
|
|
min-height: 100vh;
|
|
}
|
|
h2 {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
#player {
|
|
width: 100%;
|
|
max-width: 800px;
|
|
height: auto;
|
|
aspect-ratio: 16 / 9; /* Default to landscape */
|
|
background-color: black;
|
|
}
|
|
.controls {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin-top: 10px;
|
|
width: 100%;
|
|
max-width: 800px;
|
|
}
|
|
.time-display {
|
|
margin-bottom: 10px;
|
|
font-size: 18px;
|
|
}
|
|
button {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background-color: #dc3545;
|
|
color: white;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
max-width: 200px;
|
|
margin-top: 10px;
|
|
transition: background-color 0.3s;
|
|
font-size: 16px;
|
|
}
|
|
button:hover {
|
|
background-color: #c82333;
|
|
}
|
|
|
|
/* Media Queries */
|
|
@media (max-width: 599px) {
|
|
#player {
|
|
aspect-ratio: 9 / 16; /* Portrait for mobile */
|
|
}
|
|
.time-display {
|
|
font-size: 16px;
|
|
}
|
|
button {
|
|
width: 90%;
|
|
max-width: none;
|
|
}
|
|
}
|
|
@media (min-width: 600px) {
|
|
.controls {
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
}
|
|
.time-display {
|
|
margin-bottom: 0;
|
|
margin-right: 20px;
|
|
}
|
|
button {
|
|
width: auto;
|
|
margin-top: 0;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>Streaming Video...</h2>
|
|
<div id="player"></div>
|
|
<div class="controls">
|
|
<div class="time-display">
|
|
<span id="current-time">0:00</span> / <span id="duration">${durationStr}</span>
|
|
</div>
|
|
<button id="stop-streaming">Stop Streaming</button>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/clappr@latest/dist/clappr.min.js"></script>
|
|
<script>
|
|
const player = new Clappr.Player({
|
|
source: "${ngrokUrl}/hls/stream.m3u8",
|
|
parentId: '#player',
|
|
autoPlay: true,
|
|
mute: false,
|
|
controls: true,
|
|
width: '100%',
|
|
height: '100%',
|
|
responsive: true
|
|
});
|
|
|
|
player.on(Clappr.Events.PLAYER_TIMEUPDATE, function() {
|
|
const currentTime = Math.floor(player.getCurrentTime());
|
|
const currentTimeFormatted = formatTime(currentTime);
|
|
document.getElementById('current-time').innerText = currentTimeFormatted;
|
|
});
|
|
|
|
function formatTime(seconds) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return mins + ':' + (secs < 10 ? '0' + secs : secs);
|
|
}
|
|
|
|
document.getElementById('stop-streaming').addEventListener('click', function() {
|
|
fetch('/stop', { method: 'POST' })
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
alert(data);
|
|
player.destroy();
|
|
window.location.href = '/'; // Redirect to home after stopping
|
|
})
|
|
.catch(error => {
|
|
console.error('Error stopping streaming:', error);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`);
|
|
});
|
|
});
|
|
|
|
// Stop streaming route
|
|
app.post('/stop', (req, res) => {
|
|
if (ffmpegPid) {
|
|
console.log(`Stopping ffmpeg process with PID: ${ffmpegPid}`);
|
|
exec(`kill -9 ${ffmpegPid}`, (error) => {
|
|
if (error) {
|
|
console.error(`Error stopping ffmpeg process: ${error.message}`);
|
|
return res.status(500).send('Error stopping stream.');
|
|
}
|
|
console.log('ffmpeg process stopped successfully.');
|
|
ffmpegPid = null; // Reset ffmpegPid
|
|
});
|
|
}
|
|
|
|
// Delete HLS files
|
|
deleteHLSFiles();
|
|
return res.send('Streaming has been stopped and HLS files deleted.');
|
|
});
|
|
|
|
// Start the server
|
|
app.listen(PORT, () => {
|
|
console.log(`Server is running at http://localhost:${PORT}`);
|
|
});
|
|
|