@torrent-tv/proxy 2.105.13 → 2.106.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/package.json
CHANGED
|
@@ -24,7 +24,7 @@ function getPayload(body) {
|
|
|
24
24
|
return {};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export async function handleApiPlaybackPlanPost(req, reply, { playbackPlanner, sourceRegistry, torrentPool, ffmpegBin, localBaseUrl, viewers }) {
|
|
27
|
+
export async function handleApiPlaybackPlanPost(req, reply, { playbackPlanner, sourceRegistry, torrentPool, ffmpegBin, localBaseUrl, viewers, observeWait }) {
|
|
28
28
|
const payload = getPayload(req.body);
|
|
29
29
|
const sourceKey = typeof payload.sourceKey === "string" ? payload.sourceKey.trim() : "";
|
|
30
30
|
const fileIndex = Number(payload.fileIndex);
|
|
@@ -38,10 +38,13 @@ export async function handleApiPlaybackPlanPost(req, reply, { playbackPlanner, s
|
|
|
38
38
|
if (typeof payload.positionSeconds === "number" && Number.isFinite(payload.positionSeconds) && payload.positionSeconds >= 0) selection.positionSeconds = payload.positionSeconds;
|
|
39
39
|
if (typeof payload.wantsToPlay === "boolean") selection.wantsToPlay = payload.wantsToPlay;
|
|
40
40
|
if (consumerId) viewers?.selectsFile(consumerId, sourceKey, fileIndex, Date.now(), selection);
|
|
41
|
+
const startedAt = performance.now();
|
|
41
42
|
|
|
42
43
|
// Interface delegates to PlaybackController (orchestrator + domain). Keeps route thin.
|
|
43
44
|
const { PlaybackController } = await import("../../../services/server/controllers/PlaybackController.js");
|
|
44
45
|
const controller = new PlaybackController({ torrentPool, sourceRegistry, ffmpegBin, localBaseUrl, playbackPlanner });
|
|
46
|
+
const waitFields = { process: "P3", sourceKey, fileIndex };
|
|
47
|
+
observeWait?.(sourceKey, "process", { ...waitFields, phase: "begin", at: startedAt });
|
|
45
48
|
const cancellation = new AbortController();
|
|
46
49
|
const aborted = () => cancellation.abort();
|
|
47
50
|
const closed = () => { if (!reply.raw?.writableEnded) aborted(); };
|
|
@@ -52,8 +55,14 @@ export async function handleApiPlaybackPlanPost(req, reply, { playbackPlanner, s
|
|
|
52
55
|
const params = { sourceKey, fileIndex, userAgent };
|
|
53
56
|
const plan = payload.waitForReady === true
|
|
54
57
|
? await controller.getReadyPlan(params, { signal: cancellation.signal }) : await controller.getPlan(params);
|
|
58
|
+
const endedAt = performance.now();
|
|
59
|
+
observeWait?.(sourceKey, "process", { ...waitFields, phase: "end", at: endedAt, startedAt,
|
|
60
|
+
endedAt, durationMs: endedAt - startedAt, outcome: "plan-ready" });
|
|
55
61
|
return reply.send(plan);
|
|
56
62
|
} catch (error) {
|
|
63
|
+
const endedAt = performance.now();
|
|
64
|
+
observeWait?.(sourceKey, "process", { ...waitFields, phase: "end", at: endedAt, startedAt,
|
|
65
|
+
endedAt, durationMs: endedAt - startedAt, outcome: cancellation.signal.aborted ? "cancelled" : "error" });
|
|
57
66
|
if (cancellation.signal.aborted) return;
|
|
58
67
|
if (error instanceof Error && error.code === "SOURCE_NOT_FOUND") {
|
|
59
68
|
return reply.code(404).send({ error: error.message });
|
package/server.js
CHANGED
|
@@ -1614,7 +1614,8 @@ export async function startProxyServer({
|
|
|
1614
1614
|
handleApiClientLogsPost(req, reply, { clientLogs, viewerRequests: outputParts.viewerRequests })
|
|
1615
1615
|
);
|
|
1616
1616
|
app.post("/api/playback-plan", async (req, reply) =>
|
|
1617
|
-
handleApiPlaybackPlanPost(req, reply, { playbackPlanner, viewers: outputParts.viewers
|
|
1617
|
+
handleApiPlaybackPlanPost(req, reply, { playbackPlanner, viewers: outputParts.viewers,
|
|
1618
|
+
observeWait: (sourceKey, event, fields) => waitTrace.source(sourceKey, event, fields) })
|
|
1618
1619
|
);
|
|
1619
1620
|
app.post("/api/playback-plan/audio-tracks", async (req, reply) =>
|
|
1620
1621
|
handleApiPlaybackPlanAudioTracksPost(req, reply, { playbackPlanner })
|
|
@@ -1234,7 +1234,21 @@ export class EncodeRuns {
|
|
|
1234
1234
|
if (mediaRanges === false && tentative) return null;
|
|
1235
1235
|
if (mediaRanges === false) throw Object.assign(new Error(`Closed piece #${index} has incomplete media.`),
|
|
1236
1236
|
{ code: "ERR_INCOMPLETE_MEDIA" });
|
|
1237
|
-
const
|
|
1237
|
+
const publishStartedAt = performance.now();
|
|
1238
|
+
const publishProcess = session.spec?.carries === "audio-only" ? "P11.a" : "P11.v";
|
|
1239
|
+
this.#host.observeWait?.(session.file.sourceKey, "process", { process: publishProcess, phase: "begin",
|
|
1240
|
+
output: address, segment: index, at: publishStartedAt });
|
|
1241
|
+
let published;
|
|
1242
|
+
let publishOutcome = "error";
|
|
1243
|
+
try {
|
|
1244
|
+
published = this.#host.segmentFiles.publish(address, name, session.segmentFormat, { mediaRanges, bytes });
|
|
1245
|
+
publishOutcome = published ? "published" : "not-published";
|
|
1246
|
+
} finally {
|
|
1247
|
+
const publishEndedAt = performance.now();
|
|
1248
|
+
this.#host.observeWait?.(session.file.sourceKey, "process", { process: publishProcess, phase: "end",
|
|
1249
|
+
output: address, segment: index, at: publishEndedAt, startedAt: publishStartedAt, endedAt: publishEndedAt,
|
|
1250
|
+
durationMs: publishEndedAt - publishStartedAt, outcome: publishOutcome });
|
|
1251
|
+
}
|
|
1238
1252
|
if (!published) throw new Error(`Closed piece #${index} could not be stored.`);
|
|
1239
1253
|
if (index === safeIndex) this.#host.observeWait?.(session.file.sourceKey, "first-piece-published", {
|
|
1240
1254
|
output: address, segment: index, startedAt: observedStart, endedAt: performance.now(),
|
|
@@ -865,6 +865,11 @@ export class ViewerRequests {
|
|
|
865
865
|
}
|
|
866
866
|
|
|
867
867
|
async #playbackReadinessFor(session, consumerId, progress, outputMbps) {
|
|
868
|
+
const waitStartedAt = performance.now();
|
|
869
|
+
const waitFields = { process: "P14", sessionId: session.id, consumerId };
|
|
870
|
+
this.#host.observeWait?.(session.file.sourceKey, "process", { ...waitFields, phase: "begin", at: waitStartedAt });
|
|
871
|
+
let waitOutcome = "error";
|
|
872
|
+
try {
|
|
868
873
|
const now = Date.now();
|
|
869
874
|
// WHERE AN ANSWER'S TIME GOES, said with each answer. On Home Assistant a
|
|
870
875
|
// held viewer's answer took 11.2 s while its forecast took 0.1-0.4 s and
|
|
@@ -1116,7 +1121,14 @@ export class ViewerRequests {
|
|
|
1116
1121
|
this.#host.noteViewerWaiting?.(consumerId || session.id,
|
|
1117
1122
|
holding?.kind === "production" ? { address: holding.track, index: holding.segment } : null);
|
|
1118
1123
|
delete forecast.firstRequiredStages;
|
|
1124
|
+
waitOutcome = "forecast-ready";
|
|
1119
1125
|
return forecast;
|
|
1126
|
+
} finally {
|
|
1127
|
+
const waitEndedAt = performance.now();
|
|
1128
|
+
this.#host.observeWait?.(session.file.sourceKey, "process", { ...waitFields, phase: "end", at: waitEndedAt,
|
|
1129
|
+
startedAt: waitStartedAt, endedAt: waitEndedAt, durationMs: waitEndedAt - waitStartedAt,
|
|
1130
|
+
outcome: waitOutcome });
|
|
1131
|
+
}
|
|
1120
1132
|
}
|
|
1121
1133
|
|
|
1122
1134
|
/**
|
package/utils/wait-trace.js
CHANGED
|
@@ -53,8 +53,9 @@ export class SourceWaitTrace {
|
|
|
53
53
|
!Number.isSafeInteger(attempt) || attempt < 0) return;
|
|
54
54
|
const context = { wait, attempt, operation, startedAt: this.now() };
|
|
55
55
|
this.requests.set(req, context);
|
|
56
|
-
const
|
|
57
|
-
|
|
56
|
+
const endpointProcess = processForRequest(req.url);
|
|
57
|
+
const process = endpointProcess ?? (typeof statedProcess === "string" &&
|
|
58
|
+
/^[A-Z][0-9]+(?:\.(?:[0-9]+[a-z]?|[va]))?$/.test(statedProcess) ? statedProcess : null);
|
|
58
59
|
this.write(context, "http-start", { process, phase: "begin", method: req.method, path: req.url,
|
|
59
60
|
requestId: req.headers["x-ttv-forwarded-request-id"] ?? req.id });
|
|
60
61
|
let finished = false;
|
|
@@ -92,11 +93,11 @@ export class SourceWaitTrace {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
function processForRequest(path) {
|
|
96
|
+
if (/^\/api\/transcode-sessions\/[^/?]+\/progress(?:\?|$)/.test(path)) return "P14";
|
|
95
97
|
if (path.startsWith("/api/transcode-sessions")) return "P6.v";
|
|
96
98
|
if (path.startsWith("/api/playback-plan")) return "P3";
|
|
97
99
|
if (/\/transcode\/.*\.m3u8(?:\?|$)/.test(path)) return "P12";
|
|
98
100
|
if (/\/transcode\/.*(?:init|\.mp4)(?:\?|$)/.test(path)) return "P12";
|
|
99
101
|
if (/\/transcode\/.*(?:\.m4s|\.ts)(?:\?|$)/.test(path)) return "P12";
|
|
100
|
-
if (path.startsWith("/api/session-progress")) return "P14";
|
|
101
102
|
return null;
|
|
102
103
|
}
|