@torrent-tv/proxy 2.14.1 → 2.14.3
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 +10 -0
- package/package.json +1 -1
- package/services/hls-session-manager.js +106 -42
- package/test/behind-head-repair.test.js +38 -4
- package/test/quality-variants.test.js +55 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 2.14.3
|
|
2
|
+
|
|
3
|
+
- **Fix**: A separately published audio track begins where the PICTURE is, measured rather than guessed. The position this class keeps is the read head, and the viewer sits behind it by whatever the player has buffered — a figure the browser already reports with every link report, so the playhead is one subtraction away (less one segment of margin, since the report can be ten seconds old). 2.14.2 subtracted the whole look-ahead instead, which was safe but made the encoder produce up to two minutes of audio nobody would hear before reaching the part that was wanted. A report older than fifteen seconds is ignored — a viewer may have seeked since — and then the whole look-ahead is subtracted as before.
|
|
4
|
+
- **Fix**: A request behind the encode run is acted on when the player ASKS AGAIN, not after three seconds of waiting. Repetition is the player saying it still needs that exact segment; a delay only says time has passed, and those three seconds were part of the twenty a track change cost. A scan is told apart by what else is being asked for — more than three distinct segments behind the run within two seconds is the player sweeping the playlist, and moving the encoder to one of them would be moving it to a number picked at random.
|
|
5
|
+
- **Fix**: That scan count is taken over a two-second window rather than over the life of the run. Accumulated, it would have crossed the threshold on any long session and disabled the repair for good — silently, since nothing about a repair that never fires is logged.
|
|
6
|
+
|
|
7
|
+
## 2.14.2
|
|
8
|
+
|
|
9
|
+
- **Fix**: A separately published audio track starts BEHIND the picture's read head, and a request behind its run is answered as before. Two mistakes compounded in 2.14.1 and left the viewer on a spinner that never ended. The position this class keeps is written by the segments a session serves — the READ head — while the viewer's picture sits behind it by everything they have buffered, so the track was started AHEAD of them: field 2026-08-15, the run placed at segment #16 while the player asked for #10. On top of that, 2.14.1 had begun answering such a request "not found" at once instead of holding it, which turned a condition the encoder used to correct in twenty seconds into a permanent refusal: hls.js retried #10 for a minute and a half, raised a fatal network error, recovered, and retried it again. The prompt refusal is withdrawn — it was written for a probe and met a real request — and the track now starts a whole look-ahead behind the read head, which is exactly how far apart the two can be. The price is audio the player already holds: at ten to twenty times realtime and 75 KB a piece, a second or two of work.
|
|
10
|
+
|
|
1
11
|
## 2.14.1
|
|
2
12
|
|
|
3
13
|
- **Fix**: Changing the audio track no longer costs twenty seconds of silence. The player asks the NEW rendition for its segment #0 before anything else — measured 2026-08-15, a track changed at 159 s with the rendition correctly placed at #26 — and the repair that exists for a run placed WRONGLY took that literally: it killed the run and restarted the encoder at the beginning of the film, so the segment the viewer was waiting for arrived 20.5 s later. A rendition is never repaired by moving it, because its run is placed where the viewer is and the request behind it is the player probing; and such a request is now answered at once rather than held, since holding it spends the player's patience on a fragment that can never be produced. The refusal stands down while a seek of the rendition's own is settling: a viewer going BACKWARDS is reported to the base and forwarded to the rendition, but its run only moves when the settle fires, so until then the requests for the new position are behind the old one — and those are exactly the ones the viewer is waiting for.
|
package/package.json
CHANGED
|
@@ -354,6 +354,10 @@ const READ_WINDOW_SECONDS = 30;
|
|
|
354
354
|
const READ_WINDOW_MIN_BYTES = 16 * 1024 * 1024;
|
|
355
355
|
const READ_WINDOW_MAX_BYTES = 96 * 1024 * 1024;
|
|
356
356
|
const LOOKAHEAD_PAUSE_SECONDS = 120;
|
|
357
|
+
// How old a viewer's link report may be and still describe where they are. It
|
|
358
|
+
// is sent every 10 s, and a seek in between moves them somewhere this cannot
|
|
359
|
+
// predict — so anything older is treated as no report at all.
|
|
360
|
+
const NET_REPORT_FRESH_MS = 15_000;
|
|
357
361
|
const LOOKAHEAD_RESUME_SECONDS = 60;
|
|
358
362
|
// Seek debounce. A far (out-of-window) segment request is a server-side seek.
|
|
359
363
|
// Rather than restart ffmpeg on the first one, wait a short quiet period:
|
|
@@ -389,7 +393,18 @@ const SEEK_SETTLE_MS = 300;
|
|
|
389
393
|
// seek settles on its own — the seek is what should move the encoder — and
|
|
390
394
|
// short enough that a session cannot sit on an unanswerable request, which
|
|
391
395
|
// measured two minutes forty-one before a viewer gave up.
|
|
392
|
-
|
|
396
|
+
// A request behind the run is acted on once it has been REPEATED, not once it
|
|
397
|
+
// has waited: repetition is the player saying it still needs this exact
|
|
398
|
+
// segment, while a delay only says time has passed. The floor below stays as a
|
|
399
|
+
// last guard against acting on a single stray poll.
|
|
400
|
+
const BEHIND_HEAD_REPAIR_MIN_ASKS = 2;
|
|
401
|
+
// More distinct indices than this behind the head at once is the player
|
|
402
|
+
// scanning the playlist rather than waiting for a frame.
|
|
403
|
+
const BEHIND_HEAD_SCAN_INDICES = 3;
|
|
404
|
+
// The window the count above is taken over. A player's scan lands inside half a
|
|
405
|
+
// second (field log 2026-08-02); a viewer waiting asks every few seconds.
|
|
406
|
+
const BEHIND_HEAD_SCAN_WINDOW_MS = 2_000;
|
|
407
|
+
const BEHIND_HEAD_REPAIR_MS = 400;
|
|
393
408
|
// How far behind the run a request may be and still be treated as the encoder
|
|
394
409
|
// standing in the wrong place rather than as a player scanning the playlist. A
|
|
395
410
|
// misplaced run is out by at most the buffer the player was holding — measured
|
|
@@ -2886,6 +2901,7 @@ export class HlsSessionManager {
|
|
|
2886
2901
|
// for the seek that should move the encoder. It also stops the map growing
|
|
2887
2902
|
// for the life of a session.
|
|
2888
2903
|
session.firstWantedAt = new Map();
|
|
2904
|
+
session.behindHeadAsks = new Map();
|
|
2889
2905
|
const generation = ++session.encodeRunGeneration;
|
|
2890
2906
|
const previousFfmpeg = session.ffmpeg;
|
|
2891
2907
|
// A suspended process does not act on SIGTERM until it is continued, so the
|
|
@@ -3461,6 +3477,16 @@ export class HlsSessionManager {
|
|
|
3461
3477
|
if (!session.firstWantedAt.has(index)) {
|
|
3462
3478
|
session.firstWantedAt.set(index, Date.now());
|
|
3463
3479
|
}
|
|
3480
|
+
// How often each index behind the run has been asked for, and how many
|
|
3481
|
+
// distinct ones there are. The repair reads both: one index asked twice is
|
|
3482
|
+
// a viewer waiting, a dozen asked once each is the player scanning. Kept
|
|
3483
|
+
// only for what is behind the head — everything ahead is ordinary
|
|
3484
|
+
// read-ahead — and cleared with each run, like the record above.
|
|
3485
|
+
if (index < session.encodeStartIndex) {
|
|
3486
|
+
session.behindHeadAsks ??= new Map();
|
|
3487
|
+
const asked = session.behindHeadAsks.get(index);
|
|
3488
|
+
session.behindHeadAsks.set(index, { count: (asked?.count ?? 0) + 1, at: Date.now() });
|
|
3489
|
+
}
|
|
3464
3490
|
if (!session || session.state === "disposed" || index < 0) {
|
|
3465
3491
|
return;
|
|
3466
3492
|
}
|
|
@@ -3570,17 +3596,6 @@ export class HlsSessionManager {
|
|
|
3570
3596
|
if (head - index > BEHIND_HEAD_REPAIR_MAX_SEGMENTS) {
|
|
3571
3597
|
return;
|
|
3572
3598
|
}
|
|
3573
|
-
// An audio rendition is never repaired by moving it. Its run is placed
|
|
3574
|
-
// where the VIEWER is, from the session they are watching, so a request
|
|
3575
|
-
// behind that is not a run in the wrong place — it is the player probing.
|
|
3576
|
-
// Measured 2026-08-15: changing track at 159 s, hls.js asked for the new
|
|
3577
|
-
// rendition's segment #0, the repair obligingly took the encoder to the
|
|
3578
|
-
// start of the film, and the audio the viewer was waiting for arrived
|
|
3579
|
-
// 20.5 s later instead of at once. Left where it is, the right segment is
|
|
3580
|
-
// already being produced.
|
|
3581
|
-
if (session.audioOnly === true) {
|
|
3582
|
-
return;
|
|
3583
|
-
}
|
|
3584
3599
|
// Nothing is encoding: a rung the viewer has switched away from is left
|
|
3585
3600
|
// exactly so, and its held requests must not bring its encoder back.
|
|
3586
3601
|
if (session.ffmpeg == null || hasChildExited(session.ffmpeg)) {
|
|
@@ -3591,6 +3606,37 @@ export class HlsSessionManager {
|
|
|
3591
3606
|
if (session.seekSettleTimer != null) {
|
|
3592
3607
|
return;
|
|
3593
3608
|
}
|
|
3609
|
+
// What separates a request the viewer is waiting for from the player
|
|
3610
|
+
// scanning the playlist is not TIME but what else it is asking for. On a
|
|
3611
|
+
// seek hls.js fires dozens of DIFFERENT indices within half a second (field
|
|
3612
|
+
// log: #178, #681, #725, #807, #74, #245, #387) and abandons them all; a
|
|
3613
|
+
// viewer waiting for audio asks for the SAME one, over and over, because it
|
|
3614
|
+
// is the only thing that will let playback continue.
|
|
3615
|
+
//
|
|
3616
|
+
// So: this index has been asked for at least twice, and it is the only
|
|
3617
|
+
// thing behind the head being asked for. Both are facts about the traffic,
|
|
3618
|
+
// available at once, where a delay is a guess about it — and it was three
|
|
3619
|
+
// seconds of the twenty a track change cost on 2026-08-15.
|
|
3620
|
+
const asked = session.behindHeadAsks?.get(index)?.count ?? 0;
|
|
3621
|
+
if (asked < BEHIND_HEAD_REPAIR_MIN_ASKS) {
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
// Counted over a WINDOW, not over the run: a scan is many indices at once,
|
|
3625
|
+
// while the same map left to accumulate would eventually hold every
|
|
3626
|
+
// behind-head request a long run ever saw and switch the repair off for
|
|
3627
|
+
// good.
|
|
3628
|
+
const scanSince = Date.now() - BEHIND_HEAD_SCAN_WINDOW_MS;
|
|
3629
|
+
let distinctBehind = 0;
|
|
3630
|
+
for (const record of session.behindHeadAsks?.values() ?? []) {
|
|
3631
|
+
if (record.at >= scanSince) {
|
|
3632
|
+
distinctBehind += 1;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
if (distinctBehind > BEHIND_HEAD_SCAN_INDICES) {
|
|
3636
|
+
// A scan, not a wait. Moving the encoder to one of these is moving it to
|
|
3637
|
+
// a number the player picked at random.
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3594
3640
|
const wantedAt = session.firstWantedAt?.get(index);
|
|
3595
3641
|
if (!Number.isFinite(wantedAt) || Date.now() - wantedAt < BEHIND_HEAD_REPAIR_MS) {
|
|
3596
3642
|
return;
|
|
@@ -4762,6 +4808,37 @@ export class HlsSessionManager {
|
|
|
4762
4808
|
return this.#viewerPositionOf(this.#activeVariant(base));
|
|
4763
4809
|
}
|
|
4764
4810
|
|
|
4811
|
+
/**
|
|
4812
|
+
* Where to start a separately published audio track, in seconds.
|
|
4813
|
+
*
|
|
4814
|
+
* The player, on changing track, discards the audio it holds and refills from
|
|
4815
|
+
* the PICTURE onwards — so that is where the encoder has to begin. What this
|
|
4816
|
+
* class knows directly is the read head, which runs ahead of the picture by
|
|
4817
|
+
* the player's own buffer; the browser reports that buffer with every link
|
|
4818
|
+
* report, so the picture is the one subtraction below.
|
|
4819
|
+
*
|
|
4820
|
+
* One segment of margin, because the report is up to ten seconds old and the
|
|
4821
|
+
* picture has moved on since — a run that begins a little early costs a
|
|
4822
|
+
* segment of audio nobody plays, while one that begins a little late is
|
|
4823
|
+
* behind the viewer and can only be fixed by restarting it.
|
|
4824
|
+
*
|
|
4825
|
+
* With no fresh report, the whole look-ahead is subtracted instead: it is the
|
|
4826
|
+
* furthest the two can be apart, so it cannot leave the run ahead of them.
|
|
4827
|
+
*
|
|
4828
|
+
* @param {HlsSession} base
|
|
4829
|
+
* @returns {number}
|
|
4830
|
+
*/
|
|
4831
|
+
#audioStartSecondsFor(base) {
|
|
4832
|
+
const watching = this.#activeVariant(base);
|
|
4833
|
+
const readHead = this.#viewerPositionOf(watching);
|
|
4834
|
+
const report = watching.netReport;
|
|
4835
|
+
const reportAge = Number.isFinite(report?.at) ? Date.now() - report.at : Number.POSITIVE_INFINITY;
|
|
4836
|
+
const buffered = reportAge <= NET_REPORT_FRESH_MS && Number.isFinite(report?.bufferedAheadSec)
|
|
4837
|
+
? report.bufferedAheadSec
|
|
4838
|
+
: LOOKAHEAD_PAUSE_SECONDS;
|
|
4839
|
+
return Math.max(0, readHead - buffered - this.segmentDurationSec);
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4765
4842
|
#viewerPositionOf(session) {
|
|
4766
4843
|
if (Number.isFinite(session.viewerPositionSeconds) && session.viewerPositionSeconds > 0) {
|
|
4767
4844
|
return session.viewerPositionSeconds;
|
|
@@ -5345,8 +5422,23 @@ export class HlsSessionManager {
|
|
|
5345
5422
|
// while the player is asking for segment #537 — and the audio would begin
|
|
5346
5423
|
// at zero and never catch up, since nothing treats a far request as a
|
|
5347
5424
|
// seek. The accessor falls back to the last segment actually requested.
|
|
5348
|
-
|
|
5349
|
-
|
|
5425
|
+
// Where the PICTURE is, not where it has been read to.
|
|
5426
|
+
//
|
|
5427
|
+
// The position this class keeps is written by the segments a session
|
|
5428
|
+
// serves, so it is the READ head, and the viewer's picture sits behind it
|
|
5429
|
+
// by everything the player has buffered. Started at the read head, the
|
|
5430
|
+
// audio run begins AHEAD of the viewer, and every request they then make
|
|
5431
|
+
// is behind a run that only moves forward — field 2026-08-15, placed at
|
|
5432
|
+
// #16 while the player asked for #10, and the audio arrived only after
|
|
5433
|
+
// the encoder was dragged back.
|
|
5434
|
+
//
|
|
5435
|
+
// The distance is measured, not assumed: the browser reports how many
|
|
5436
|
+
// seconds it holds ahead of the picture with every link report, so the
|
|
5437
|
+
// playhead is one subtraction away. A stale report is no use — a viewer
|
|
5438
|
+
// who seeked since then is somewhere else entirely — so an old one is
|
|
5439
|
+
// ignored and the whole look-ahead is subtracted instead, which cannot
|
|
5440
|
+
// leave the run ahead of them.
|
|
5441
|
+
startPositionSeconds: this.#audioStartSecondsFor(base),
|
|
5350
5442
|
segmentFormatId: base.segmentFormat.id,
|
|
5351
5443
|
// Cut where the picture is cut. Two streams meant to be played together
|
|
5352
5444
|
// have to be divided at the same times, and the grid is the base's — the
|
|
@@ -5874,34 +5966,6 @@ export class HlsSessionManager {
|
|
|
5874
5966
|
// at this position (server-side seeking). The caller long-polls.
|
|
5875
5967
|
if (!isPlaylist) {
|
|
5876
5968
|
const requestedIndex = session.segmentFormat.segmentIndexFromName(fileName);
|
|
5877
|
-
// An audio rendition asked for something behind its run says so at once
|
|
5878
|
-
// instead of holding. Its run is placed where the viewer is and is not
|
|
5879
|
-
// moved from there, so the request can never be answered — and holding it
|
|
5880
|
-
// for the full window costs the player its own patience on the fragment
|
|
5881
|
-
// it needs NEXT. Measured 2026-08-15: on a track change at 159 s hls.js
|
|
5882
|
-
// asked for segment #0, and a held request plus a repaired encoder cost
|
|
5883
|
-
// 20.5 s of silence. Refused promptly, the player moves to the segment
|
|
5884
|
-
// that is genuinely being produced.
|
|
5885
|
-
if (
|
|
5886
|
-
session.audioOnly === true &&
|
|
5887
|
-
Number.isFinite(requestedIndex) &&
|
|
5888
|
-
requestedIndex < (session.encodeStartIndex ?? 0) &&
|
|
5889
|
-
session.ffmpeg != null &&
|
|
5890
|
-
// Not while a seek of its own is settling. A viewer seeking BACKWARDS
|
|
5891
|
-
// is reported to the base and forwarded here, but the run only moves
|
|
5892
|
-
// when the settle fires — until then `encodeStartIndex` still names the
|
|
5893
|
-
// old position, and every request for the new one is "behind" it. Those
|
|
5894
|
-
// are exactly the requests the viewer is waiting for, so they are held,
|
|
5895
|
-
// as they were before this refusal existed.
|
|
5896
|
-
session.seekTarget == null &&
|
|
5897
|
-
session.seekSettleTimer == null
|
|
5898
|
-
) {
|
|
5899
|
-
logger.info(
|
|
5900
|
-
`transcode ${session.id} audio segment #${requestedIndex} is behind this rendition's run ` +
|
|
5901
|
-
`(#${session.encodeStartIndex}); it is not made and the run stays where the viewer is`
|
|
5902
|
-
);
|
|
5903
|
-
return { kind: "not-found" };
|
|
5904
|
-
}
|
|
5905
5969
|
this.#ensureEncodingFor(
|
|
5906
5970
|
session,
|
|
5907
5971
|
requestedIndex,
|
|
@@ -71,16 +71,23 @@ async function managerWithRunAhead() {
|
|
|
71
71
|
return { manager, session, dirPath, restarts };
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
test("a request behind the run is repaired once
|
|
74
|
+
test("a request behind the run is repaired once the player asks again", async (t) => {
|
|
75
75
|
const { manager, session, dirPath } = await managerWithRunAhead();
|
|
76
76
|
t.after(async () => {
|
|
77
77
|
await manager.disposeAll();
|
|
78
78
|
await rm(dirPath, { recursive: true, force: true });
|
|
79
79
|
});
|
|
80
|
-
// Asked for
|
|
81
|
-
|
|
80
|
+
// Asked for a moment ago and still unanswerable. What decides is that the
|
|
81
|
+
// player comes BACK for the same segment: it is the only one it wants, and no
|
|
82
|
+
// amount of waiting could say that as clearly.
|
|
83
|
+
session.firstWantedAt.set(WANTED, Date.now() - 1000);
|
|
84
|
+
const name = fmp4Format.segmentFileName(WANTED);
|
|
82
85
|
|
|
83
|
-
await manager.getFileStream(SESSION_ID,
|
|
86
|
+
await manager.getFileStream(SESSION_ID, name, { requestSeq: 1 });
|
|
87
|
+
|
|
88
|
+
assert.equal(session.seekTarget, null, "one poll is not yet evidence");
|
|
89
|
+
|
|
90
|
+
await manager.getFileStream(SESSION_ID, name, { requestSeq: 2 });
|
|
84
91
|
|
|
85
92
|
assert.equal(
|
|
86
93
|
session.seekTarget,
|
|
@@ -89,6 +96,33 @@ test("a request behind the run is repaired once it has waited", async (t) => {
|
|
|
89
96
|
);
|
|
90
97
|
});
|
|
91
98
|
|
|
99
|
+
test("a burst of different segments behind the run is a scan, and moves nothing", async (t) => {
|
|
100
|
+
const { manager, session, dirPath } = await managerWithRunAhead();
|
|
101
|
+
t.after(async () => {
|
|
102
|
+
await manager.disposeAll();
|
|
103
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
104
|
+
});
|
|
105
|
+
// What hls.js does on a seek: dozens of indices within half a second, each
|
|
106
|
+
// abandoned. Field log 2026-08-02: #178, #681, #725, #807, #74, #245, #387.
|
|
107
|
+
const scanned = [WANTED, WANTED - 40, WANTED - 120, WANTED - 200, WANTED - 300];
|
|
108
|
+
for (const index of scanned) {
|
|
109
|
+
session.firstWantedAt.set(index, Date.now() - 1000);
|
|
110
|
+
}
|
|
111
|
+
// Interleaved, as they arrive on the wire: the player opens them together
|
|
112
|
+
// rather than finishing with one before opening the next.
|
|
113
|
+
for (const seq of [1, 2]) {
|
|
114
|
+
for (const index of scanned) {
|
|
115
|
+
await manager.getFileStream(SESSION_ID, fmp4Format.segmentFileName(index), { requestSeq: seq });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
assert.equal(
|
|
120
|
+
session.seekTarget,
|
|
121
|
+
null,
|
|
122
|
+
"moving the encoder to one of these would be moving it to a number the player picked at random"
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
92
126
|
test("a request behind the run is left alone at first", async (t) => {
|
|
93
127
|
const { manager, session, dirPath } = await managerWithRunAhead();
|
|
94
128
|
t.after(async () => {
|
|
@@ -638,46 +638,68 @@ test("a rung served by copy stays offered while a re-encoded rung is on screen",
|
|
|
638
638
|
);
|
|
639
639
|
});
|
|
640
640
|
|
|
641
|
-
test("
|
|
642
|
-
const { manager, dirPath } = await managerWithBase();
|
|
641
|
+
test("a separately published audio track starts where the picture is, from the reported buffer", async (t) => {
|
|
642
|
+
const { manager, base, dirPath } = await managerWithBase();
|
|
643
643
|
t.after(async () => {
|
|
644
644
|
await manager.disposeAll();
|
|
645
645
|
await rm(dirPath, { recursive: true, force: true });
|
|
646
646
|
});
|
|
647
|
-
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
647
|
+
base.audioSeparate = true;
|
|
648
|
+
// Served up to 140 s, and the browser says it holds 40 s ahead of the
|
|
649
|
+
// picture — so the viewer is at 100 s, and that, less a segment of margin,
|
|
650
|
+
// is where the track has to begin.
|
|
651
|
+
base.viewerPositionSeconds = 140;
|
|
652
|
+
base.netReport = { linkMbps: 20, bufferedAheadSec: 40, at: Date.now() };
|
|
653
|
+
manager.getCachedAudioTracks = () => [
|
|
654
|
+
{ index: 0, language: "rus", title: "", isDefault: true },
|
|
655
|
+
{ index: 1, language: "eng", title: "", isDefault: false }
|
|
656
|
+
];
|
|
657
|
+
const created = [];
|
|
658
|
+
manager.createOrGetSession = async (params) => {
|
|
659
|
+
created.push(params);
|
|
660
|
+
const rendition = fakeSession({ id: VARIANT_ID, encodeHeight: 0, dirPath });
|
|
661
|
+
rendition.audioOnly = true;
|
|
662
|
+
return { sessionId: VARIANT_ID, session: rendition };
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
await manager.resolveAudioRenditionFile(BASE_ID, 1, "segment-00010.mp4");
|
|
666
|
+
|
|
667
|
+
assert.equal(created.length, 1, "the track's own session was made");
|
|
664
668
|
assert.equal(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
"
|
|
669
|
+
created[0].startPositionSeconds,
|
|
670
|
+
96,
|
|
671
|
+
"140 s served, less the 40 s the player holds, less one segment of margin"
|
|
668
672
|
);
|
|
669
|
-
|
|
670
|
-
assert.equal(rendition.seekTarget ?? null, null, "no seek was armed for it either");
|
|
673
|
+
});
|
|
671
674
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
675
|
+
test("a stale buffer report is not used to place an audio track", async (t) => {
|
|
676
|
+
const { manager, base, dirPath } = await managerWithBase();
|
|
677
|
+
t.after(async () => {
|
|
678
|
+
await manager.disposeAll();
|
|
679
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
680
|
+
});
|
|
681
|
+
base.audioSeparate = true;
|
|
682
|
+
base.viewerPositionSeconds = 300;
|
|
683
|
+
// Sent a minute ago: the viewer may have seeked anywhere since, so it says
|
|
684
|
+
// nothing about where they are now.
|
|
685
|
+
base.netReport = { linkMbps: 20, bufferedAheadSec: 5, at: Date.now() - 60_000 };
|
|
686
|
+
manager.getCachedAudioTracks = () => [
|
|
687
|
+
{ index: 0, language: "rus", title: "", isDefault: true },
|
|
688
|
+
{ index: 1, language: "eng", title: "", isDefault: false }
|
|
689
|
+
];
|
|
690
|
+
const created = [];
|
|
691
|
+
manager.createOrGetSession = async (params) => {
|
|
692
|
+
created.push(params);
|
|
693
|
+
const rendition = fakeSession({ id: VARIANT_ID, encodeHeight: 0, dirPath });
|
|
694
|
+
rendition.audioOnly = true;
|
|
695
|
+
return { sessionId: VARIANT_ID, session: rendition };
|
|
696
|
+
};
|
|
679
697
|
|
|
680
|
-
|
|
698
|
+
await manager.resolveAudioRenditionFile(BASE_ID, 1, "segment-00010.mp4");
|
|
681
699
|
|
|
682
|
-
assert.equal(
|
|
700
|
+
assert.equal(
|
|
701
|
+
created[0].startPositionSeconds,
|
|
702
|
+
176,
|
|
703
|
+
"the whole look-ahead is subtracted instead — it cannot leave the run ahead of the viewer"
|
|
704
|
+
);
|
|
683
705
|
});
|