@torrent-tv/proxy 2.89.6 → 2.89.8

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.
Files changed (92) hide show
  1. package/AGENTS.md +32 -12
  2. package/CHANGELOG.md +2340 -2330
  3. package/assets/calibration/audio/NOTICE.md +19 -19
  4. package/assets/calibration/audio/manifest.json +98 -98
  5. package/biome.json +412 -412
  6. package/docs/browser-proxy-contract.md +155 -155
  7. package/docs/container-architecture.md +273 -273
  8. package/docs/encode-run-state.md +1 -1
  9. package/docs/playback-start-forecast.md +113 -98
  10. package/package.json +51 -50
  11. package/routes/api/sources/files/get.js +106 -106
  12. package/scripts/build-audio-samples.mjs +31 -31
  13. package/services/encode/EncodeRun.js +67 -15
  14. package/services/encode/EncodeRuns.js +19 -7
  15. package/services/encode/OutputOpening.js +784 -784
  16. package/services/encode/audio-calibration.js +67 -67
  17. package/services/encode/audio-work-rate.js +15 -15
  18. package/services/encode/encode-exit.js +2 -0
  19. package/services/encode/hwaccel.js +1894 -1894
  20. package/services/encode/output/index.js +24 -24
  21. package/services/encode/piece-completeness.js +60 -2
  22. package/services/encode/run-command.js +757 -755
  23. package/services/encode/segment-formats/index.js +1 -0
  24. package/services/encode/segment-formats/mp4-boxes.js +8 -1
  25. package/services/media/ContainerOrchestrator.js +202 -202
  26. package/services/media/KeyframeTables.js +356 -356
  27. package/services/media/SubtitleCues.js +592 -592
  28. package/services/media/audio-inventory.js +299 -299
  29. package/services/media/container/AviContainer.js +268 -268
  30. package/services/media/container/Container.js +632 -632
  31. package/services/media/container/ContainerFactory.js +38 -38
  32. package/services/media/container/MatroskaContainer.js +1031 -1031
  33. package/services/media/container/Mp4Container.js +1354 -1354
  34. package/services/media/container/SubtitleFileContainer.js +339 -339
  35. package/services/media/container/ebml-stream.js +174 -174
  36. package/services/media/container/matroska-clusters.js +1165 -1165
  37. package/services/media/container/unavailable.js +77 -77
  38. package/services/media/tracks/ContainerTrack.js +84 -84
  39. package/services/media/tracks/TextSubtitleTrack.js +295 -295
  40. package/services/storage/piece-store/shared-piece-store.js +1937 -1937
  41. package/services/storage/segment-store/SegmentStore.js +1294 -1294
  42. package/services/torrent/Contents.js +382 -382
  43. package/services/torrent/episode-naming.js +182 -182
  44. package/services/torrent/torrent-pool.js +2688 -2688
  45. package/services/torrent/worker/client.js +836 -836
  46. package/services/torrent/worker/held-bytes.js +231 -231
  47. package/services/torrent/worker/worker.js +1137 -1137
  48. package/services/transport/data-channel-handler.js +1373 -1373
  49. package/services/viewer/PriorityMap.js +421 -421
  50. package/test/audio-calibration.test.js +23 -23
  51. package/test/auto-quality-step.test.js +762 -762
  52. package/test/coalesce.test.js +39 -39
  53. package/test/coverage-follows-the-disk.test.js +5 -0
  54. package/test/decode-cost.test.js +475 -475
  55. package/test/encode-run.test.js +58 -0
  56. package/test/episode-naming.test.js +131 -131
  57. package/test/failed-encoder-still-serves-what-exists.test.js +1 -1
  58. package/test/fixtures/audio-frame-boundary.json +1 -1
  59. package/test/fixtures/refused-audio-20261002.json +10 -0
  60. package/test/fmp4-presentation-clock.test.js +4 -2
  61. package/test/format-keyed-outputs.test.js +78 -78
  62. package/test/health-metrics.test.js +11 -13
  63. package/test/held-read-ownership.test.js +77 -77
  64. package/test/helpers/encode-run.js +14 -6
  65. package/test/helpers/held-file.js +77 -77
  66. package/test/helpers/manager.js +62 -62
  67. package/test/helpers/matroska-file.js +296 -296
  68. package/test/keyframe-table-budget.test.js +123 -123
  69. package/test/matroska-cluster-discovery.test.js +323 -323
  70. package/test/one-output-one-session.test.js +273 -273
  71. package/test/output-speed.test.js +6 -1
  72. package/test/piece-completeness.test.js +37 -1
  73. package/test/quality-variants.test.js +1274 -1270
  74. package/test/rate-control-in-the-output.test.js +4 -3
  75. package/test/reads-not-yet-arrived.test.js +218 -218
  76. package/test/seek-frees-the-viewers-requests.test.js +1 -1
  77. package/test/segment-closed-on-both-branches.test.js +114 -114
  78. package/test/segment-judged-by-its-own-header.test.js +222 -222
  79. package/test/segment-serve-wiring.test.js +481 -481
  80. package/test/serving-existing-output.test.js +111 -111
  81. package/test/source-files-route.test.js +113 -113
  82. package/test/subtitle-cue-walk.test.js +385 -385
  83. package/test/subtitle-metadata.test.js +15 -15
  84. package/test/subtitle-plan-waits.test.js +125 -125
  85. package/test/subtitle-track-numbering.test.js +391 -391
  86. package/test/two-viewers-one-picture.test.js +508 -508
  87. package/test/viewer-requests-admission.test.js +92 -92
  88. package/test/viewer-starts-on-map.test.js +27 -27
  89. package/test/viewer-states.test.js +5 -2
  90. package/test/viewing-generations.test.js +390 -390
  91. package/test/whole-file-held-read.test.js +30 -30
  92. package/utils/coalesce.js +41 -41
