nodejs-video-streamer/video-stream.js

95 lines
2.9 KiB
JavaScript

// video-stream.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const mime = require('mime-types'); // npm install mime-types
const app = express();
const PORT = 4002;
// CORS headers for Watchparty / browsers
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Range, Origin, X-Requested-With, Content-Type, Accept');
next();
});
// Ask user for video directory
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter full path to video directory: ', (videoDir) => {
videoDir = path.resolve(videoDir.trim());
if (!fs.existsSync(videoDir)) {
console.error('❌ Directory does not exist!');
process.exit(1);
}
const allowedExtensions = ['.mp4', '.mkv', '.webm', '.mov', '.avi', '.flv', '.ogg'];
const videos = fs.readdirSync(videoDir).filter(f =>
allowedExtensions.includes(path.extname(f).toLowerCase())
);
if (videos.length === 0) {
console.error('❌ No video files found!');
process.exit(1);
}
// Generate random serial numbers
const serialMap = {};
videos.forEach(video => {
const serial = Math.random().toString(36).slice(2, 10).toUpperCase();
serialMap[serial] = video;
// VPS domain URL (change if needed)
console.log(`🎥 ${video} -> Serial URL: https://stream.arulbalaji.xyz/videos/serial/${serial}`);
});
// Serve video by serial
app.get('/videos/serial/:code', (req, res) => {
const code = req.params.code;
const filename = serialMap[code];
if (!filename) return res.status(404).send('Invalid or expired serial');
const filePath = path.join(videoDir, filename);
if (!fs.existsSync(filePath)) return res.status(404).send('File missing');
const stat = fs.statSync(filePath);
const fileSize = stat.size;
const range = req.headers.range;
const contentType = mime.lookup(path.extname(filename)) || 'application/octet-stream';
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunkSize = (end - start) + 1;
const file = fs.createReadStream(filePath, { start, end });
res.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': contentType
});
file.pipe(res);
} else {
res.writeHead(200, {
'Content-Length': fileSize,
'Content-Type': contentType,
'Accept-Ranges': 'bytes'
});
fs.createReadStream(filePath).pipe(res);
}
});
app.listen(PORT, '127.0.0.1', () => {
console.log(`✅ Serial video server running on 127.0.0.1:${PORT}`);
});
rl.close();
});