@torrent-tv/proxy 2.88.5 → 2.88.6
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,3 +1,7 @@
|
|
|
1
|
+
## 2.88.6
|
|
2
|
+
|
|
3
|
+
- **Fix**: Validate closed segment coverage against the same cut-time delta passed to FFmpeg. Complete audio segments that close before the requested grid boundary remain usable; truncated segments still fail validation.
|
|
4
|
+
|
|
1
5
|
## 2.88.5
|
|
2
6
|
|
|
3
7
|
- **Fix**: Reject closed non-final fragments that do not reach their next declared cut, using measured coded-frame coverage. Remove such previously cached fragments from the inventory so missing media can be produced again instead of indefinitely blocking startup.
|
|
@@ -2263,4 +2267,4 @@ All eight were found by reading the file after the torrent worker was killed by
|
|
|
2263
2267
|
|
|
2264
2268
|
- **New**: WebRTC P2P tunnel architecture — replaced direct HTTP streaming with a persistent WebSocket tunnel to the server. Video is delivered from the proxy to the browser over a WebRTC data channel; the server acts only as a signalling relay.
|
|
2265
2269
|
- **New**: `node-datachannel` dependency for server-side WebRTC.
|
|
2266
|
-
- **Removed**: `public_base_url` config — no longer needed.
|
|
2270
|
+
- **Removed**: `public_base_url` config — no longer needed.
|
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import { ENCODE_EXIT } from "./encode-exit.js";
|
|
|
15
15
|
import { EncodeRun } from "./EncodeRun.js";
|
|
16
16
|
import { computeOutputDimensions } from "./args.js";
|
|
17
17
|
import { buildRunCommand, trueStartOf } from "./run-command.js";
|
|
18
|
+
import { SEGMENT_CUT_TIME_DELTA_SECONDS } from "./output/index.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* @typedef {Object} SegmentFiles
|
|
@@ -837,7 +838,8 @@ export class EncodeRuns {
|
|
|
837
838
|
session.segmentFormat.servedNameOf?.(name) ?? name);
|
|
838
839
|
if (Number.isInteger(index) && index < safeIndex) return null;
|
|
839
840
|
return this.#host.segmentFiles.publish(session.outputKey ?? "", name, session.segmentFormat, {
|
|
840
|
-
endSeconds: index < session.timeline.segmentCount - 1 ?
|
|
841
|
+
endSeconds: index < session.timeline.segmentCount - 1 ?
|
|
842
|
+
session.timeline.publishedStartOf(index + 1) - SEGMENT_CUT_TIME_DELTA_SECONDS : undefined
|
|
841
843
|
});
|
|
842
844
|
},
|
|
843
845
|
onEnded: (ended) => this.noteRunEnded(session, run, ended)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
|
|
2
2
|
export { PLAYLIST_FILE_NAME } from "./playlists.js";
|
|
3
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
|
+
|
|
4
8
|
/**
|
|
5
9
|
* The code an error carries when no output suits the viewer: nothing this proxy
|
|
6
10
|
* could produce or already holds is admitted by their link. Its `details` say
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { PLAYLIST_FILE_NAME } from "./output/playlists.js";
|
|
19
19
|
import { AUDIO_TRANSCODE_KBPS } from "./args.js";
|
|
20
|
+
import { SEGMENT_CUT_TIME_DELTA_SECONDS } from "./output/index.js";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* What ffmpeg's own CLI subtracts from an input seek, and therefore what has to
|
|
@@ -694,7 +695,7 @@ export function buildRunCommand({
|
|
|
694
695
|
// recorded a hair late would skip to the next one and double the
|
|
695
696
|
// segment. The tolerance absorbs that rounding.
|
|
696
697
|
"-segment_time_delta",
|
|
697
|
-
|
|
698
|
+
String(SEGMENT_CUT_TIME_DELTA_SECONDS),
|
|
698
699
|
"-segment_start_number",
|
|
699
700
|
String(inputIndex),
|
|
700
701
|
// THE ENCODER SAYS WHEN A PIECE IS FINISHED, on a channel of its own.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { logger } from "../../utils/logger.js";
|
|
11
|
-
import { isOutputName } from "../encode/output/index.js";
|
|
11
|
+
import { isOutputName, SEGMENT_CUT_TIME_DELTA_SECONDS } from "../encode/output/index.js";
|
|
12
12
|
import { predictPlaybackReadiness, RateTrend, forecastRate as forecastRateOf } from "./playback-readiness.js";
|
|
13
13
|
|
|
14
14
|
function isWarmupTimeoutError(error) {
|
|
@@ -569,7 +569,8 @@ export class ViewerRequests {
|
|
|
569
569
|
const nativeRanges = Array.from({ length: timeline?.segmentCount ?? 0 }, (_, index) =>
|
|
570
570
|
this.#host.segmentStore.mediaRangesOf(output.outputKey, index, {
|
|
571
571
|
startSeconds: timeline.publishedStartOf(index),
|
|
572
|
-
endSeconds: index < timeline.segmentCount - 1 ?
|
|
572
|
+
endSeconds: index < timeline.segmentCount - 1 ?
|
|
573
|
+
timeline.publishedStartOf(index + 1) - SEGMENT_CUT_TIME_DELTA_SECONDS : undefined
|
|
573
574
|
}));
|
|
574
575
|
const segments = Array.from({ length: timeline?.segmentCount ?? 0 }, (_, index) => {
|
|
575
576
|
const ranges = nativeRanges[index];
|
|
@@ -10,6 +10,7 @@ import os from "node:os";
|
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { directoryNameFor, SegmentStore } from "../services/storage/segment-store/SegmentStore.js";
|
|
12
12
|
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
13
|
+
import { SEGMENT_CUT_TIME_DELTA_SECONDS } from "../services/encode/output/index.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @returns {{ store: SegmentStore, root: string, lines: string[] }}
|
|
@@ -35,6 +36,19 @@ function writeSegment(dir, index, bytes = 16) {
|
|
|
35
36
|
|
|
36
37
|
const KEY = "torrent:abc:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
|
|
37
38
|
|
|
39
|
+
test("a complete audio cut within the configured muxer delta remains usable", (t) => {
|
|
40
|
+
const { store, root } = storeInATempRoot();
|
|
41
|
+
t.after(() => { store.dropAll("the check is over"); rmSync(root, { recursive: true, force: true }); });
|
|
42
|
+
const format = { ...fmp4Format, readMediaRanges: () => [{ start: 0, end: 4.738913832199547 }],
|
|
43
|
+
mediaCoverageEnd: (ranges) => ranges.at(-1).end };
|
|
44
|
+
const dir = store.directoryFor(KEY);
|
|
45
|
+
store.useFormat(KEY, format);
|
|
46
|
+
const endSeconds = 4.755 - SEGMENT_CUT_TIME_DELTA_SECONDS;
|
|
47
|
+
writeFileSync(path.join(dir, "making-0-00000.mp4"), Buffer.alloc(16));
|
|
48
|
+
assert.equal(store.publish(KEY, "making-0-00000.mp4", format, { endSeconds }), "segment-00000.mp4");
|
|
49
|
+
assert.ok(store.mediaRangesOf(KEY, 0, { endSeconds }));
|
|
50
|
+
});
|
|
51
|
+
|
|
38
52
|
test("a closed but truncated non-final piece is not published or reused", (t) => {
|
|
39
53
|
const { store, root } = storeInATempRoot();
|
|
40
54
|
t.after(() => { store.dropAll("the check is over"); rmSync(root, { recursive: true, force: true }); });
|