recorders route: accept full codec field set + node/port pinning
POST/PATCH now persist all new codec columns via a whitelist. /start forwards every codec setting to the capture container as an env var. The live-asset created during /start now uses the recorder's container ext (.mov vs .mp4 etc.) instead of always assuming .mov.
This commit is contained in:
parent
0efef0d81b
commit
4c65753358
1 changed files with 94 additions and 117 deletions
|
|
@ -48,7 +48,6 @@ function generateClipName(recorderName) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build Docker PortBindings and ExposedPorts for listener-mode recorders.
|
* Build Docker PortBindings and ExposedPorts for listener-mode recorders.
|
||||||
* Returns { portBindings, exposedPorts } — both empty objects for non-listener sources.
|
|
||||||
*/
|
*/
|
||||||
function buildPortConfig(sourceType, sourceConfig) {
|
function buildPortConfig(sourceType, sourceConfig) {
|
||||||
const portBindings = {};
|
const portBindings = {};
|
||||||
|
|
@ -71,15 +70,35 @@ function buildPortConfig(sourceType, sourceConfig) {
|
||||||
return { portBindings, exposedPorts };
|
return { portBindings, exposedPorts };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whitelist of recorder columns the API accepts on POST/PATCH. Keeping it
|
||||||
|
// explicit prevents accidental writes to status / container_id / timestamps.
|
||||||
|
const RECORDER_FIELDS = [
|
||||||
|
'name', 'source_type', 'source_config',
|
||||||
|
'recording_codec', 'recording_resolution',
|
||||||
|
'recording_video_bitrate', 'recording_framerate',
|
||||||
|
'recording_audio_codec', 'recording_audio_bitrate', 'recording_audio_channels',
|
||||||
|
'recording_container',
|
||||||
|
'proxy_enabled', 'proxy_codec', 'proxy_resolution',
|
||||||
|
'proxy_video_bitrate', 'proxy_framerate',
|
||||||
|
'proxy_audio_codec', 'proxy_audio_bitrate', 'proxy_audio_channels',
|
||||||
|
'proxy_container',
|
||||||
|
'project_id', 'node_id', 'device_index',
|
||||||
|
];
|
||||||
|
|
||||||
|
function pickRecorderFields(body) {
|
||||||
|
const out = {};
|
||||||
|
for (const k of RECORDER_FIELDS) {
|
||||||
|
if (body[k] !== undefined) out[k] = body[k];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// GET / - List all recorders
|
// GET / - List all recorders
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
'SELECT * FROM recorders ORDER BY created_at DESC'
|
'SELECT * FROM recorders ORDER BY created_at DESC'
|
||||||
);
|
);
|
||||||
// Annotate recording rows with the container's actual StartedAt + the
|
|
||||||
// pre-created live asset id so the UI can keep a stable elapsed timer
|
|
||||||
// and embed an HLS preview.
|
|
||||||
const rows = await Promise.all(result.rows.map(async (r) => {
|
const rows = await Promise.all(result.rows.map(async (r) => {
|
||||||
if (r.status === 'recording' && r.container_id) {
|
if (r.status === 'recording' && r.container_id) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -99,7 +118,6 @@ router.get('/', async (req, res, next) => {
|
||||||
return r;
|
return r;
|
||||||
}));
|
}));
|
||||||
res.json(rows);
|
res.json(rows);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
|
|
@ -108,57 +126,46 @@ router.get('/', async (req, res, next) => {
|
||||||
// POST / - Create a new recorder
|
// POST / - Create a new recorder
|
||||||
router.post('/', async (req, res, next) => {
|
router.post('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const fields = pickRecorderFields(req.body);
|
||||||
name,
|
|
||||||
source_type,
|
|
||||||
source_config,
|
|
||||||
recording_codec,
|
|
||||||
recording_resolution,
|
|
||||||
proxy_enabled,
|
|
||||||
proxy_codec,
|
|
||||||
proxy_resolution,
|
|
||||||
project_id,
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
if (!name || !source_type) {
|
if (!fields.name || !fields.source_type) {
|
||||||
return res
|
return res
|
||||||
.status(400)
|
.status(400)
|
||||||
.json({ error: 'Name and source_type are required' });
|
.json({ error: 'Name and source_type are required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = uuidv4();
|
// Defaults — written on insert so the DB row is always self-contained.
|
||||||
|
const defaults = {
|
||||||
|
source_config: {},
|
||||||
|
recording_codec: 'prores_hq',
|
||||||
|
recording_resolution: 'native',
|
||||||
|
recording_audio_codec: 'pcm_s24le',
|
||||||
|
recording_audio_channels: 2,
|
||||||
|
recording_container: 'mov',
|
||||||
|
proxy_enabled: true,
|
||||||
|
proxy_codec: 'h264',
|
||||||
|
proxy_resolution: '1920x1080',
|
||||||
|
proxy_video_bitrate: '8M',
|
||||||
|
proxy_audio_codec: 'aac',
|
||||||
|
proxy_audio_bitrate: '192k',
|
||||||
|
proxy_audio_channels: 2,
|
||||||
|
proxy_container: 'mp4',
|
||||||
|
};
|
||||||
|
const row = { id: uuidv4(), status: 'stopped', ...defaults, ...fields };
|
||||||
|
|
||||||
|
// Build INSERT dynamically so adding columns later means one place to update.
|
||||||
|
const cols = Object.keys(row);
|
||||||
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(', ');
|
||||||
|
const values = cols.map(k => {
|
||||||
|
const v = row[k];
|
||||||
|
if (k === 'source_config') return v && typeof v === 'object' ? v : {};
|
||||||
|
return v;
|
||||||
|
});
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
`INSERT INTO recorders (
|
`INSERT INTO recorders (${cols.join(', ')}, created_at, updated_at)
|
||||||
id,
|
VALUES (${placeholders}, NOW(), NOW())
|
||||||
name,
|
RETURNING *`,
|
||||||
source_type,
|
values
|
||||||
source_config,
|
|
||||||
recording_codec,
|
|
||||||
recording_resolution,
|
|
||||||
proxy_enabled,
|
|
||||||
proxy_codec,
|
|
||||||
proxy_resolution,
|
|
||||||
project_id,
|
|
||||||
status,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW(), NOW())
|
|
||||||
RETURNING *`,
|
|
||||||
[
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
source_type,
|
|
||||||
source_config || {},
|
|
||||||
recording_codec || 'prores_hq',
|
|
||||||
recording_resolution || 'native',
|
|
||||||
proxy_enabled !== false,
|
|
||||||
proxy_codec || 'libx264',
|
|
||||||
proxy_resolution || '1920x1080',
|
|
||||||
project_id || null,
|
|
||||||
'stopped',
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(201).json(result.rows[0]);
|
res.status(201).json(result.rows[0]);
|
||||||
|
|
@ -206,41 +213,18 @@ router.patch('/:id', async (req, res, next) => {
|
||||||
return res.status(409).json({ error: 'Cannot edit a recorder while it is recording — stop it first' });
|
return res.status(409).json({ error: 'Cannot edit a recorder while it is recording — stop it first' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const fields = pickRecorderFields(req.body);
|
||||||
name,
|
const cols = Object.keys(fields);
|
||||||
source_type,
|
if (cols.length === 0) {
|
||||||
source_config,
|
|
||||||
recording_codec,
|
|
||||||
recording_resolution,
|
|
||||||
proxy_enabled,
|
|
||||||
proxy_codec,
|
|
||||||
proxy_resolution,
|
|
||||||
project_id,
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const updates = [];
|
|
||||||
const params = [];
|
|
||||||
let n = 1;
|
|
||||||
|
|
||||||
if (name !== undefined) { updates.push(`name = $${n++}`); params.push(name); }
|
|
||||||
if (source_type !== undefined) { updates.push(`source_type = $${n++}`); params.push(source_type); }
|
|
||||||
if (source_config !== undefined) { updates.push(`source_config = $${n++}`); params.push(source_config); }
|
|
||||||
if (recording_codec !== undefined) { updates.push(`recording_codec = $${n++}`); params.push(recording_codec); }
|
|
||||||
if (recording_resolution !== undefined){ updates.push(`recording_resolution = $${n++}`); params.push(recording_resolution); }
|
|
||||||
if (proxy_enabled !== undefined) { updates.push(`proxy_enabled = $${n++}`); params.push(proxy_enabled); }
|
|
||||||
if (proxy_codec !== undefined) { updates.push(`proxy_codec = $${n++}`); params.push(proxy_codec); }
|
|
||||||
if (proxy_resolution !== undefined) { updates.push(`proxy_resolution = $${n++}`); params.push(proxy_resolution); }
|
|
||||||
if (project_id !== undefined) { updates.push(`project_id = $${n++}`); params.push(project_id || null); }
|
|
||||||
|
|
||||||
if (updates.length === 0) {
|
|
||||||
return res.status(400).json({ error: 'No fields to update' });
|
return res.status(400).json({ error: 'No fields to update' });
|
||||||
}
|
}
|
||||||
|
|
||||||
updates.push(`updated_at = NOW()`);
|
const setClause = cols.map((k, i) => `${k} = $${i + 1}`).join(', ');
|
||||||
|
const params = cols.map(k => fields[k]);
|
||||||
params.push(id);
|
params.push(id);
|
||||||
|
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
`UPDATE recorders SET ${updates.join(', ')} WHERE id = $${n} RETURNING *`,
|
`UPDATE recorders SET ${setClause}, updated_at = NOW() WHERE id = $${params.length} RETURNING *`,
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -255,7 +239,6 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
// Get recorder config from DB
|
|
||||||
const recorderResult = await pool.query(
|
const recorderResult = await pool.query(
|
||||||
'SELECT * FROM recorders WHERE id = $1',
|
'SELECT * FROM recorders WHERE id = $1',
|
||||||
[id]
|
[id]
|
||||||
|
|
@ -271,7 +254,6 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
return res.status(400).json({ error: 'Recorder is already recording' });
|
return res.status(400).json({ error: 'Recorder is already recording' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get S3 config from environment
|
|
||||||
const s3Endpoint = process.env.S3_ENDPOINT;
|
const s3Endpoint = process.env.S3_ENDPOINT;
|
||||||
const s3Bucket = process.env.S3_BUCKET;
|
const s3Bucket = process.env.S3_BUCKET;
|
||||||
const s3AccessKey = process.env.S3_ACCESS_KEY;
|
const s3AccessKey = process.env.S3_ACCESS_KEY;
|
||||||
|
|
@ -279,35 +261,32 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
const mamApiUrl = process.env.MAM_API_URL || 'http://mam-api:3000';
|
const mamApiUrl = process.env.MAM_API_URL || 'http://mam-api:3000';
|
||||||
const dockerNetwork = process.env.DOCKER_NETWORK || 'wild-dragon_wild-dragon';
|
const dockerNetwork = process.env.DOCKER_NETWORK || 'wild-dragon_wild-dragon';
|
||||||
|
|
||||||
// Generate clip name with timestamp
|
|
||||||
const clipName = generateClipName(recorder.name);
|
const clipName = generateClipName(recorder.name);
|
||||||
|
|
||||||
// live-asset: create the asset row right now (status='live') so the library
|
// live-asset: create the asset row right now (status='live') so the
|
||||||
// shows the recording while it is happening. The capture container will
|
// library shows the recording while it is happening.
|
||||||
// tee an HLS stream into /live/<assetId>/.
|
|
||||||
const assetIdLive = uuidv4();
|
const assetIdLive = uuidv4();
|
||||||
try {
|
try {
|
||||||
|
const ext = recorder.recording_container || 'mov';
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO assets (
|
`INSERT INTO assets (
|
||||||
id, project_id, bin_id, filename, display_name, status, media_type,
|
id, project_id, bin_id, filename, display_name, status, media_type,
|
||||||
original_s3_key, created_at, updated_at
|
original_s3_key, created_at, updated_at
|
||||||
) VALUES ($1, $2, NULL, $3, $3, 'live', 'video', $4, NOW(), NOW())`,
|
) VALUES ($1, $2, NULL, $3, $3, 'live', 'video', $4, NOW(), NOW())`,
|
||||||
[assetIdLive, recorder.project_id, clipName, `projects/${recorder.project_id}/masters/${clipName}.mov`]
|
[assetIdLive, recorder.project_id, clipName, `projects/${recorder.project_id}/masters/${clipName}.${ext}`]
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[recorders] could not pre-create live asset:', e.message);
|
console.warn('[recorders] could not pre-create live asset:', e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine source config and whether this is a listener-mode recorder
|
|
||||||
const sourceConfig = recorder.source_config || {};
|
const sourceConfig = recorder.source_config || {};
|
||||||
const isListener = sourceConfig.mode === 'listener';
|
const isListener = sourceConfig.mode === 'listener';
|
||||||
const sourceType = recorder.source_type;
|
const sourceType = recorder.source_type;
|
||||||
|
const deviceIndex = recorder.device_index ?? sourceConfig.device ?? 0;
|
||||||
|
|
||||||
// Build port bindings for listener-mode SRT/RTMP containers
|
|
||||||
const { portBindings, exposedPorts } = buildPortConfig(sourceType, sourceConfig);
|
const { portBindings, exposedPorts } = buildPortConfig(sourceType, sourceConfig);
|
||||||
|
|
||||||
// Build container environment — pass all source params so the capture
|
// Build container env — all codec controls flow through here.
|
||||||
// service can auto-start recording on container startup
|
|
||||||
const env = [
|
const env = [
|
||||||
`S3_ENDPOINT=${s3Endpoint}`,
|
`S3_ENDPOINT=${s3Endpoint}`,
|
||||||
`S3_BUCKET=${s3Bucket}`,
|
`S3_BUCKET=${s3Bucket}`,
|
||||||
|
|
@ -318,17 +297,34 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
`RECORDER_ID=${id}`,
|
`RECORDER_ID=${id}`,
|
||||||
`SOURCE_TYPE=${sourceType}`,
|
`SOURCE_TYPE=${sourceType}`,
|
||||||
`SOURCE_CONFIG=${JSON.stringify(sourceConfig)}`,
|
`SOURCE_CONFIG=${JSON.stringify(sourceConfig)}`,
|
||||||
`RECORDING_CODEC=${recorder.recording_codec}`,
|
`DEVICE_INDEX=${deviceIndex}`,
|
||||||
`RECORDING_RESOLUTION=${recorder.recording_resolution}`,
|
|
||||||
`PROXY_ENABLED=${recorder.proxy_enabled}`,
|
// Recording codec controls
|
||||||
`PROXY_CODEC=${recorder.proxy_codec}`,
|
`RECORDING_CODEC=${recorder.recording_codec || 'prores_hq'}`,
|
||||||
`PROXY_RESOLUTION=${recorder.proxy_resolution}`,
|
`RECORDING_RESOLUTION=${recorder.recording_resolution || 'native'}`,
|
||||||
|
`RECORDING_VIDEO_BITRATE=${recorder.recording_video_bitrate || ''}`,
|
||||||
|
`RECORDING_FRAMERATE=${recorder.recording_framerate || ''}`,
|
||||||
|
`RECORDING_AUDIO_CODEC=${recorder.recording_audio_codec || 'pcm_s24le'}`,
|
||||||
|
`RECORDING_AUDIO_BITRATE=${recorder.recording_audio_bitrate || ''}`,
|
||||||
|
`RECORDING_AUDIO_CHANNELS=${recorder.recording_audio_channels ?? 2}`,
|
||||||
|
`RECORDING_CONTAINER=${recorder.recording_container || 'mov'}`,
|
||||||
|
|
||||||
|
// Proxy codec controls
|
||||||
|
`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_FRAMERATE=${recorder.proxy_framerate || ''}`,
|
||||||
|
`PROXY_AUDIO_CODEC=${recorder.proxy_audio_codec || 'aac'}`,
|
||||||
|
`PROXY_AUDIO_BITRATE=${recorder.proxy_audio_bitrate || '192k'}`,
|
||||||
|
`PROXY_AUDIO_CHANNELS=${recorder.proxy_audio_channels ?? 2}`,
|
||||||
|
`PROXY_CONTAINER=${recorder.proxy_container || 'mp4'}`,
|
||||||
|
|
||||||
`PROJECT_ID=${recorder.project_id}`,
|
`PROJECT_ID=${recorder.project_id}`,
|
||||||
`CLIP_NAME=${clipName}`,
|
`CLIP_NAME=${clipName}`,
|
||||||
`ASSET_ID=${assetIdLive}`,
|
`ASSET_ID=${assetIdLive}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add source-specific env vars for SRT/RTMP
|
|
||||||
if (sourceType === 'srt' || sourceType === 'rtmp') {
|
if (sourceType === 'srt' || sourceType === 'rtmp') {
|
||||||
env.push(`LISTEN=${isListener ? '1' : '0'}`);
|
env.push(`LISTEN=${isListener ? '1' : '0'}`);
|
||||||
if (isListener) {
|
if (isListener) {
|
||||||
|
|
@ -341,8 +337,6 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build container config
|
|
||||||
// Stable network alias so mam-api can fetch live capture status by id
|
|
||||||
const alias = `recorder-${id}`;
|
const alias = `recorder-${id}`;
|
||||||
const containerConfig = {
|
const containerConfig = {
|
||||||
Image: 'wild-dragon-capture:latest',
|
Image: 'wild-dragon-capture:latest',
|
||||||
|
|
@ -362,7 +356,6 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
Hostname: alias,
|
Hostname: alias,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create container
|
|
||||||
const createRes = await dockerApi('POST', '/containers/create', containerConfig);
|
const createRes = await dockerApi('POST', '/containers/create', containerConfig);
|
||||||
|
|
||||||
if (createRes.status !== 201) {
|
if (createRes.status !== 201) {
|
||||||
|
|
@ -373,8 +366,6 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerId = createRes.data.Id;
|
const containerId = createRes.data.Id;
|
||||||
|
|
||||||
// Start container
|
|
||||||
const startRes = await dockerApi('POST', `/containers/${containerId}/start`);
|
const startRes = await dockerApi('POST', `/containers/${containerId}/start`);
|
||||||
|
|
||||||
if (startRes.status !== 204) {
|
if (startRes.status !== 204) {
|
||||||
|
|
@ -384,7 +375,6 @@ router.post('/:id/start', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update recorder in DB
|
|
||||||
const updateResult = await pool.query(
|
const updateResult = await pool.query(
|
||||||
`UPDATE recorders
|
`UPDATE recorders
|
||||||
SET container_id = $1, status = $2, current_session_id = $3, updated_at = NOW()
|
SET container_id = $1, status = $2, current_session_id = $3, updated_at = NOW()
|
||||||
|
|
@ -404,7 +394,6 @@ router.post('/:id/stop', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
// Get recorder from DB
|
|
||||||
const recorderResult = await pool.query(
|
const recorderResult = await pool.query(
|
||||||
'SELECT * FROM recorders WHERE id = $1',
|
'SELECT * FROM recorders WHERE id = $1',
|
||||||
[id]
|
[id]
|
||||||
|
|
@ -420,13 +409,11 @@ router.post('/:id/stop', async (req, res, next) => {
|
||||||
return res.status(400).json({ error: 'No container running' });
|
return res.status(400).json({ error: 'No container running' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop container
|
|
||||||
const stopRes = await dockerApi(
|
const stopRes = await dockerApi(
|
||||||
'POST',
|
'POST',
|
||||||
`/containers/${recorder.container_id}/stop`
|
`/containers/${recorder.container_id}/stop`
|
||||||
);
|
);
|
||||||
|
|
||||||
// 204 = stopped, 304 = already stopped — both are acceptable
|
|
||||||
if (stopRes.status !== 204 && stopRes.status !== 304) {
|
if (stopRes.status !== 204 && stopRes.status !== 304) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: 'Failed to stop container',
|
error: 'Failed to stop container',
|
||||||
|
|
@ -434,7 +421,6 @@ router.post('/:id/stop', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove container — 204 = removed, 404 = already gone (both acceptable)
|
|
||||||
const removeRes = await dockerApi(
|
const removeRes = await dockerApi(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
`/containers/${recorder.container_id}`
|
`/containers/${recorder.container_id}`
|
||||||
|
|
@ -447,7 +433,6 @@ router.post('/:id/stop', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update recorder in DB
|
|
||||||
const updateResult = await pool.query(
|
const updateResult = await pool.query(
|
||||||
`UPDATE recorders
|
`UPDATE recorders
|
||||||
SET container_id = NULL, status = $1, updated_at = NOW()
|
SET container_id = NULL, status = $1, updated_at = NOW()
|
||||||
|
|
@ -467,7 +452,6 @@ router.get('/:id/status', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
// Get recorder from DB
|
|
||||||
const recorderResult = await pool.query(
|
const recorderResult = await pool.query(
|
||||||
'SELECT * FROM recorders WHERE id = $1',
|
'SELECT * FROM recorders WHERE id = $1',
|
||||||
[id]
|
[id]
|
||||||
|
|
@ -487,7 +471,6 @@ router.get('/:id/status', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query Docker API for container status
|
|
||||||
const inspectRes = await dockerApi(
|
const inspectRes = await dockerApi(
|
||||||
'GET',
|
'GET',
|
||||||
`/containers/${recorder.container_id}/json`
|
`/containers/${recorder.container_id}/json`
|
||||||
|
|
@ -506,7 +489,6 @@ router.get('/:id/status', async (req, res, next) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const duration = Math.floor((now - startedAt) / 1000);
|
const duration = Math.floor((now - startedAt) / 1000);
|
||||||
|
|
||||||
// Try to fetch live signal from the recorder's capture sidecar via network alias
|
|
||||||
let signal = container.State.Running ? 'receiving' : 'stopped';
|
let signal = container.State.Running ? 'receiving' : 'stopped';
|
||||||
let signalKnown = false;
|
let signalKnown = false;
|
||||||
let live = null;
|
let live = null;
|
||||||
|
|
@ -516,9 +498,7 @@ router.get('/:id/status', async (req, res, next) => {
|
||||||
live = await captureRes.json();
|
live = await captureRes.json();
|
||||||
if (live && live.signal) { signal = live.signal; signalKnown = true; }
|
if (live && live.signal) { signal = live.signal; signalKnown = true; }
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) { /* not ready yet */ }
|
||||||
// Container may not be ready yet, or alias hasn't propagated. Leave signal as default.
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
status: container.State.Running ? 'recording' : 'stopped',
|
status: container.State.Running ? 'recording' : 'stopped',
|
||||||
|
|
@ -527,9 +507,9 @@ router.get('/:id/status', async (req, res, next) => {
|
||||||
signal,
|
signal,
|
||||||
signalKnown,
|
signalKnown,
|
||||||
framesReceived: live ? live.framesReceived : null,
|
framesReceived: live ? live.framesReceived : null,
|
||||||
currentFps: live ? live.currentFps : null,
|
currentFps: live ? live.currentFps : null,
|
||||||
lastFrameAt: live ? live.lastFrameAt : null,
|
lastFrameAt: live ? live.lastFrameAt : null,
|
||||||
lastError: live ? live.lastError : null,
|
lastError: live ? live.lastError : null,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
|
|
@ -541,7 +521,6 @@ router.delete('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
// Get recorder from DB
|
|
||||||
const recorderResult = await pool.query(
|
const recorderResult = await pool.query(
|
||||||
'SELECT * FROM recorders WHERE id = $1',
|
'SELECT * FROM recorders WHERE id = $1',
|
||||||
[id]
|
[id]
|
||||||
|
|
@ -553,7 +532,6 @@ router.delete('/:id', async (req, res, next) => {
|
||||||
|
|
||||||
const recorder = recorderResult.rows[0];
|
const recorder = recorderResult.rows[0];
|
||||||
|
|
||||||
// If recording, stop the container first
|
|
||||||
if (recorder.container_id) {
|
if (recorder.container_id) {
|
||||||
try {
|
try {
|
||||||
await dockerApi('POST', `/containers/${recorder.container_id}/stop`);
|
await dockerApi('POST', `/containers/${recorder.container_id}/stop`);
|
||||||
|
|
@ -563,7 +541,6 @@ router.delete('/:id', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete from DB
|
|
||||||
const deleteResult = await pool.query(
|
const deleteResult = await pool.query(
|
||||||
'DELETE FROM recorders WHERE id = $1 RETURNING *',
|
'DELETE FROM recorders WHERE id = $1 RETURNING *',
|
||||||
[id]
|
[id]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue