@torrent-tv/proxy 2.57.0 → 2.58.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.
@@ -122,3 +122,37 @@ test("a burst big enough to explain the lag is not called a stopped association"
122
122
  );
123
123
  assert.equal(verdict, "flowing");
124
124
  });
125
+
126
+ test("the peer's own answering cadence counts toward the allowance", () => {
127
+ // Field case 2026-08-27: queues empty, 3.4 MB/s crossing, tab hidden so the
128
+ // browser echoed about once a second. Without the peer's cadence the
129
+ // allowance is one probe and every other line read `association-stopped`.
130
+ const withoutCadence = allowedGap({
131
+ queuedBytes: 0,
132
+ bytesPerSecond: 3.4 * 1024 * 1024,
133
+ rttMs: 9
134
+ });
135
+ assert.equal(withoutCadence, 1);
136
+ const withCadence = allowedGap({
137
+ queuedBytes: 0,
138
+ bytesPerSecond: 3.4 * 1024 * 1024,
139
+ rttMs: 9,
140
+ echoIntervalMs: 1000
141
+ });
142
+ assert.ok(withCadence >= 3, `a second of cadence must allow more than ${withCadence}`);
143
+ const allowed = Object.fromEntries(ALL.map((label) => [label, withCadence]));
144
+ const { verdict } = readProbeState(
145
+ state({ proxy: 98, "proxy-control": 98, "proxy-fast": 98 }, { allowed, echoAgeMs: 977 })
146
+ );
147
+ assert.equal(verdict, "flowing");
148
+ });
149
+
150
+ test("a stale echo is judged against the peer's cadence, not a fixed half second", () => {
151
+ // The same numbers with the cadence unknown must still be able to say the
152
+ // reverse direction is gone — the bound rises with the cadence, it does not
153
+ // disappear.
154
+ const { verdict } = readProbeState(
155
+ state({ proxy: 99, "proxy-control": 99, "proxy-fast": 99 }, { echoAgeMs: 60_000, echoStaleMs: 2000 })
156
+ );
157
+ assert.equal(verdict, "reverse-direction-gone");
158
+ });
@@ -37,6 +37,10 @@ const ID_TRACK_NUMBER = 0xd7;
37
37
  const ID_TRACK_TYPE = 0x83;
38
38
  const ID_CODEC_ID = 0x86;
39
39
  const ID_LANGUAGE = 0x22b59c;
40
+ const ID_LANGUAGE_BCP47 = 0x22b59d;
41
+ const ID_FLAG_ENABLED = 0xb9;
42
+ const ID_FLAG_FORCED = 0x55aa;
43
+ const ID_FLAG_HEARING_IMPAIRED = 0x55ab;
40
44
  const ID_CUES = 0x1c53bb6b;
41
45
  const ID_CUE_POINT = 0xbb;
42
46
  const ID_CUE_TIME = 0xb3;
@@ -95,13 +99,26 @@ function uint32Element(id, value) {
95
99
  return element(id, payload);
96
100
  }
97
101
 
