@torrent-tv/proxy 2.89.10 → 2.89.12
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,11 @@
|
|
|
1
|
+
## 2.89.12
|
|
2
|
+
|
|
3
|
+
- **Fix**: A connection that is gone is no longer probed. The delivery probe stopped only when a data channel reported `onClosed`, and a peer connection closed moments after its channels opened reports nothing: on the stand on 2026-10-04 a trial connection the page closed 70 ms after opening was still sent probes twice a second hours later, one timer left behind per such connection. When the transport watcher finds the connection gone, its probes now end with it.
|
|
4
|
+
|
|
5
|
+
## 2.89.11
|
|
6
|
+
|
|
7
|
+
- **Fix**: A failed encoder run says what the source holds again. The line `this run asked for …, and the source holds N video, N audio, N subtitle` was meant to tell an output with no stream (the source's fault) from a track index past the end (ours), but every field failure printed `what the source holds was not recorded`: a session takes its media info from the playback planner's cache, and the planner left the stream counts out of it although it parses the same ffmpeg banner. They are cached with the rest now.
|
|
8
|
+
|
|
1
9
|
## 2.89.10
|
|
2
10
|
|
|
3
11
|
- **Fix**: An audio track warm-up that is still being made answers `503` with `warming: true`. That answer means the track and how it is produced are recorded for the viewer; a `503` without it is a preparation that failed and recorded nothing. Until now the two were the same status, so a page restating its soundtrack after a reconnect could not tell whether the proxy had kept it.
|
package/package.json
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
parseFfmpegBitDepth,
|
|
17
17
|
parseFfmpegBitrateKbps,
|
|
18
18
|
parseFfmpegVideoFps,
|
|
19
|
-
parseFfmpegHdr
|
|
19
|
+
parseFfmpegHdr,
|
|
20
|
+
parseFfmpegStreamCounts
|
|
20
21
|
} from "./ffmpeg-banner.js";
|
|
21
22
|
|
|
22
23
|
/** Audio codecs that browsers can decode natively without transcoding. */
|
|
@@ -912,7 +913,12 @@ export function createPlaybackPlanner({
|
|
|
912
913
|
fps: videoFacts.fps,
|
|
913
914
|
startTime: parseFfmpegStartTimeSeconds(probe.stderr),
|
|
914
915
|
isHdr: videoFacts.isHdr,
|
|
915
|
-
bitDepth: videoFacts.bitDepth
|
|
916
|
+
bitDepth: videoFacts.bitDepth,
|
|
917
|
+
// What the source holds, from this same banner. A failed run reads it
|
|
918
|
+
// to say whose fault an output with no stream is, and a session takes
|
|
919
|
+
// its media info from this cache without probing again — so a field
|
|
920
|
+
// left out here was "not recorded" on every failure (field 2026-09-29).
|
|
921
|
+
streamCounts: parseFfmpegStreamCounts(probe.stderr)
|
|
916
922
|
});
|
|
917
923
|
// Warm the file-body start for the transcode session that follows.
|
|
918
924
|
// Fire-and-forget: never delays the plan response.
|
|
@@ -761,6 +761,7 @@ export function createDataChannelHandler({
|
|
|
761
761
|
* @returns {void}
|
|
762
762
|
*/
|
|
763
763
|
function connectionGone(sessionId, because) {
|
|
764
|
+
deliveryProbe.forget(sessionId);
|
|
764
765
|
const viewers = viewersOnConnection.get(sessionId);
|
|
765
766
|
viewersOnConnection.delete(sessionId);
|
|
766
767
|
if (!viewers || viewers.size === 0) {
|
|
@@ -414,6 +414,7 @@ export function readProbeState(state) {
|
|
|
414
414
|
* @returns {{
|
|
415
415
|
* attach: (sessionId: string, tag: string, label: string, channel: import('node-datachannel').DataChannel) => void,
|
|
416
416
|
* detach: (sessionId: string, channel: import('node-datachannel').DataChannel) => void,
|
|
417
|
+
* forget: (sessionId: string) => void,
|
|
417
418
|
* noteEcho: (sessionId: string, echo: object) => void,
|
|
418
419
|
* dispose: () => void
|
|
419
420
|
* }}
|
|
@@ -699,6 +700,24 @@ export function createDeliveryProbe({
|
|
|
699
700
|
}
|
|
700
701
|
},
|
|
701
702
|
|
|
703
|
+
// The connection is gone, whether or not its channels said so. A peer
|
|
704
|
+
// connection closed moments after its channels opened gives no `onClosed`
|
|
705
|
+
// (field 2026-10-04: a trial connection closed 70 ms after opening was
|
|
706
|
+
// still probed hours later), so `detach` alone left a timer per such
|
|
707
|
+
// connection for the life of the process.
|
|
708
|
+
forget(sessionId) {
|
|
709
|
+
const connection = connections.get(sessionId);
|
|
710
|
+
if (!connection) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (connection.timer !== null) {
|
|
714
|
+
clearInterval(connection.timer);
|
|
715
|
+
connection.timer = null;
|
|
716
|
+
}
|
|
717
|
+
connection.channels.clear();
|
|
718
|
+
connections.delete(sessionId);
|
|
719
|
+
},
|
|
720
|
+
|
|
702
721
|
noteEcho(sessionId, echo) {
|
|
703
722
|
const connection = connections.get(sessionId);
|
|
704
723
|
if (!connection || !echo || typeof echo !== "object") {
|
|
@@ -160,3 +160,34 @@ test("a viewer whose newest channel closed is reached on the older connection",
|
|
|
160
160
|
assert.equal(cuesOn(old.control), 1);
|
|
161
161
|
assert.deepEqual(gone, [], "the trial connection still has its media channel");
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
test("a connection the transport no longer knows is not probed any more, though no channel said it closed", (t) => {
|
|
165
|
+
t.mock.timers.enable({ apis: ["setInterval", "setTimeout", "Date"] });
|
|
166
|
+
let alive = true;
|
|
167
|
+
const handler = createDataChannelHandler({
|
|
168
|
+
proxyPort: 9090,
|
|
169
|
+
getTransportSnapshot: () => (alive ? { bytesSent: 0, bytesReceived: 0 } : null),
|
|
170
|
+
onViewerGone: () => {}
|
|
171
|
+
});
|
|
172
|
+
const channels = ["proxy", "proxy-control", "proxy-fast"].map((label) => new FakeDataChannel(label));
|
|
173
|
+
for (const channel of channels) {
|
|
174
|
+
handler.handleChannel("trial-peer", channel);
|
|
175
|
+
}
|
|
176
|
+
const probes = () =>
|
|
177
|
+
channels.reduce((sum, channel) => sum + channel.messages.filter((message) => message.includes('"probe"')).length, 0);
|
|
178
|
+
t.mock.timers.tick(2_000);
|
|
179
|
+
assert.ok(probes() > 0, "an open connection is probed");
|
|
180
|
+
|
|
181
|
+
// Field 2026-10-04: the peer connection closed 70 ms after its channels
|
|
182
|
+
// opened, and none of them reported `onClosed`.
|
|
183
|
+
alive = false;
|
|
184
|
+
const advance = (ms) => {
|
|
185
|
+
// Second by second: the watcher compares the clock between its own ticks,
|
|
186
|
+
// and one long jump lands every tick on the same instant.
|
|
187
|
+
for (let elapsed = 0; elapsed < ms; elapsed += 1_000) t.mock.timers.tick(1_000);
|
|
188
|
+
};
|
|
189
|
+
advance(30_000);
|
|
190
|
+
const afterGone = probes();
|
|
191
|
+
advance(30_000);
|
|
192
|
+
assert.equal(probes(), afterGone, "nothing is sent on a connection that is gone");
|
|
193
|
+
});
|