fix server.js
This commit is contained in:
parent
45f16f143a
commit
b7d34c9248
1 changed files with 184 additions and 0 deletions
184
server.js
Normal file
184
server.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* AeThex LIVE - Main Server
|
||||
* Next.js + Socket.io + YouTube Live Detector + RTMP ingest
|
||||
*
|
||||
* Port 3000: HTTP (Next.js + Socket.io)
|
||||
* Port 1935: RTMP ingest (node-media-server)
|
||||
* Port 8000: HLS output (node-media-server)
|
||||
*/
|
||||
|
||||
const { createServer } = require('http');
|
||||
const { Server } = require('socket.io');
|
||||
const next = require('next');
|
||||
const NodeMediaServer = require('node-media-server');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const app = next({ dev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
// ── YOUTUBE LIVE DETECTOR ──────────────────────────────────────────────────
|
||||
const YOUTUBE_CHANNEL_ID = 'UC22sQjrETb_YaTJqh36sxLQ';
|
||||
const YOUTUBE_API_KEY = process.env.YOUTUBE_API_KEY || null;
|
||||
const POLL_INTERVAL = 60000; // 60 seconds
|
||||
|
||||
let currentScene = 'offline';
|
||||
let liveVideoId = null;
|
||||
let io = null;
|
||||
|
||||
function checkYouTubeLive() {
|
||||
if (YOUTUBE_API_KEY) {
|
||||
const url = `https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=${YOUTUBE_CHANNEL_ID}&type=video&eventType=live&key=${YOUTUBE_API_KEY}`;
|
||||
https.get(url, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const wasLive = currentScene === 'live';
|
||||
if (json.items && json.items.length > 0) {
|
||||
liveVideoId = json.items[0].id.videoId;
|
||||
if (!wasLive) { console.log(`[DETECTOR] MrPiglr\u00a0is LIVE: ${liveVideoId}`); setScene('live'); }
|
||||
} else {
|
||||
liveVideoId = null;
|
||||
if (wasLive) { console.log('[DETECTOR] MrPiglr went offline'); setScene('offline'); }
|
||||
}
|
||||
} catch (e) { console.error('[DETECTOR] Parse error:', e.message); }
|
||||
});
|
||||
}).on('error', err => console.error('[DETECTOR] Request error:', err.message));
|
||||
} else {
|
||||
const options = { hostname: 'www.youtube.com', path: `/channel/${YOUTUBE_CHANNEL_ID}/live`, headers: { 'User-Agent': 'Mozilla/5.0' } };
|
||||
https.get(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
const wasLive = currentScene === 'live';
|
||||
const isLive = data.includes('"isLiveNow":true') || data.includes('"liveBroadcastContent":"live"');
|
||||
const vidMatch = data.match(/"videoId":"([^"]+)"/);
|
||||
if (isLive) {
|
||||
if (vidMatch) liveVideoId = vidMatch[1];
|
||||
if (!wasLive) { console.log('[DETECTOR] MrPiglr is LIVE (scraped)'); setScene('live'); }
|
||||
} else {
|
||||
liveVideoId = null;
|
||||
if (wasLive) { console.log('[DETECTOR] MrPiglr went offline (scraped)'); setScene('offline'); }
|
||||
}
|
||||
});
|
||||
}).on('error', err => console.error('[DETECTOR] Scrape error:', err.message));
|
||||
}
|
||||
}
|
||||
|
||||
function setScene(scene, triggeredBy = 'system') {
|
||||
const prev = currentScene;
|
||||
currentScene = scene;
|
||||
console.log(`[SCENE] ${prev} => ${scene} (by: ${triggeredBy})`);
|
||||
if (io) io.emit('scene:change', { scene, liveVideoId, triggeredBy, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
function processCommand(msg, socket, username) {
|
||||
if (!msg.startsWith('/')) return false;
|
||||
const parts = msg.trim().split(' ');
|
||||
const cmd = parts[0].toLowerCase();
|
||||
const args = parts.slice(1).join(' ');
|
||||
const commands = {
|
||||
'/music': () => setScene('music', username),
|
||||
'/kael': () => { setScene('kael', username); return { kaelQuery: args }; },
|
||||
'/passport': () => { setScene('passport', username); return { passportUser: args }; },
|
||||
'/oakdale': () => setScene('oakdale', username),
|
||||
'/forge': () => setScene('forge', username),
|
||||
'/live': () => setScene('live', username),
|
||||
'/offline': () => setScene('offline', username),
|
||||
'/help': () => null,
|
||||
};
|
||||
if (commands[cmd]) {
|
||||
commands[cmd]();
|
||||
if (cmd === '/kael' && args) io.emit('kael:query', { query: args, username });
|
||||
if (cmd === '/passport' && args) io.emit('passport:lookup', { username: args, requestedBy: username });
|
||||
io.emit('chat:message', { id: Date.now().toString(), username, message: msg, type: 'command', timestamp: Date.now() });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const mediaDir = path.join(__dirname, 'media');
|
||||
if (!fs.existsSync(mediaDir)) fs.mkdirSync(mediaDir, { recursive: true });
|
||||
|
||||
const nms = new NodeMediaServer({
|
||||
rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 30, ping_timeout: 60 },
|
||||
http: { port: 8000, mediaroot: mediaDir, allow_origin: '*', api: true },
|
||||
});
|
||||
|
||||
nms.on('prePublish', (id, StreamPath) => {
|
||||
console.log(`[RTMP] Stream started: ${StreamPath}`);
|
||||
setScene('live', 'obs-push');
|
||||
if (io) io.emit('rtmp:started', { streamPath: StreamPath });
|
||||
});
|
||||
|
||||
nms.on('donePublish', (id, StreamPath) => {
|
||||
console.log(`[RTMP] Stream ended: ${StreamPath}`);
|
||||
setScene('offline', 'obs-end');
|
||||
if (io) io.emit('rtmp:ended', {});
|
||||
});
|
||||
|
||||
app.prepare().then(() => {
|
||||
let viewerCount = 0;
|
||||
|
||||
const httpServer = createServer((req, res) => {
|
||||
if (req.method === 'POST' && req.url === '/_internal/scene') {
|
||||
if (req.headers['x-internal'] !== 'true') { res.writeHead(403); res.end(); return; }
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { scene } = JSON.parse(body);
|
||||
setScene(scene, 'api');
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: true, scene }));
|
||||
} catch { res.writeHead(400); res.end(); }
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.method === 'GET' && req.url === '/_internal/status') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ scene: currentScene, liveVideoId, viewers: viewerCount, uptime: process.uptime() }));
|
||||
return;
|
||||
}
|
||||
handle(req, res);
|
||||
});
|
||||
|
||||
io = new Server(httpServer, { cors: { origin: '*', methods: ['GET', 'POST'] } });
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
viewerCount++;
|
||||
io.emit('viewers:update', viewerCount);
|
||||
socket.emit('state:init', { scene: currentScene, liveVideoId, timestamp: Date.now() });
|
||||
socket.on('chat:send', ({ message, username }) => {
|
||||
if (!message || !username) return;
|
||||
if (!processCommand(message, socket, username)) {
|
||||
io.emit('chat:message', { id: Date.now().toString() + Math.random(), username, message, type: 'chat', timestamp: Date.now() });
|
||||
}
|
||||
});
|
||||
socket.on('scene:request', ({ scene, username }) => setScene(scene, username || 'viewer'));
|
||||
socket.on('kael:response', (data) => io.emit('kael:response', data));
|
||||
socket.on('disconnect', () => {
|
||||
viewerCount = Math.max(0, viewerCount - 1);
|
||||
io.emit('viewers:update', viewerCount);
|
||||
});
|
||||
});
|
||||
|
||||
nms.run();
|
||||
checkYouTubeLive();
|
||||
setInterval(checkYouTubeLive, POLL_INTERVAL);
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
httpServer.listen(PORT, () => {
|
||||
console.log('\n' + '═'.repeat(60));
|
||||
console.log(' 🔴 AeThex LIVE - Broadcast Network');
|
||||
console.log('═'.repeat(60));
|
||||
console.log(` HTTP: http://localhost:${PORT}`);
|
||||
console.log(` RTMP: rtmp://localhost:1935/live`);
|
||||
console.log(` Scene: ${currentScene}`);
|
||||
console.log('═'.repeat(60) + '\n');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue