修复不显示清空缓存按钮的BUG
This commit is contained in:
@@ -463,6 +463,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
seekToTime(targetTime);
|
seekToTime(targetTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const seekToTime = (targetTime) => {
|
||||||
|
if (!isStreamActive || videoDuration <= 0) return;
|
||||||
|
const clampedTime = Math.max(0, Math.min(targetTime, videoDuration));
|
||||||
|
console.log(`[Seek] Seeking to ${clampedTime.toFixed(2)}s / ${videoDuration.toFixed(2)}s`);
|
||||||
|
|
||||||
|
if (hlsInstance) {
|
||||||
|
// For HLS: seek within the current buffer if possible
|
||||||
|
const relativeTime = clampedTime - seekOffset;
|
||||||
|
if (relativeTime >= 0 && relativeTime <= (videoPlayer.duration || 0)) {
|
||||||
|
videoPlayer.currentTime = relativeTime;
|
||||||
|
} else {
|
||||||
|
// Need server-side restart from new position
|
||||||
|
seekOffset = clampedTime;
|
||||||
|
const streamUrl = buildHlsPlaylistUrl();
|
||||||
|
hlsInstance.destroy();
|
||||||
|
hlsInstance = new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60 });
|
||||||
|
hlsInstance.loadSource(streamUrl);
|
||||||
|
hlsInstance.attachMedia(videoPlayer);
|
||||||
|
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
videoPlayer.play().catch(() => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
videoPlayer.currentTime = clampedTime;
|
||||||
|
}
|
||||||
|
updateSeekBarPosition(clampedTime);
|
||||||
|
};
|
||||||
|
|
||||||
if (seekBar) {
|
if (seekBar) {
|
||||||
seekBar.addEventListener('mousedown', handleSeekStart);
|
seekBar.addEventListener('mousedown', handleSeekStart);
|
||||||
document.addEventListener('mousemove', handleSeekMove);
|
document.addEventListener('mousemove', handleSeekMove);
|
||||||
@@ -898,10 +926,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
hlsInstance.loadSource(streamUrl);
|
hlsInstance.loadSource(streamUrl);
|
||||||
hlsInstance.attachMedia(videoPlayer);
|
hlsInstance.attachMedia(videoPlayer);
|
||||||
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
console.log('[HLS] MANIFEST_PARSED - starting playback');
|
||||||
transcodingOverlay.classList.add('hidden');
|
transcodingOverlay.classList.add('hidden');
|
||||||
videoPlayer.classList.remove('hidden');
|
videoPlayer.classList.remove('hidden');
|
||||||
isStreamActive = true;
|
isStreamActive = true;
|
||||||
videoPlayer.play().catch(() => { });
|
videoPlayer.play().catch((e) => {
|
||||||
|
console.warn('[HLS] Autoplay blocked:', e.message);
|
||||||
|
});
|
||||||
showSeekBar();
|
showSeekBar();
|
||||||
showCustomControls();
|
showCustomControls();
|
||||||
updatePlayControls();
|
updatePlayControls();
|
||||||
@@ -910,15 +941,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
schedulePlaybackChromeHide();
|
schedulePlaybackChromeHide();
|
||||||
});
|
});
|
||||||
hlsInstance.on(Hls.Events.ERROR, function (event, data) {
|
hlsInstance.on(Hls.Events.ERROR, function (event, data) {
|
||||||
|
console.error('[HLS] Error:', data.type, data.details, data.fatal ? '(FATAL)' : '', data);
|
||||||
if (data.fatal) {
|
if (data.fatal) {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.warn('[HLS] Fatal network error, attempting recovery...');
|
||||||
hlsInstance.startLoad();
|
hlsInstance.startLoad();
|
||||||
break;
|
break;
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.warn('[HLS] Fatal media error, attempting recovery...');
|
||||||
hlsInstance.recoverMediaError();
|
hlsInstance.recoverMediaError();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
console.error('[HLS] Fatal error, destroying instance');
|
||||||
hlsInstance.destroy();
|
hlsInstance.destroy();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
100
server.js
100
server.js
@@ -157,6 +157,20 @@ const AVAILABLE_VIDEO_DECODERS = [
|
|||||||
|
|
||||||
const getProgressKey = (key) => key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_')).join('/');
|
const getProgressKey = (key) => key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_')).join('/');
|
||||||
|
|
||||||
|
const makeSafeName = (name) => name.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
||||||
|
|
||||||
|
const getHlsCacheDir = (bucket, key) => {
|
||||||
|
const safeBucket = makeSafeName(bucket);
|
||||||
|
const safeKey = key.split('/').map(makeSafeName).join('-');
|
||||||
|
return path.join(CACHE_DIR, `hls-${safeBucket}-${safeKey}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInputCachePath = (bucket, key) => {
|
||||||
|
const safeBucket = makeSafeName(bucket);
|
||||||
|
const safeKey = key.split('/').map(makeSafeName).join('-');
|
||||||
|
return path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKey}.tmp`);
|
||||||
|
};
|
||||||
|
|
||||||
const createStreamSessionId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
const createStreamSessionId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
||||||
|
|
||||||
const addWsClient = (progressKey, ws) => {
|
const addWsClient = (progressKey, ws) => {
|
||||||
@@ -276,9 +290,13 @@ const shouldRetryWithSoftware = (message) => {
|
|||||||
return /Cannot load libcuda\.so\.1|Could not open encoder before EOF|Error while opening encoder|Operation not permitted|Invalid argument|mpp_create|rkmpp/i.test(message);
|
return /Cannot load libcuda\.so\.1|Could not open encoder before EOF|Error while opening encoder|Operation not permitted|Invalid argument|mpp_create|rkmpp/i.test(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
const probeFile = (filePath) => {
|
const probeFile = (filePath, timeoutMs = 15000) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
reject(new Error(`ffprobe timed out after ${timeoutMs}ms for ${filePath}`));
|
||||||
|
}, timeoutMs);
|
||||||
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
||||||
|
clearTimeout(timer);
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else resolve(metadata);
|
else resolve(metadata);
|
||||||
});
|
});
|
||||||
@@ -308,6 +326,12 @@ const parseTimemarkToSeconds = (timemark) => {
|
|||||||
return (hours * 3600) + (minutes * 60) + seconds;
|
return (hours * 3600) + (minutes * 60) + seconds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sanitizeNumber = (value) => {
|
||||||
|
if (value === null || value === undefined) return null;
|
||||||
|
const num = Number(value);
|
||||||
|
return Number.isFinite(num) ? num : null;
|
||||||
|
};
|
||||||
|
|
||||||
const stopActiveTranscode = (progressKey) => {
|
const stopActiveTranscode = (progressKey) => {
|
||||||
const activeProcess = transcodeProcesses.get(progressKey);
|
const activeProcess = transcodeProcesses.get(progressKey);
|
||||||
if (!activeProcess?.command) {
|
if (!activeProcess?.command) {
|
||||||
@@ -563,10 +587,8 @@ app.get('/api/videos', async (req, res) => {
|
|||||||
return videoExtensions.some(ext => lowerKey.endsWith(ext));
|
return videoExtensions.some(ext => lowerKey.endsWith(ext));
|
||||||
})
|
})
|
||||||
.map(key => {
|
.map(key => {
|
||||||
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
|
const hlsDir = getHlsCacheDir(bucket, key);
|
||||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
|
const tmpInputPath = getInputCachePath(bucket, key);
|
||||||
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${safeKeySegments.join('-')}`);
|
|
||||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
hasTranscodeCache: fs.existsSync(hlsDir),
|
hasTranscodeCache: fs.existsSync(hlsDir),
|
||||||
@@ -627,9 +649,7 @@ app.post('/api/clear-video-transcode-cache', async (req, res) => {
|
|||||||
if (!bucket || !key) {
|
if (!bucket || !key) {
|
||||||
return res.status(400).json({ error: 'Bucket and key are required' });
|
return res.status(400).json({ error: 'Bucket and key are required' });
|
||||||
}
|
}
|
||||||
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
|
const hlsDir = getHlsCacheDir(bucket, key);
|
||||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
|
|
||||||
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${safeKeySegments.join('-')}`);
|
|
||||||
|
|
||||||
if (fs.existsSync(hlsDir)) {
|
if (fs.existsSync(hlsDir)) {
|
||||||
fs.rmSync(hlsDir, { recursive: true, force: true });
|
fs.rmSync(hlsDir, { recursive: true, force: true });
|
||||||
@@ -647,9 +667,7 @@ app.post('/api/clear-video-download-cache', async (req, res) => {
|
|||||||
if (!bucket || !key) {
|
if (!bucket || !key) {
|
||||||
return res.status(400).json({ error: 'Bucket and key are required' });
|
return res.status(400).json({ error: 'Bucket and key are required' });
|
||||||
}
|
}
|
||||||
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
|
const tmpInputPath = getInputCachePath(bucket, key);
|
||||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
|
|
||||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
|
||||||
|
|
||||||
if (fs.existsSync(tmpInputPath)) {
|
if (fs.existsSync(tmpInputPath)) {
|
||||||
fs.rmSync(tmpInputPath, { force: true });
|
fs.rmSync(tmpInputPath, { force: true });
|
||||||
@@ -714,9 +732,7 @@ app.get('/api/hls/playlist.m3u8', async (req, res) => {
|
|||||||
const key = req.query.key;
|
const key = req.query.key;
|
||||||
if (!bucket || !key) return res.status(400).send('Bad Request');
|
if (!bucket || !key) return res.status(400).send('Bad Request');
|
||||||
|
|
||||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));
|
const tmpInputPath = getInputCachePath(bucket, key);
|
||||||
const safeBucket = bucket.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
||||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
|
||||||
|
|
||||||
const auth = await extractS3Credentials(req);
|
const auth = await extractS3Credentials(req);
|
||||||
const s3Client = createS3Client(auth);
|
const s3Client = createS3Client(auth);
|
||||||
@@ -732,13 +748,21 @@ app.get('/api/hls/playlist.m3u8', async (req, res) => {
|
|||||||
|
|
||||||
let duration = 0;
|
let duration = 0;
|
||||||
try {
|
try {
|
||||||
|
console.log(`[HLS] Probing file: ${key} (${tmpInputPath})`);
|
||||||
const metadata = await probeFile(tmpInputPath);
|
const metadata = await probeFile(tmpInputPath);
|
||||||
duration = parseFloat(metadata.format?.duration || 0);
|
duration = parseFloat(metadata.format?.duration || 0);
|
||||||
} catch (err) { }
|
console.log(`[HLS] Probe complete: duration=${duration}s`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[HLS] Probe failed for ${key}:`, err.message);
|
||||||
|
}
|
||||||
|
|
||||||
if (duration <= 0) duration = 3600;
|
if (duration <= 0) {
|
||||||
|
duration = 3600;
|
||||||
|
console.warn(`[HLS] Duration invalid, using fallback: ${duration}s`);
|
||||||
|
}
|
||||||
|
|
||||||
const totalSegments = Math.ceil(duration / HLS_SEGMENT_TIME);
|
const totalSegments = Math.ceil(duration / HLS_SEGMENT_TIME);
|
||||||
|
console.log(`[HLS] Generating m3u8: ${totalSegments} segments, duration=${duration}s, key=${key}`);
|
||||||
|
|
||||||
let m3u8 = `#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:${HLS_SEGMENT_TIME}\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:VOD\n`;
|
let m3u8 = `#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:${HLS_SEGMENT_TIME}\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:VOD\n`;
|
||||||
for (let i = 0; i < totalSegments; i++) {
|
for (let i = 0; i < totalSegments; i++) {
|
||||||
@@ -750,6 +774,7 @@ app.get('/api/hls/playlist.m3u8', async (req, res) => {
|
|||||||
}
|
}
|
||||||
m3u8 += `#EXT-X-ENDLIST\n`;
|
m3u8 += `#EXT-X-ENDLIST\n`;
|
||||||
|
|
||||||
|
console.log(`[HLS] Sending m3u8 playlist to client (${m3u8.length} bytes)`);
|
||||||
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
|
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
|
||||||
res.setHeader('Cache-Control', 'no-cache');
|
res.setHeader('Cache-Control', 'no-cache');
|
||||||
res.send(m3u8);
|
res.send(m3u8);
|
||||||
@@ -783,12 +808,12 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
|
|
||||||
if (!bucket || !key || isNaN(seg)) return res.status(400).send('Bad Request');
|
if (!bucket || !key || isNaN(seg)) return res.status(400).send('Bad Request');
|
||||||
|
|
||||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));
|
console.log(`[HLS] Segment request: seg=${seg}, key=${key}, encoder=${requestedEncoder}`);
|
||||||
const safeBucket = bucket.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
|
||||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
|
||||||
|
|
||||||
const progressKey = safeKeySegments.join('/');
|
const tmpInputPath = getInputCachePath(bucket, key);
|
||||||
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${progressKey}`);
|
|
||||||
|
const progressKey = getProgressKey(key);
|
||||||
|
const hlsDir = getHlsCacheDir(bucket, key);
|
||||||
if (!fs.existsSync(hlsDir)) fs.mkdirSync(hlsDir, { recursive: true });
|
if (!fs.existsSync(hlsDir)) fs.mkdirSync(hlsDir, { recursive: true });
|
||||||
|
|
||||||
const targetSegPath = path.join(hlsDir, `segment_${seg}.ts`);
|
const targetSegPath = path.join(hlsDir, `segment_${seg}.ts`);
|
||||||
@@ -817,17 +842,24 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
const needsNewProcess = !currentProcess || (!fs.existsSync(targetSegPath) && (seg < (currentProcess.currentSeg || 0) || seg > (currentProcess.currentSeg || 0) + 4));
|
const needsNewProcess = !currentProcess || (!fs.existsSync(targetSegPath) && (seg < (currentProcess.currentSeg || 0) || seg > (currentProcess.currentSeg || 0) + 4));
|
||||||
|
|
||||||
if (needsNewProcess) {
|
if (needsNewProcess) {
|
||||||
|
console.log(`[HLS] Starting new FFmpeg process for seg=${seg}, key=${key}`);
|
||||||
if (currentProcess && currentProcess.command) {
|
if (currentProcess && currentProcess.command) {
|
||||||
|
console.log(`[HLS] Killing previous FFmpeg process for ${progressKey}`);
|
||||||
try { currentProcess.command.kill('SIGKILL'); } catch (e) { }
|
try { currentProcess.command.kill('SIGKILL'); } catch (e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
const startTime = Math.max(0, seg * HLS_SEGMENT_TIME);
|
const startTime = Math.max(0, seg * HLS_SEGMENT_TIME);
|
||||||
|
|
||||||
let sourceMetadata = null;
|
let sourceMetadata = null;
|
||||||
try { sourceMetadata = await probeFile(tmpInputPath); } catch (e) { }
|
try {
|
||||||
|
sourceMetadata = await probeFile(tmpInputPath);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[HLS] Probe failed for segment transcode: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
const encoderName = availableEncoderValues.has(requestedEncoder) ? requestedEncoder : 'h264_rkmpp';
|
const encoderName = availableEncoderValues.has(requestedEncoder) ? requestedEncoder : 'h264_rkmpp';
|
||||||
const decoderName = availableDecoderValues.has(requestedDecoder) ? requestedDecoder : 'auto';
|
const decoderName = availableDecoderValues.has(requestedDecoder) ? requestedDecoder : 'auto';
|
||||||
|
console.log(`[HLS] FFmpeg config: encoder=${encoderName}, decoder=${decoderName}, startTime=${startTime}s`);
|
||||||
|
|
||||||
const m3u8Path = path.join(hlsDir, `temp.m3u8`);
|
const m3u8Path = path.join(hlsDir, `temp.m3u8`);
|
||||||
if (fs.existsSync(m3u8Path)) fs.unlinkSync(m3u8Path);
|
if (fs.existsSync(m3u8Path)) fs.unlinkSync(m3u8Path);
|
||||||
@@ -858,7 +890,7 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
|
|
||||||
ffmpegCommand.outputOptions(hlsOptions).output(m3u8Path);
|
ffmpegCommand.outputOptions(hlsOptions).output(m3u8Path);
|
||||||
ffmpegCommand.on('error', (err) => {
|
ffmpegCommand.on('error', (err) => {
|
||||||
console.error('HLS FFmpeg Error:', err.message);
|
console.error(`[HLS] FFmpeg Error for ${progressKey}:`, err.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
ffmpegCommand.on('progress', (progress) => {
|
ffmpegCommand.on('progress', (progress) => {
|
||||||
@@ -874,9 +906,9 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
const progressState = {
|
const progressState = {
|
||||||
status: 'transcoding',
|
status: 'transcoding',
|
||||||
percent,
|
percent,
|
||||||
frame: progress.frames || null,
|
frame: sanitizeNumber(progress.frames),
|
||||||
fps: progress.currentFps || null,
|
fps: sanitizeNumber(progress.currentFps),
|
||||||
bitrate: progress.currentKbps || null,
|
bitrate: sanitizeNumber(progress.currentKbps),
|
||||||
timemark: progress.timemark || null,
|
timemark: progress.timemark || null,
|
||||||
absoluteSeconds,
|
absoluteSeconds,
|
||||||
duration: totalDuration || null,
|
duration: totalDuration || null,
|
||||||
@@ -887,7 +919,7 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
progressMap[progressKey] = progressState;
|
progressMap[progressKey] = progressState;
|
||||||
broadcastWs(progressKey, { type: 'progress', key, progress: progressState });
|
broadcastWs(progressKey, { type: 'progress', key, progress: progressState });
|
||||||
|
|
||||||
console.log(`[FFmpeg] ${progressKey} | ${progress.timemark} | ${progress.currentFps}fps | ${progress.currentKbps}kbps | ${percent}%`);
|
console.log(`[FFmpeg] ${progressKey} | ${progress.timemark} | ${sanitizeNumber(progress.currentFps) ?? '-'}fps | ${sanitizeNumber(progress.currentKbps) ?? '-'}kbps | ${percent}%`);
|
||||||
});
|
});
|
||||||
|
|
||||||
ffmpegCommand.on('end', () => {
|
ffmpegCommand.on('end', () => {
|
||||||
@@ -895,12 +927,16 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ffmpegCommand.run();
|
ffmpegCommand.run();
|
||||||
|
console.log(`[HLS] FFmpeg process started for ${progressKey}`);
|
||||||
currentProcess = { command: ffmpegCommand, currentSeg: seg, lastActive: Date.now() };
|
currentProcess = { command: ffmpegCommand, currentSeg: seg, lastActive: Date.now() };
|
||||||
hlsProcesses.set(progressKey, currentProcess);
|
hlsProcesses.set(progressKey, currentProcess);
|
||||||
|
} else {
|
||||||
|
console.log(`[HLS] Reusing existing FFmpeg process for seg=${seg}, currentSeg=${currentProcess?.currentSeg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ready = await waitForSegment(hlsDir, seg);
|
const ready = await waitForSegment(hlsDir, seg);
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
|
console.error(`[HLS] Segment generation timeout: seg=${seg}, key=${key}`);
|
||||||
return res.status(500).send('Segment generation timeout');
|
return res.status(500).send('Segment generation timeout');
|
||||||
}
|
}
|
||||||
if (currentProcess) {
|
if (currentProcess) {
|
||||||
@@ -908,6 +944,7 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
|||||||
currentProcess.lastActive = Date.now();
|
currentProcess.lastActive = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[HLS] Serving segment: seg=${seg}`);
|
||||||
res.setHeader('Content-Type', 'video/MP2T');
|
res.setHeader('Content-Type', 'video/MP2T');
|
||||||
res.sendFile(targetSegPath);
|
res.sendFile(targetSegPath);
|
||||||
});
|
});
|
||||||
@@ -934,8 +971,7 @@ app.get('/api/stream', async (req, res) => {
|
|||||||
|
|
||||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));
|
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));
|
||||||
const progressKey = safeKeySegments.join('/');
|
const progressKey = safeKeySegments.join('/');
|
||||||
const safeBucket = bucket.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
const tmpInputPath = getInputCachePath(bucket, key);
|
||||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
|
||||||
const cacheExists = fs.existsSync(tmpInputPath);
|
const cacheExists = fs.existsSync(tmpInputPath);
|
||||||
|
|
||||||
const auth = await extractS3Credentials(req);
|
const auth = await extractS3Credentials(req);
|
||||||
@@ -1027,9 +1063,9 @@ app.get('/api/stream', async (req, res) => {
|
|||||||
const progressState = {
|
const progressState = {
|
||||||
status: 'transcoding',
|
status: 'transcoding',
|
||||||
percent,
|
percent,
|
||||||
frame: progress.frames || null,
|
frame: sanitizeNumber(progress.frames),
|
||||||
fps: progress.currentFps || null,
|
fps: sanitizeNumber(progress.currentFps),
|
||||||
bitrate: progress.currentKbps || null,
|
bitrate: sanitizeNumber(progress.currentKbps),
|
||||||
timemark: progress.timemark || null,
|
timemark: progress.timemark || null,
|
||||||
absoluteSeconds,
|
absoluteSeconds,
|
||||||
duration: progressMap[progressKey]?.duration || null,
|
duration: progressMap[progressKey]?.duration || null,
|
||||||
|
|||||||
Reference in New Issue
Block a user