修复不显示清空缓存按钮的BUG
This commit is contained in:
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 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 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);
|
||||
};
|
||||
|
||||
const probeFile = (filePath) => {
|
||||
const probeFile = (filePath, timeoutMs = 15000) => {
|
||||
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) => {
|
||||
clearTimeout(timer);
|
||||
if (err) reject(err);
|
||||
else resolve(metadata);
|
||||
});
|
||||
@@ -308,6 +326,12 @@ const parseTimemarkToSeconds = (timemark) => {
|
||||
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 activeProcess = transcodeProcesses.get(progressKey);
|
||||
if (!activeProcess?.command) {
|
||||
@@ -563,10 +587,8 @@ app.get('/api/videos', async (req, res) => {
|
||||
return videoExtensions.some(ext => lowerKey.endsWith(ext));
|
||||
})
|
||||
.map(key => {
|
||||
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
|
||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
|
||||
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${safeKeySegments.join('-')}`);
|
||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
||||
const hlsDir = getHlsCacheDir(bucket, key);
|
||||
const tmpInputPath = getInputCachePath(bucket, key);
|
||||
return {
|
||||
key: key,
|
||||
hasTranscodeCache: fs.existsSync(hlsDir),
|
||||
@@ -627,9 +649,7 @@ app.post('/api/clear-video-transcode-cache', async (req, res) => {
|
||||
if (!bucket || !key) {
|
||||
return res.status(400).json({ error: 'Bucket and key are required' });
|
||||
}
|
||||
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
|
||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
|
||||
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${safeKeySegments.join('-')}`);
|
||||
const hlsDir = getHlsCacheDir(bucket, key);
|
||||
|
||||
if (fs.existsSync(hlsDir)) {
|
||||
fs.rmSync(hlsDir, { recursive: true, force: true });
|
||||
@@ -647,9 +667,7 @@ app.post('/api/clear-video-download-cache', async (req, res) => {
|
||||
if (!bucket || !key) {
|
||||
return res.status(400).json({ error: 'Bucket and key are required' });
|
||||
}
|
||||
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
|
||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
|
||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
||||
const tmpInputPath = getInputCachePath(bucket, key);
|
||||
|
||||
if (fs.existsSync(tmpInputPath)) {
|
||||
fs.rmSync(tmpInputPath, { force: true });
|
||||
@@ -714,9 +732,7 @@ app.get('/api/hls/playlist.m3u8', async (req, res) => {
|
||||
const key = req.query.key;
|
||||
if (!bucket || !key) return res.status(400).send('Bad Request');
|
||||
|
||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));
|
||||
const safeBucket = bucket.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
||||
const tmpInputPath = getInputCachePath(bucket, key);
|
||||
|
||||
const auth = await extractS3Credentials(req);
|
||||
const s3Client = createS3Client(auth);
|
||||
@@ -732,13 +748,21 @@ app.get('/api/hls/playlist.m3u8', async (req, res) => {
|
||||
|
||||
let duration = 0;
|
||||
try {
|
||||
console.log(`[HLS] Probing file: ${key} (${tmpInputPath})`);
|
||||
const metadata = await probeFile(tmpInputPath);
|
||||
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);
|
||||
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`;
|
||||
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`;
|
||||
|
||||
console.log(`[HLS] Sending m3u8 playlist to client (${m3u8.length} bytes)`);
|
||||
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
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');
|
||||
|
||||
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));
|
||||
const safeBucket = bucket.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
||||
console.log(`[HLS] Segment request: seg=${seg}, key=${key}, encoder=${requestedEncoder}`);
|
||||
|
||||
const progressKey = safeKeySegments.join('/');
|
||||
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${progressKey}`);
|
||||
const tmpInputPath = getInputCachePath(bucket, key);
|
||||
|
||||
const progressKey = getProgressKey(key);
|
||||
const hlsDir = getHlsCacheDir(bucket, key);
|
||||
if (!fs.existsSync(hlsDir)) fs.mkdirSync(hlsDir, { recursive: true });
|
||||
|
||||
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));
|
||||
|
||||
if (needsNewProcess) {
|
||||
console.log(`[HLS] Starting new FFmpeg process for seg=${seg}, key=${key}`);
|
||||
if (currentProcess && currentProcess.command) {
|
||||
console.log(`[HLS] Killing previous FFmpeg process for ${progressKey}`);
|
||||
try { currentProcess.command.kill('SIGKILL'); } catch (e) { }
|
||||
}
|
||||
|
||||
const startTime = Math.max(0, seg * HLS_SEGMENT_TIME);
|
||||
|
||||
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 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`);
|
||||
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.on('error', (err) => {
|
||||
console.error('HLS FFmpeg Error:', err.message);
|
||||
console.error(`[HLS] FFmpeg Error for ${progressKey}:`, err.message);
|
||||
});
|
||||
|
||||
ffmpegCommand.on('progress', (progress) => {
|
||||
@@ -874,9 +906,9 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
||||
const progressState = {
|
||||
status: 'transcoding',
|
||||
percent,
|
||||
frame: progress.frames || null,
|
||||
fps: progress.currentFps || null,
|
||||
bitrate: progress.currentKbps || null,
|
||||
frame: sanitizeNumber(progress.frames),
|
||||
fps: sanitizeNumber(progress.currentFps),
|
||||
bitrate: sanitizeNumber(progress.currentKbps),
|
||||
timemark: progress.timemark || null,
|
||||
absoluteSeconds,
|
||||
duration: totalDuration || null,
|
||||
@@ -887,7 +919,7 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
||||
progressMap[progressKey] = 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', () => {
|
||||
@@ -895,12 +927,16 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
||||
});
|
||||
|
||||
ffmpegCommand.run();
|
||||
console.log(`[HLS] FFmpeg process started for ${progressKey}`);
|
||||
currentProcess = { command: ffmpegCommand, currentSeg: seg, lastActive: Date.now() };
|
||||
hlsProcesses.set(progressKey, currentProcess);
|
||||
} else {
|
||||
console.log(`[HLS] Reusing existing FFmpeg process for seg=${seg}, currentSeg=${currentProcess?.currentSeg}`);
|
||||
}
|
||||
|
||||
const ready = await waitForSegment(hlsDir, seg);
|
||||
if (!ready) {
|
||||
console.error(`[HLS] Segment generation timeout: seg=${seg}, key=${key}`);
|
||||
return res.status(500).send('Segment generation timeout');
|
||||
}
|
||||
if (currentProcess) {
|
||||
@@ -908,6 +944,7 @@ app.get('/api/hls/segment.ts', async (req, res) => {
|
||||
currentProcess.lastActive = Date.now();
|
||||
}
|
||||
|
||||
console.log(`[HLS] Serving segment: seg=${seg}`);
|
||||
res.setHeader('Content-Type', 'video/MP2T');
|
||||
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 progressKey = safeKeySegments.join('/');
|
||||
const safeBucket = bucket.replace(/[^a-zA-Z0-9_\-]/g, '_');
|
||||
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
|
||||
const tmpInputPath = getInputCachePath(bucket, key);
|
||||
const cacheExists = fs.existsSync(tmpInputPath);
|
||||
|
||||
const auth = await extractS3Credentials(req);
|
||||
@@ -1027,9 +1063,9 @@ app.get('/api/stream', async (req, res) => {
|
||||
const progressState = {
|
||||
status: 'transcoding',
|
||||
percent,
|
||||
frame: progress.frames || null,
|
||||
fps: progress.currentFps || null,
|
||||
bitrate: progress.currentKbps || null,
|
||||
frame: sanitizeNumber(progress.frames),
|
||||
fps: sanitizeNumber(progress.currentFps),
|
||||
bitrate: sanitizeNumber(progress.currentKbps),
|
||||
timemark: progress.timemark || null,
|
||||
absoluteSeconds,
|
||||
duration: progressMap[progressKey]?.duration || null,
|
||||
|
||||
Reference in New Issue
Block a user