@@ -1,24 +1,24 @@
1
- export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
2
- export { PLAYLIST_FILE_NAME } from "./playlists.js";
3
-
4
- // FFmpeg may close a segment this far before its requested cut. Both the
5
- // command and coverage validation use this existing muxer configuration.
6
- export const SEGMENT_CUT_TIME_DELTA_SECONDS = 0.05;
7
-
8
- /**
9
- * The code an error carries when no output suits the viewer: nothing this proxy
10
- * could produce or already holds is admitted by their link. Its `details` say
11
- * why and with which figures. A definition shared by both sides of the
12
- * interface — the encoding that decides it and the routes that answer it.
13
- */
14
- export const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
15
-
16
- /**
17
- * The code an error carries when THIS MACHINE cannot take the output: no mode
18
- * of its encoder was shown at startup to hold the picture at any size this
19
- * viewer could be given, or the machine has no place for one more encoder
20
- * beside what already runs (roadmap item 97, step 14). Not the viewer's link —
21
- * another proxy may well serve it, and the page asks the pool before anything
22
- * plays. Its `details` say why, with the figures.
23
- */
24
- export const OUTPUT_NO_CAPACITY = "OUTPUT_NO_CAPACITY";
1
+ export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
2
+ export { PLAYLIST_FILE_NAME } from "./playlists.js";
3
+
4
+ // FFmpeg may close a segment this far before its requested cut. Both the
5
+ // command and coverage validation use this existing muxer configuration.
6
+ export const SEGMENT_CUT_TIME_DELTA_SECONDS = 0.05;
7
+
8
+ /**
9
+ * The code an error carries when no output suits the viewer: nothing this proxy
10
+ * could produce or already holds is admitted by their link. Its `details` say
11
+ * why and with which figures. A definition shared by both sides of the
12
+ * interface — the encoding that decides it and the routes that answer it.
13
+ */
14
+ export const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
15
+
16
+ /**
17
+ * The code an error carries when THIS MACHINE cannot take the output: no mode
18
+ * of its encoder was shown at startup to hold the picture at any size this
19
+ * viewer could be given, or the machine has no place for one more encoder
20
+ * beside what already runs (roadmap item 97, step 14). Not the viewer's link —
21
+ * another proxy may well serve it, and the page asks the pool before anything
22
+ * plays. Its `details` say why, with the figures.
23
+ */
24
+ export const OUTPUT_NO_CAPACITY = "OUTPUT_NO_CAPACITY";
@@ -24,11 +24,17 @@ import { SEGMENT_CUT_TIME_DELTA_SECONDS } from "./output/index.js";
24
24
  */
25
25
  export function cutOf(timeline, index) {
26
26
  return index < timeline.segmentCount - 1
27
- ? timeline.publishedStartOf(index + 1) - SEGMENT_CUT_TIME_DELTA_SECONDS
27
+ ? (Math.round(timeline.publishedStartOf(index + 1) * 1_000_000) -
28
+ Math.round(SEGMENT_CUT_TIME_DELTA_SECONDS * 1_000_000)) / 1_000_000
28
29
  : undefined;
29
30
  }
