@torrent-tv/proxy 2.25.1 → 2.26.0
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/package.json +1 -1
- package/services/hls-session-manager.js +43 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.26.0
|
|
2
|
+
|
|
3
|
+
- **New**: The keyframe-index measurement now answers the question it was raising. Each file's summary reports the distribution of how far produced segments fell from the playlist (median and worst, not one extreme), how many keyframes were read from the container, and — the discriminator — **how many of the disagreeing segments began at ANOTHER time the same table names**. That separates the two explanations that have been argued rather than measured: a table describing times the file does not have, against a table listing only some keyframes with our grid built over its gaps. Every deviation measured on 2026-08-17 was positive, 0.58-2.96 s, which is what a cut pushed forward to the next real keyframe looks like. The summary is also written every 25 distinct boundaries instead of only when a session is disposed, because a proxy restart — every addon update is one — takes its sessions with it and the summary was routinely never written.
|
|
4
|
+
|
|
1
5
|
## 2.25.1
|
|
2
6
|
|
|
3
7
|
- **Fix**: Picture and sound are back in step. Two releases in a row moved a segment's stamp toward the playlist — 2.24.1 per session, 2.25.0 by one offset for the whole family — and both desynced playback in the field the same day. The reason is what the first segment of a run is: it is not CUT at all, it begins where ffmpeg's seek landed, and the picture must land on a keyframe while the sound needs none, so after every restart the two runs genuinely begin at different real times and the whole run carries that difference (measured: the sound's #292 began at 1587.892 s and #293 at 1592.692 s, one segment apart, the run shifted 2.5 s from the grid). Labelling each track with its own true time is what keeps them together in real time; a segment is stamped with its own start again, as it was for weeks before 2.24.1. What stays from those releases is the part that was right: one published timeline per family, and a warning when a piece lands further from the playlist than a player will bridge.
|
package/package.json
CHANGED
|
@@ -202,6 +202,18 @@ export function newIndexCheck() {
|
|
|
202
202
|
disagreed: 0,
|
|
203
203
|
maxDeviationSec: 0,
|
|
204
204
|
firstDisagreementIndex: -1,
|
|
205
|
+
// Every deviation, so the summary can report a distribution instead of one
|
|
206
|
+
// extreme. Bounded by the number of distinct boundaries a session produces.
|
|
207
|
+
deviations: [],
|
|
208
|
+
// Of the segments that started away from the playlist, how many began at
|
|
209
|
+
// ANOTHER time in the very list the grid was built from. This is the
|
|
210
|
+
// measurement that separates the two explanations: a table that describes
|
|
211
|
+
// times the file does not have, against a table that lists only SOME
|
|
212
|
+
// keyframes and a grid built over its gaps. Asked 2026-08-17 by the user,
|
|
213
|
+
// who was right that the second is far more likely — every deviation
|
|
214
|
+
// measured that day was positive, 0.58-2.96 s, which is what a cut pushed
|
|
215
|
+
// forward to the next real keyframe looks like.
|
|
216
|
+
landedOnAnotherKeyframe: 0,
|
|
205
217
|
// Which boundaries have been counted. A segment can be requested again, and
|
|
206
218
|
// a repeat is the same boundary, not new evidence.
|
|
207
219
|
seen: new Set()
|
|
@@ -217,12 +229,17 @@ export function newIndexCheck() {
|
|
|
217
229
|
* start the playlist declared for it.
|
|
218
230
|
* @returns {void}
|
|
219
231
|
*/
|
|
220
|
-
export function noteIndexDeviation(check, index, deviationSec) {
|
|
232
|
+
export function noteIndexDeviation(check, index, deviationSec, landedOnKeyframe = null) {
|
|
221
233
|
if (check.seen.has(index)) {
|
|
222
234
|
return;
|
|
223
235
|
}
|
|
224
236
|
check.seen.add(index);
|
|
225
237
|
check.checked += 1;
|
|
238
|
+
check.deviations ??= [];
|
|
239
|
+
check.deviations.push(deviationSec);
|
|
240
|
+
if (landedOnKeyframe === true) {
|
|
241
|
+
check.landedOnAnotherKeyframe = (check.landedOnAnotherKeyframe ?? 0) + 1;
|
|
242
|
+
}
|
|
226
243
|
if (deviationSec > SEGMENT_START_DISAGREEMENT_SEC) {
|
|
227
244
|
check.disagreed += 1;
|
|
228
245
|
if (check.firstDisagreementIndex < 0) {
|
|
@@ -4786,7 +4803,14 @@ export class HlsSessionManager {
|
|
|
4786
4803
|
#noteIndexAccuracy(session, index, trueStart, declaredStart) {
|
|
4787
4804
|
const deviation = Math.abs(trueStart - declaredStart);
|
|
4788
4805
|
session.indexCheck ??= newIndexCheck();
|
|
4789
|
-
|
|
4806
|
+
// Did this segment begin at ANOTHER keyframe from the same list? Half an
|
|
4807
|
+
// audio frame is the tolerance — anything the list names is exact, so a
|
|
4808
|
+
// match is a match. `keyframeTimes` is the list the grid was built from, so
|
|
4809
|
+
// this compares the file against the table on the table's own terms.
|
|
4810
|
+
const knownKeyframe = Array.isArray(session.keyframeTimes)
|
|
4811
|
+
? session.keyframeTimes.some((time) => Math.abs(time - trueStart) <= 0.05)
|
|
4812
|
+
: null;
|
|
4813
|
+
noteIndexDeviation(session.indexCheck, index, deviation, knownKeyframe);
|
|
4790
4814
|
if (deviation > SEGMENT_START_DISAGREEMENT_SEC) {
|
|
4791
4815
|
// Which boundary the true start DOES match, if any. This is what tells
|
|
4792
4816
|
// the two possible faults apart, and they need opposite fixes: matching
|
|
@@ -4808,6 +4832,14 @@ export class HlsSessionManager {
|
|
|
4808
4832
|
: "the container's keyframe index disagrees with the file; using the file")
|
|
4809
4833
|
);
|
|
4810
4834
|
}
|
|
4835
|
+
// Said as the evidence accumulates, not only when the session is disposed.
|
|
4836
|
+
// A proxy restart takes its sessions with it — every addon update does —
|
|
4837
|
+
// and a summary that only ever appears at the end is a summary that is
|
|
4838
|
+
// routinely never written. Twenty-five distinct boundaries is enough for
|
|
4839
|
+
// the proportion to mean something and rare enough not to repeat itself.
|
|
4840
|
+
if (session.indexCheck.checked > 0 && session.indexCheck.checked % 25 === 0) {
|
|
4841
|
+
this.#logIndexAccuracy(session);
|
|
4842
|
+
}
|
|
4811
4843
|
this.correctBoundaryFromSegment(session, index, trueStart);
|
|
4812
4844
|
}
|
|
4813
4845
|
|
|
@@ -4974,12 +5006,19 @@ export class HlsSessionManager {
|
|
|
4974
5006
|
if (!check || check.checked === 0) {
|
|
4975
5007
|
return;
|
|
4976
5008
|
}
|
|
5009
|
+
const deviations = [...(check.deviations ?? [])].sort((left, right) => left - right);
|
|
5010
|
+
const median = deviations.length > 0 ? deviations[Math.floor(deviations.length / 2)] : 0;
|
|
5011
|
+
const landed = check.landedOnAnotherKeyframe ?? 0;
|
|
4977
5012
|
logger.info(
|
|
4978
5013
|
`keyframe-index ${session.containerFormat || "unknown"} "${session.fileName}": ` +
|
|
4979
5014
|
`${check.disagreed} of ${check.checked} produced segments started away from the playlist, ` +
|
|
4980
|
-
`worst ${check.maxDeviationSec.toFixed(3)}s` +
|
|
5015
|
+
`median ${median.toFixed(3)}s worst ${check.maxDeviationSec.toFixed(3)}s` +
|
|
4981
5016
|
(check.firstDisagreementIndex >= 0 ? ` (first at #${check.firstDisagreementIndex})` : "") +
|
|
4982
|
-
|
|
5017
|
+
// The discriminator, stated in the same line as the count it explains: a
|
|
5018
|
+
// segment that began at another time the SAME table names was not
|
|
5019
|
+
// mis-described by the table — the grid was built over a gap in it.
|
|
5020
|
+
`; ${landed} of them began at another keyframe the table names` +
|
|
5021
|
+
` [tolerance ${SEGMENT_START_DISAGREEMENT_SEC}s, ${(session.keyframeTimes?.length ?? 0)} keyframes read]`
|
|
4983
5022
|
);
|
|
4984
5023
|
}
|
|
4985
5024
|
|