diff --git a/server.js b/server.js index 2894228..8da0056 100644 --- a/server.js +++ b/server.js @@ -92,6 +92,17 @@ const progressMap = {}; const transcodeProcesses = new Map(); const wsSubscriptions = new Map(); const progressApiLogMap = new Map(); +const progressStateLogMap = new Map(); + +const setProgressState = (progressKey, key, nextState, reason = '') => { + progressMap[progressKey] = nextState; + const signature = `${nextState?.status || 'null'}:${typeof nextState?.percent === 'number' ? nextState.percent : 'na'}:${nextState?.streamSessionId || 'na'}`; + if (progressStateLogMap.get(progressKey) !== signature) { + progressStateLogMap.set(progressKey, signature); + console.log(`[progress-state] key=${key} status=${nextState?.status || 'null'} percent=${typeof nextState?.percent === 'number' ? nextState.percent : 'na'} reason=${reason || 'unspecified'} session=${nextState?.streamSessionId || 'na'}`); + } + return nextState; +}; const AVAILABLE_VIDEO_ENCODERS = [ { value: 'h264_rkmpp', label: 'h264_rkmpp (RKMPP H.264)' }, @@ -800,12 +811,12 @@ app.post('/api/stop-transcode', (req, res) => { return res.status(404).json({ error: 'No active transcode found for this key' }); } stopActiveTranscode(progressKey); - progressMap[progressKey] = { + setProgressState(progressKey, key, { status: 'cancelled', percent: 0, details: 'Transcode stopped by user', mp4Url: null - }; + }, 'stop-transcode'); broadcastWs(progressKey, { type: 'progress', key, progress: progressMap[progressKey] }); res.json({ message: 'Transcode stopped' }); } catch (error) { @@ -868,7 +879,7 @@ app.get('/api/stream', async (req, res) => { try { const replacedExistingStream = stopActiveTranscode(progressKey); if (replacedExistingStream && startSeconds > 0) { - progressMap[progressKey] = { + setProgressState(progressKey, key, { ...(progressMap[progressKey] || {}), status: 'transcoding', percent: Math.min(Math.max(Math.round((startSeconds / Math.max(progressMap[progressKey]?.duration || startSeconds || 1, 1)) * 100), 0), 100), @@ -876,7 +887,7 @@ app.get('/api/stream', async (req, res) => { streamSessionId, details: `Restarting transcode from ${startSeconds.toFixed(2)}s`, mp4Url: null - }; + }, 'restart-seek'); broadcastWs(progressKey, { type: 'progress', key, progress: progressMap[progressKey] }); } @@ -885,7 +896,7 @@ app.get('/api/stream', async (req, res) => { const cacheExists = fs.existsSync(downloadPath); if (!cacheExists) { - progressMap[progressKey] = { + setProgressState(progressKey, key, { status: 'downloading', percent: 0, downloadedBytes: 0, @@ -893,7 +904,7 @@ app.get('/api/stream', async (req, res) => { streamSessionId, details: 'Downloading full source before streaming...', mp4Url: null - }; + }, 'download-start'); broadcastWs(progressKey, { type: 'progress', key, progress: progressMap[progressKey] }); await ensureSourceCached({ @@ -915,12 +926,12 @@ app.get('/api/stream', async (req, res) => { details: totalBytes ? `Downloading source ${percent}%` : 'Downloading source...', mp4Url: null }; - progressMap[progressKey] = downloadState; + setProgressState(progressKey, key, downloadState, 'download-progress'); broadcastWs(progressKey, { type: 'progress', key, progress: downloadState }); } }); - progressMap[progressKey] = { + setProgressState(progressKey, key, { status: 'downloaded', percent: 100, downloadedBytes, @@ -928,14 +939,14 @@ app.get('/api/stream', async (req, res) => { streamSessionId, details: 'Source download complete, starting real-time transcode...', mp4Url: null - }; + }, 'download-complete'); broadcastWs(progressKey, { type: 'progress', key, progress: progressMap[progressKey] }); } else { const stats = fs.statSync(downloadPath); totalBytes = stats.size; downloadedBytes = totalBytes; console.log(`[download] cache ready bucket=${bucket} key=${key} path=${downloadPath} bytes=${totalBytes}`); - progressMap[progressKey] = { + setProgressState(progressKey, key, { status: 'downloaded', percent: 100, downloadedBytes, @@ -943,7 +954,7 @@ app.get('/api/stream', async (req, res) => { streamSessionId, details: 'Source already downloaded locally, starting real-time transcode...', mp4Url: null - }; + }, 'download-cache-hit'); broadcastWs(progressKey, { type: 'progress', key, progress: progressMap[progressKey] }); } @@ -953,11 +964,11 @@ app.get('/api/stream', async (req, res) => { const metadata = await probeFile(downloadPath); sourceMetadata = metadata; const duration = metadata.format?.duration || 0; - progressMap[progressKey] = { + setProgressState(progressKey, key, { ...(progressMap[progressKey] || {}), duration: parseFloat(duration) || 0, streamSessionId - }; + }, 'duration-probed'); broadcastWs(progressKey, { type: 'duration', key, duration: parseFloat(duration) }); } catch (probeErr) { console.error('Probe failed:', probeErr); @@ -1049,7 +1060,7 @@ app.get('/api/stream', async (req, res) => { details: `Streaming transcode ${percent}%`, mp4Url: null }; - progressMap[progressKey] = progressState; + setProgressState(progressKey, key, progressState, 'transcode-progress'); broadcastWs(progressKey, { type: 'progress', key, progress: progressState }); }) .on('stderr', (stderrLine) => { @@ -1064,7 +1075,7 @@ app.get('/api/stream', async (req, res) => { } console.log(`[transcode] complete bucket=${bucket} key=${key} encoder=${encoderName} cache=${convertPath}`); transcodeProcesses.delete(progressKey); - progressMap[progressKey] = { + setProgressState(progressKey, key, { status: 'finished', percent: 100, duration: progressMap[progressKey]?.duration || null, @@ -1072,7 +1083,7 @@ app.get('/api/stream', async (req, res) => { streamSessionId, details: 'Streaming transcode complete', mp4Url: null - }; + }, 'transcode-finished'); broadcastWs(progressKey, { type: 'progress', key, progress: progressMap[progressKey] }); }) .on('error', (err) => { @@ -1090,7 +1101,7 @@ app.get('/api/stream', async (req, res) => { details: err.message || 'Streaming transcode failed', mp4Url: null }; - progressMap[progressKey] = failedState; + setProgressState(progressKey, key, failedState, 'transcode-failed'); broadcastWs(progressKey, { type: 'progress', key, progress: failedState }); if (!res.headersSent) { res.status(500).json({ error: 'Failed to stream transcoded video', detail: err.message });