@torrent-tv/proxy 2.9.135 → 2.9.136
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
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
## 2.9.
|
|
1
|
+
## 2.9.136
|
|
2
|
+
|
|
3
|
+
- **Fix**: What a complete init segment must describe is now taken from what the proxy DECLARES it will output, not from a count. 2.9.135 required two tracks, which is a guess — wrong for a film with no soundtrack, and meaningless for a source carrying several dubs, subtitles or a cover-art video stream. The output does not inherit the source's track list: the command maps at most one video and at most one audio, each optional, and subtitles never enter the HLS output at all. So the proxy knows the output's set exactly, because it chose it — the probe says which kinds exist, the mapping says how many are taken. Deriving the figure from produced pieces instead reads correctly only once a piece carrying every track exists, and the moment that matters is the one before that: an early piece written before the video was muxed sets the requirement to one and waves through precisely the header this exists to reject. Pieces remain as a floor, since a piece carrying more than declared is evidence, and evidence outranks a declaration.
|
|
4
|
+
|
|
5
|
+
## 2.9.135
|
|
2
6
|
|
|
3
7
|
- **Fix**: A session no longer plays sound with no picture at all. The init segment — the header that tells the browser which tracks exist — is lifted out of the first self-contained piece and then cached for the WHOLE session, because the player fetches `#EXT-X-MAP` once and never again. A piece written before the video track had been muxed declares audio alone, and the browser then has no video source buffer for the rest of the session however much video arrives afterwards. Measured 2026-08-10 from the browser's own counters: sixty-five seconds of playing sound with `videoWidth=0`, `totalVideoFrames=0` and `readyState=4` — an element perfectly satisfied, with no picture in it. A header short of a track is now passed over and the next piece tried; if no piece carries the full set the richest one found is served and the shortfall is logged, so a source that genuinely lacks a stream still plays while the other possibility stays visible.
|
|
4
8
|
|
package/package.json
CHANGED
|
@@ -1528,13 +1528,35 @@ export class HlsSessionManager {
|
|
|
1528
1528
|
if (typeof session.segmentFormat.extractInit !== "function") {
|
|
1529
1529
|
return null;
|
|
1530
1530
|
}
|
|
1531
|
-
//
|
|
1532
|
-
//
|
|
1533
|
-
//
|
|
1534
|
-
//
|
|
1535
|
-
|
|
1531
|
+
// How many tracks a complete header must declare is ANSWERED, not assumed.
|
|
1532
|
+
//
|
|
1533
|
+
// The probe already knows the source's stream list, and the output maps at
|
|
1534
|
+
// most one of each (`-map 0:v:0? -map 0:a:0?`), so the count follows from
|
|
1535
|
+
// what the source actually has. A film with no soundtrack expects one; an
|
|
1536
|
+
// ordinary file expects two; neither is a convention.
|
|
1537
|
+
//
|
|
1538
|
+
// Deriving it from the produced pieces instead — the first version of this
|
|
1539
|
+
// — reads correctly only once a piece carrying every track exists, and the
|
|
1540
|
+
// whole point is the moment BEFORE that: early pieces written before the
|
|
1541
|
+
// video was muxed would set the requirement to one and wave through exactly
|
|
1542
|
+
// the header this exists to reject. The pieces are still consulted, but
|
|
1543
|
+
// only as a floor: a piece carrying more than the probe led us to expect is
|
|
1544
|
+
// evidence, and evidence outranks the probe.
|
|
1545
|
+
const probed = this.getCachedMediaInfo?.({
|
|
1546
|
+
sourceKey: session.sourceKey,
|
|
1547
|
+
fileIndex: session.fileIndex
|
|
1548
|
+
}) ?? null;
|
|
1549
|
+
let expectedTracks = probed
|
|
1550
|
+
? (probed.videoCodec ? 1 : 0) + (probed.audioCodec ? 1 : 0)
|
|
1551
|
+
: 0;
|
|
1552
|
+
if (expectedTracks === 0) {
|
|
1553
|
+
// No probe to consult. Fall back to the evidence, with its known lag.
|
|
1554
|
+
expectedTracks = 1;
|
|
1555
|
+
}
|
|
1536
1556
|
let best = null;
|
|
1537
1557
|
let bestTracks = 0;
|
|
1558
|
+
/** @type {Map<string, Buffer>} Pieces read once and used for both passes. */
|
|
1559
|
+
const pieces = new Map();
|
|
1538
1560
|
let names;
|
|
1539
1561
|
try {
|
|
1540
1562
|
names = (this.#runDirs(session).flatMap((dir) => {
|
|
@@ -1545,13 +1567,32 @@ export class HlsSessionManager {
|
|
|
1545
1567
|
} catch {
|
|
1546
1568
|
return null;
|
|
1547
1569
|
}
|
|
1570
|
+
// First pass: what do the produced pieces actually carry? The answer is the
|
|
1571
|
+
// requirement — no assumption about the source is involved.
|
|
1572
|
+
if (typeof session.segmentFormat.countSegmentTracks === "function") {
|
|
1573
|
+
for (const name of names) {
|
|
1574
|
+
try {
|
|
1575
|
+
const found = await this.#findProducedFile(session, name);
|
|
1576
|
+
if (!found) {
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
const bytes = await readFile(found);
|
|
1580
|
+
pieces.set(name, bytes);
|
|
1581
|
+
expectedTracks = Math.max(expectedTracks, session.segmentFormat.countSegmentTracks(bytes));
|
|
1582
|
+
} catch {
|
|
1583
|
+
// Being written right now — it says nothing about the others.
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1548
1588
|
for (const name of names) {
|
|
1549
1589
|
try {
|
|
1550
|
-
const
|
|
1590
|
+
const cached = pieces.get(name);
|
|
1591
|
+
const found = cached ? name : await this.#findProducedFile(session, name);
|
|
1551
1592
|
if (!found) {
|
|
1552
1593
|
continue;
|
|
1553
1594
|
}
|
|
1554
|
-
const init = session.segmentFormat.extractInit(await readFile(found));
|
|
1595
|
+
const init = session.segmentFormat.extractInit(cached ?? await readFile(found));
|
|
1555
1596
|
if (init && init.length > 0) {
|
|
1556
1597
|
return init;
|
|
1557
1598
|
}
|
|
@@ -265,6 +265,22 @@ export const fmp4Format = {
|
|
|
265
265
|
* @param {Buffer} initBytes
|
|
266
266
|
* @returns {number}
|
|
267
267
|
*/
|
|
268
|
+
/**
|
|
269
|
+
* How many distinct tracks this piece's own fragments carry.
|
|
270
|
+
*
|
|
271
|
+
* Needs no knowledge of the source: whatever a produced piece contains, it
|
|
272
|
+
* contains. Comparing the richest piece against a candidate header is what
|
|
273
|
+
* lets an init be judged without assuming how many tracks a file "should"
|
|
274
|
+
* have — a film with no soundtrack and a file with two audio tracks are both
|
|
275
|
+
* answered correctly, and neither is guessed at.
|
|
276
|
+
*
|
|
277
|
+
* @param {Buffer} bytes
|
|
278
|
+
* @returns {number}
|
|
279
|
+
*/
|
|
280
|
+
countSegmentTracks(bytes) {
|
|
281
|
+
return countFragmentTracks(bytes);
|
|
282
|
+
},
|
|
283
|
+
|
|
268
284
|
countInitTracks(initBytes) {
|
|
269
285
|
if (!initBytes || initBytes.length === 0) {
|
|
270
286
|
return 0;
|