probe: fallback to basic TCP/UDP connectivity check when capture service is offline
This commit is contained in:
parent
8b57a9a35a
commit
4864db03f3
1 changed files with 76 additions and 9 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import express from 'express';
|
||||
import http from 'http';
|
||||
import net from 'net';
|
||||
import dgram from 'dgram';
|
||||
import pool from '../db/pool.js';
|
||||
import { requireAuth } from '../middleware/auth.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
|
@ -165,9 +167,9 @@ router.post('/', async (req, res, next) => {
|
|||
proxy_enabled: true,
|
||||
proxy_codec: 'h264',
|
||||
proxy_resolution: '1920x1080',
|
||||
proxy_video_bitrate: '8M',
|
||||
proxy_video_bitrate: '2M',
|
||||
proxy_audio_codec: 'aac',
|
||||
proxy_audio_bitrate: '192k',
|
||||
proxy_audio_bitrate: '128k',
|
||||
proxy_audio_channels: 2,
|
||||
proxy_container: 'mp4',
|
||||
};
|
||||
|
|
@ -331,10 +333,10 @@ router.post('/:id/start', async (req, res, next) => {
|
|||
`PROXY_ENABLED=${recorder.proxy_enabled !== false ? 'true' : 'false'}`,
|
||||
`PROXY_CODEC=${recorder.proxy_codec || 'h264'}`,
|
||||
`PROXY_RESOLUTION=${recorder.proxy_resolution || '1920x1080'}`,
|
||||
`PROXY_VIDEO_BITRATE=${recorder.proxy_video_bitrate || '8M'}`,
|
||||
`PROXY_VIDEO_BITRATE=${recorder.proxy_video_bitrate || '2M'}`,
|
||||
`PROXY_FRAMERATE=${recorder.proxy_framerate || ''}`,
|
||||
`PROXY_AUDIO_CODEC=${recorder.proxy_audio_codec || 'aac'}`,
|
||||
`PROXY_AUDIO_BITRATE=${recorder.proxy_audio_bitrate || '192k'}`,
|
||||
`PROXY_AUDIO_BITRATE=${recorder.proxy_audio_bitrate || '128k'}`,
|
||||
`PROXY_AUDIO_CHANNELS=${recorder.proxy_audio_channels ?? 2}`,
|
||||
`PROXY_CONTAINER=${recorder.proxy_container || 'mp4'}`,
|
||||
|
||||
|
|
@ -648,19 +650,84 @@ router.delete('/:id', async (req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
router.post('/probe', async (req, res, next) => {
|
||||
// POST /probe - Test source connectivity.
|
||||
// Tries the capture service first (full SRT/RTMP handshake + metadata).
|
||||
// Falls back to a basic TCP/UDP reachability check if capture is offline.
|
||||
router.post('/probe', async (req, res) => {
|
||||
const { source_type, url } = req.body || {};
|
||||
|
||||
// Try capture service first — it can do a real SRT/RTMP handshake
|
||||
try {
|
||||
const r = await fetch('http://capture:3001/capture/probe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(req.body || {}),
|
||||
signal: AbortSignal.timeout(15000),
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
const data = await r.json().catch(() => ({}));
|
||||
res.status(r.status).json(data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
return res.status(r.status).json(data);
|
||||
} catch (_) {
|
||||
// capture service not running — fall through to basic connectivity probe
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return res.json({
|
||||
reachable: false,
|
||||
mode: 'basic',
|
||||
note: 'Capture service offline. Provide a URL for connectivity check.',
|
||||
});
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try { parsed = new URL(url); } catch {
|
||||
return res.status(400).json({ error: 'Invalid URL' });
|
||||
}
|
||||
|
||||
const host = parsed.hostname;
|
||||
const proto = (parsed.protocol || '').replace(':', '').toLowerCase();
|
||||
const isUdp = proto === 'srt' || source_type === 'srt';
|
||||
const port = parseInt(parsed.port, 10) || (isUdp ? 9000 : 1935);
|
||||
|
||||
const reachable = await (isUdp ? probeUdp(host, port) : probeTcp(host, port));
|
||||
return res.json({
|
||||
reachable,
|
||||
mode: 'basic',
|
||||
note: `Capture service offline · ${isUdp ? 'UDP' : 'TCP'} connectivity check only`,
|
||||
...(reachable ? { source: `${host}:${port}` } : { error: `${host}:${port} did not respond` }),
|
||||
});
|
||||
});
|
||||
|
||||
function probeTcp(host, port) {
|
||||
return new Promise((resolve) => {
|
||||
const sock = new net.Socket();
|
||||
let done = false;
|
||||
const finish = (ok) => { if (!done) { done = true; sock.destroy(); resolve(ok); } };
|
||||
sock.setTimeout(4000);
|
||||
sock.connect(port, host, () => finish(true));
|
||||
sock.on('error', () => finish(false));
|
||||
sock.on('timeout', () => finish(false));
|
||||
});
|
||||
}
|
||||
|
||||
function probeUdp(host, port) {
|
||||
return new Promise((resolve) => {
|
||||
const sock = dgram.createSocket('udp4');
|
||||
let done = false;
|
||||
const finish = (ok) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
try { sock.close(); } catch (_) {}
|
||||
resolve(ok);
|
||||
};
|
||||
sock.on('error', () => finish(false));
|
||||
sock.send(Buffer.alloc(16, 0), 0, 16, port, host, (err) => {
|
||||
if (err) return finish(false);
|
||||
// If no ICMP port-unreachable arrives within 2.5s, assume port is open
|
||||
setTimeout(() => finish(true), 2500);
|
||||
});
|
||||
// Hard timeout — give up after 5s regardless
|
||||
setTimeout(() => finish(false), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
Loading…
Reference in a new issue