@torrent-tv/proxy 2.9.6 → 2.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/CLAUDE.md +8 -3
- package/package.json +1 -1
- package/services/playback-planner.js +42 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.9.7
|
|
2
|
+
|
|
3
|
+
- **Fix**: `playback-planner` retries the codec probe while the file header is still downloading and no longer caches an **empty** probe result. Previously a transient empty probe (common for a later file in a multi-file torrent whose pieces arrive late) was cached permanently, so the file was mis-planned as directly playable forever — an unsupported video codec (e.g. xvid) got copied and played as a **black screen**. The probe now retries (up to 60 s) until at least one codec is detected, and only a successful detection is cached.
|
|
4
|
+
|
|
1
5
|
## 2.9.6
|
|
2
6
|
|
|
3
7
|
- **Fix**: `probeInputDurationSeconds` now returns as soon as ffmpeg prints the container header (`Duration:`) instead of letting `-f null -` decode the whole stream until the 8 s timeout. Transcode-session creation was wasting ~8.6 s per session on this redundant decode (the duration was already available from the header, and `playback-plan` had probed it moments earlier). Cuts session-creation latency from ~9.7 s to ~1 s.
|
package/CLAUDE.md
CHANGED
|
@@ -50,9 +50,14 @@ Linux-only host (e.g. POSIX-only signals must degrade elsewhere).
|
|
|
50
50
|
## Changelog
|
|
51
51
|
|
|
52
52
|
Every behavioural change must be recorded in `CHANGELOG.md` — add an entry under
|
|
53
|
-
a new `## <version>` heading at the top
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
a new `## <version>` heading at the top, following the existing
|
|
54
|
+
`- **New**/**Fix**/**Chore**:` format.
|
|
55
|
+
|
|
56
|
+
**Do NOT edit `package.json` version.** `npm run patch`/`minor` runs `npm
|
|
57
|
+
version …` which bumps it. Write the CHANGELOG entry at the version that bump
|
|
58
|
+
will produce: **current `package.json` version + 1 patch** (or + 1 minor).
|
|
59
|
+
Accumulate bullets into that single pending entry until it's published. See the
|
|
60
|
+
parent `../CLAUDE.md`.
|
|
56
61
|
|
|
57
62
|
## Release
|
|
58
63
|
|
package/package.json
CHANGED
|
@@ -97,6 +97,18 @@ function probeStreamCodecs({ ffmpegBin, inputUrl, userAgent = "", timeoutMs = 8_
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Resolve after a given number of milliseconds.
|
|
102
|
+
*
|
|
103
|
+
* @param {number} ms
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
*/
|
|
106
|
+
function delay(ms) {
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
setTimeout(resolve, ms);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
/**
|
|
101
113
|
* Build the direct stream URL for a source file served by the local proxy.
|
|
102
114
|
*
|
|
@@ -198,28 +210,34 @@ export function createPlaybackPlanner({
|
|
|
198
210
|
return plan;
|
|
199
211
|
}
|
|
200
212
|
|
|
201
|
-
// Pre-fetch file edges (head + tail)
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
213
|
+
// Pre-fetch file edges (head + tail), then probe — retrying while the
|
|
214
|
+
// file header is still downloading. In a multi-file torrent the pieces
|
|
215
|
+
// for a given file arrive unevenly, so the first probe can return empty
|
|
216
|
+
// codecs. A transient empty probe must NOT be cached: otherwise the wrong
|
|
217
|
+
// plan (file treated as directly playable) sticks permanently for this
|
|
218
|
+
// file, and an unsupported codec like xvid gets copied → black video.
|
|
205
219
|
await torrentPool.prefetchFileEdges(torrent, fileIndex);
|
|
220
|
+
let probe = await probeStreamCodecs({ ffmpegBin, inputUrl: directUrl, userAgent });
|
|
221
|
+
const probeDeadline = Date.now() + 60_000;
|
|
222
|
+
let attempt = 0;
|
|
223
|
+
while (
|
|
224
|
+
probe.audioCodec.length === 0 &&
|
|
225
|
+
probe.videoCodec.length === 0 &&
|
|
226
|
+
Date.now() < probeDeadline
|
|
227
|
+
) {
|
|
228
|
+
attempt += 1;
|
|
229
|
+
await delay(Math.min(3_000, 500 + attempt * 250));
|
|
230
|
+
await torrentPool.prefetchFileEdges(torrent, fileIndex);
|
|
231
|
+
probe = await probeStreamCodecs({ ffmpegBin, inputUrl: directUrl, userAgent });
|
|
232
|
+
}
|
|
233
|
+
const { audioCodec, videoCodec, container, durationSeconds } = probe;
|
|
234
|
+
const codecsDetected = audioCodec.length > 0 || videoCodec.length > 0;
|
|
206
235
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
userAgent
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Only transcode when the codec is known AND not natively supported.
|
|
214
|
-
// When ffprobe cannot detect the codec (e.g. the torrent has just started
|
|
215
|
-
// downloading and the MOOV atom at the end of the MP4 is not yet available),
|
|
216
|
-
// fall back to "direct" so the browser can attempt native playback. The
|
|
217
|
-
// browser-side loading pipeline already has its own transcode fallback.
|
|
236
|
+
// `mode` is advisory only (audio-codec based). The browser makes the
|
|
237
|
+
// authoritative decision independently per stream via canPlayType /
|
|
238
|
+
// mediaCapabilities, transcoding only what it cannot play.
|
|
218
239
|
const requiresTranscode = audioCodec.length > 0 && !DIRECT_AUDIO_CODECS.has(audioCodec);
|
|
219
240
|
const plan = {
|
|
220
|
-
// `mode` is advisory only (audio-codec based). The browser makes the
|
|
221
|
-
// authoritative decision independently per stream via canPlayType /
|
|
222
|
-
// mediaCapabilities, transcoding only what it cannot play.
|
|
223
241
|
mode: requiresTranscode ? "hls" : "direct",
|
|
224
242
|
directUrl,
|
|
225
243
|
reason: requiresTranscode ? "audio-codec-transcode-required" : "audio-codec-supported",
|
|
@@ -228,7 +246,12 @@ export function createPlaybackPlanner({
|
|
|
228
246
|
container,
|
|
229
247
|
durationSeconds
|
|
230
248
|
};
|
|
231
|
-
cache.
|
|
249
|
+
// Only cache a plan whose codecs were actually detected. An empty probe is
|
|
250
|
+
// a "header not downloaded yet" signal, not a valid result — caching it
|
|
251
|
+
// would permanently mis-plan the file.
|
|
252
|
+
if (codecsDetected) {
|
|
253
|
+
cache.set(cacheKey, plan);
|
|
254
|
+
}
|
|
232
255
|
return plan;
|
|
233
256
|
}
|
|
234
257
|
};
|