98
- function trackEntry({ number, type, codecId, language }) {
99
- return element(ID_TRACK_ENTRY, Buffer.concat([
102
+ function trackEntry({ number, type, codecId, language, flags = {}, languageBcp47 = null }) {
103
+ const parts = [
100
104
  uintElement(ID_TRACK_NUMBER, number),
101
105
  uintElement(ID_TRACK_TYPE, type),
102
106
  stringElement(ID_CODEC_ID, codecId),
103
107
  stringElement(ID_LANGUAGE, language)
104
- ]));
108
+ ];
109
+ if (languageBcp47 !== null) {
110
+ parts.push(stringElement(ID_LANGUAGE_BCP47, languageBcp47));
111
+ }
112
+ for (const [id, value] of [
113
+ [ID_FLAG_ENABLED, flags.enabled],
114
+ [ID_FLAG_FORCED, flags.forced],
115
+ [ID_FLAG_HEARING_IMPAIRED, flags.hearingImpaired]
116
+ ]) {
117
+ if (value !== undefined) {
118
+ parts.push(uintElement(id, value ? 1 : 0));
119
+ }
120
+ }
121
+ return element(ID_TRACK_ENTRY, Buffer.concat(parts));
105
122
  }
106
123
 
107
124
  /**
@@ -260,3 +277,94 @@ test("two walks of one file at the same time read each cluster once", async () =
260
277
  );
261
278
  forgetSubtitles(sourceKey);
262
279
  });
280
+
281
+ /**
282
+ * A file whose subtitle tracks carry the flags RFC 9559 defines for them: one
283
+ * forced, one for viewers who cannot hear, one the file marks unusable, and one
284
+ * writing its language as RFC 5646 alongside the three-letter code.
285
+ *
286
+ * @returns {Buffer}
287
+ */
288
+ function fileWithFlags() {
289
+ const info = element(ID_INFO, uintElement(ID_TIMESTAMP_SCALE, 1_000_000));
290
+ const tracks = element(ID_TRACKS, Buffer.concat([
291
+ trackEntry({ number: 1, type: 1, codecId: "V_MPEG4/ISO/AVC", language: "und" }),
292
+ trackEntry({ number: 2, type: 17, codecId: "S_TEXT/UTF8", language: "rus", flags: { forced: true } }),
293
+ trackEntry({ number: 3, type: 17, codecId: "S_TEXT/UTF8", language: "eng", flags: { hearingImpaired: true } }),
294
+ trackEntry({ number: 4, type: 17, codecId: "S_TEXT/UTF8", language: "fre", flags: { enabled: false } }),
295
+ trackEntry({ number: 5, type: 17, codecId: "S_TEXT/ASS", language: "por", languageBcp47: "pt-BR" })
296
+ ]));
297
+ const seekEntry = (targetId, position) => element(ID_SEEK, Buffer.concat([
298
+ element(ID_SEEK_ID, idBytes(targetId)),
299
+ uint32Element(ID_SEEK_POSITION, position)
300
+ ]));
301
+ const seekHeadWith = (infoAt, tracksAt) => element(ID_SEEK_HEAD, Buffer.concat([
302
+ seekEntry(ID_INFO, infoAt),
303
+ seekEntry(ID_TRACKS, tracksAt)
304
+ ]));
305
+ const headLength = seekHeadWith(0, 0).length;
306
+ const segmentPayload = Buffer.concat([
307
+ seekHeadWith(headLength, headLength + info.length),
308
+ info,
309
+ tracks
310
+ ]);
311
+ const ebml = element(ID_EBML, Buffer.from([0x42, 0x86, 0x81, 0x01]));
312
+ return Buffer.concat([ebml, element(ID_SEGMENT, segmentPayload)]);
313
+ }
314
+
315
+ test("the flags the file states about a track are read, not guessed from its name", async () => {
316
+ const plan = await readSubtitlePlan(readerOver(fileWithFlags()), fileWithFlags().length);
317
+
318
+ const forced = plan.tracks.find((track) => track.trackNumber === 2);
319
+ assert.equal(forced.isForced, true, "FlagForced 0x55AA");
320
+ assert.equal(forced.isHearingImpaired, false);
321
+
322
+ const sdh = plan.tracks.find((track) => track.trackNumber === 3);
323
+ assert.equal(sdh.isHearingImpaired, true, "FlagHearingImpaired 0x55AB");
324
+ assert.equal(sdh.isForced, false);
325
+ });
326
+
327
+ test("a track the file marks unusable is not offered, but is still counted", async () => {
328
+ // FlagEnabled (0xB9): "Set to 1 if the track is usable." Track 4 says 0, so
329
+ // it is not offered — but it KEEPS its place in the numbering, because ffmpeg
330
+ // keeps it: `matroskadec.c` parses MATROSKA_ID_TRACKFLAGENABLED as EBML_NONE,
331
+ // reading the element and storing nothing, so the stream is created and gets
332
+ // its own `0:s:N`. Dropping it here would shift every track after it.
333
+ const plan = await readSubtitlePlan(readerOver(fileWithFlags()), fileWithFlags().length);
334
+
335
+ assert.equal(plan.tracks.some((track) => track.trackNumber === 4), false, "not offered for extraction");
336
+ const counted = plan.declared.find((track) => track.trackNumber === 4);
337
+ assert.ok(counted, "still declared, so the numbering does not move");
338
+ assert.equal(counted.isEnabled, false);
339
+ });
340
+
341
+ test("an unusable track keeps its place, so the tracks after it keep theirs", async () => {
342
+ const plan = await readSubtitlePlan(readerOver(fileWithFlags()), fileWithFlags().length);
343
+
344
+ // s:0 forced, s:1 SDH, s:2 the unusable one, s:3 the Brazilian track.
345
+ assert.deepEqual(
346
+ plan.tracks.map((track) => [track.trackNumber, track.declaredIndex]),
347
+ [[2, 0], [3, 1], [5, 3]]
348
+ );
349
+ });
350
+
351
+ test("the list ffmpeg is lined up against still speaks ffmpeg's language codes", async () => {
352
+ // `declared` exists to be paired with the `-i` banner, which prints the
353
+ // three-letter code; reporting "pt-BR" there would break the pairing and cost
354
+ // the FlagDefault reading with it.
355
+ const plan = await readSubtitlePlan(readerOver(fileWithFlags()), fileWithFlags().length);
356
+
357
+ const declared = plan.declared.find((track) => track.trackNumber === 5);
358
+ assert.equal(declared.language, "por");
359
+ assert.equal(declared.languageBcp47, "pt-BR");
360
+ });
361
+
362
+ test("where the file writes RFC 5646, that is the language", async () => {
363
+ // "If this element is used, then any Language elements used in the same
364
+ // TrackEntry MUST be ignored."
365
+ const plan = await readSubtitlePlan(readerOver(fileWithFlags()), fileWithFlags().length);
366
+
367
+ const track = plan.tracks.find((entry) => entry.trackNumber === 5);
368
+ assert.equal(track.language, "pt-BR", "not the three-letter por");
369
+ assert.equal(track.languageBcp47, "pt-BR");
370
+ });