30
31
 
31
32
  /**
33
+ * Track positions read from empty edits retain their movie-tick uncertainty.
34
+ * Fresh closures also carry the muxer's packet end and its actual cut, so a
35
+ * restarted run is judged on its own reference clock. Neither allowance is a
36
+ * browser's frame-join rule.
37
+ *
32
38
  * @param {{ producedThroughSeconds?: (ranges: object) => number | null }} format
33
39
  * @param {object} ranges - The piece's media intervals, as the format read them.
34
40
  * @param {number | undefined} cutSeconds - From {@link cutOf}.
@@ -39,8 +45,60 @@ export function judgePiece(format, ranges, cutSeconds) {
39
45
  if (throughSeconds === null) {
40
46
  return { whole: false, throughSeconds };
41
47
  }
42
- if (Number.isFinite(cutSeconds) && throughSeconds < cutSeconds) {
48
+ const cutMicros = ranges?.production?.cutMicros ??
49
+ (Number.isFinite(cutSeconds) ? BigInt(Math.round(cutSeconds * 1_000_000)) : null);
50
+ if (cutMicros !== null && ranges?.tracks?.length) {
51
+ const tracks = ranges.tracks;
52
+ const production = ranges.production;
53
+ const reference = production ? tracks.find(({ kind }) => kind === production.kind) : null;
54
+ const referenceEnd = reference?.ranges.at(-1)?.end;
55
+ const whole = tracks.every((track) => {
56
+ const { timescale, ranges: held, positionErrorTicks = 0n } = track;
57
+ const end = held.at(-1)?.end;
58
+ if (end === undefined) return false;
59
+ if (referenceEnd !== undefined) {
60
+ if (track === reference) return 2n * production.endMicros + 1n >= 2n * cutMicros;
61
+ // The segment muxer's CSV reports packet time before movenc truncates
62
+ // the empty edit. Compare on that clock, including another track's
63
+ // relative end and the precision of both written track positions.
64
+ const difference = (end + positionErrorTicks) * reference.timescale -
65
+ (referenceEnd - (reference.positionErrorTicks ?? 0n)) * timescale;
66
+ const denominator = timescale * reference.timescale;
67
+ return 2n * difference * 1_000_000n +
68
+ (2n * production.endMicros + 1n) * denominator >= 2n * cutMicros * denominator;
69
+ }
70
+ return (end + positionErrorTicks) * 1_000_000n >= cutMicros * timescale;
71
+ });
72
+ return { whole, throughSeconds };
73
+ }
74
+ if (cutMicros !== null && throughSeconds < Number(cutMicros) / 1_000_000) {
43
75
  return { whole: false, throughSeconds };
44
76
  }
45
77
  return { whole: true, throughSeconds };
46
78
  }
79
+
80
+ /** The muxer cuts relative to its first reference packet, not the requested seek. */
81
+ export function productionOf(timing, cutTimes, relativeIndex, kind) {
82
+ if (!timing || timing.originMicros === null || !Number.isFinite(cutTimes?.[relativeIndex])) return null;
83
+ return {
84
+ kind,
85
+ endMicros: timing.endMicros,
86
+ cutMicros: timing.originMicros + BigInt(Math.round(cutTimes[relativeIndex] * 1_000_000)) -
87
+ BigInt(Math.round(SEGMENT_CUT_TIME_DELTA_SECONDS * 1_000_000))
88
+ };
89
+ }
90
+
91
+ /**
92
+ * The first CSV entry has start_time = 0 even after a seek (segment.c).
93
+ * Recover its reference packet's PTS from the reported end minus the exact
94
+ * sample span. The edit-list offset cancels, including its lost precision.
95
+ */
96
+ export function originOf(ranges, endMicros, kind) {
97
+ const track = ranges?.tracks?.find(track => track.kind === kind);
98
+ if (track?.firstSampleStart === undefined || !track.ranges.length) return null;
99
+ const ticks = endMicros * track.timescale -
100
+ (track.ranges.at(-1).end - track.firstSampleStart) * 1_000_000n;
101
+ // CSV rounds to microseconds; rescale with the muxer's nearest rounding.
102
+ return ticks >= 0n ? (2n * ticks + track.timescale) / (2n * track.timescale) :
103
+ -((-2n * ticks + track.timescale) / (2n * track.timescale));
104
+ }