@torrent-tv/proxy 2.87.7 → 2.87.9
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.87.9
|
|
2
|
+
|
|
3
|
+
- **Fix**: Stop refreshing late sidecar metadata when the completed header read reports no audio tracks.
|
|
4
|
+
|
|
5
|
+
## 2.87.8
|
|
6
|
+
|
|
7
|
+
- **Fix**: Keep a viewer registered until every data channel on its WebRTC connection closes, and ignore a closing connection after the viewer has identified themselves on a replacement connection.
|
|
8
|
+
|
|
1
9
|
## 2.87.7
|
|
2
10
|
|
|
3
11
|
- **Fix**: Refresh audio metadata for sidecar tracks after a delayed container-header read, keeping the existing track numbers and rendition count stable.
|
package/package.json
CHANGED
|
@@ -445,7 +445,7 @@ export function createPlaybackPlanner({
|
|
|
445
445
|
tracks: tracks
|
|
446
446
|
.filter((track) => track?.type === "audio")
|
|
447
447
|
.sort((left, right) => (left.declaredIndex ?? 0) - (right.declaredIndex ?? 0)),
|
|
448
|
-
complete: Array.isArray(tracks)
|
|
448
|
+
complete: Array.isArray(tracks)
|
|
449
449
|
}))
|
|
450
450
|
.catch((error) => {
|
|
451
451
|
logger.info(`audio tracks: "${label}" could not be read (${error?.message ?? error})`);
|
|
@@ -312,7 +312,7 @@ function makeSendQueueWatcher({ log, getTransportSnapshot, witness, usrsctpState
|
|
|
312
312
|
*
|
|
313
313
|
* @returns {void}
|
|
314
314
|
*/
|
|
315
|
-
const stop = () => {
|
|
315
|
+
const stop = (because = "the transport stopped answering") => {
|
|
316
316
|
if (stopped) {
|
|
317
317
|
return;
|
|
318
318
|
}
|
|
@@ -332,7 +332,7 @@ function makeSendQueueWatcher({ log, getTransportSnapshot, witness, usrsctpState
|
|
|
332
332
|
// because the first does not always come: a peer connection can die
|
|
333
333
|
// without `onClosed`, which is why this watch was written in the first
|
|
334
334
|
// place.
|
|
335
|
-
onConnectionGone?.(sessionId,
|
|
335
|
+
onConnectionGone?.(sessionId, because);
|
|
336
336
|
}
|
|
337
337
|
};
|
|
338
338
|
// Independent of the queue: the transport's own counters, sampled for as
|
|
@@ -697,6 +697,16 @@ export function createDataChannelHandler({
|
|
|
697
697
|
* @type {Map<string, Set<string>>}
|
|
698
698
|
*/
|
|
699
699
|
const viewersOnConnection = new Map();
|
|
700
|
+
/**
|
|
701
|
+
* The newest connection currently carrying each viewer's identity.
|
|
702
|
+
*
|
|
703
|
+
* A reconnect can open its channels before the old connection finishes
|
|
704
|
+
* closing. The old connection must not release a viewer who has already
|
|
705
|
+
* identified themselves on the replacement.
|
|
706
|
+
*
|
|
707
|
+
* @type {Map<string, string>}
|
|
708
|
+
*/
|
|
709
|
+
const connectionOfViewer = new Map();
|
|
700
710
|
|
|
701
711
|
/**
|
|
702
712
|
* Everyone on this connection has gone, because the connection has.
|
|
@@ -716,6 +726,10 @@ export function createDataChannelHandler({
|
|
|
716
726
|
return;
|
|
717
727
|
}
|
|
718
728
|
for (const consumerId of viewers) {
|
|
729
|
+
if (connectionOfViewer.get(consumerId) !== sessionId) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
connectionOfViewer.delete(consumerId);
|
|
719
733
|
onLog?.(`[dc] Session ${sessionId.slice(0, 8)}: viewer ${consumerId} left — ${because}`);
|
|
720
734
|
try {
|
|
721
735
|
onViewerGone(consumerId, because);
|
|
@@ -997,6 +1011,7 @@ export function createDataChannelHandler({
|
|
|
997
1011
|
const isNew = !known.has(message.consumerId);
|
|
998
1012
|
known.add(message.consumerId);
|
|
999
1013
|
viewersOnConnection.set(sessionId, known);
|
|
1014
|
+
connectionOfViewer.set(message.consumerId, sessionId);
|
|
1000
1015
|
channelOfViewer.set(message.consumerId, channel);
|
|
1001
1016
|
if (isNew) {
|
|
1002
1017
|
log(`[dc] Session ${tag}: viewer ${message.consumerId} is on this connection`);
|
|
@@ -1030,7 +1045,7 @@ export function createDataChannelHandler({
|
|
|
1030
1045
|
});
|
|
1031
1046
|
|
|
1032
1047
|
channel.onClosed(() => {
|
|
1033
|
-
stopWatchdog();
|
|
1048
|
+
stopWatchdog("the connection closed");
|
|
1034
1049
|
deliveryProbe.detach(sessionId, channel);
|
|
1035
1050
|
for (const entry of partials.values()) {
|
|
1036
1051
|
clearTimeout(entry.timer);
|
|
@@ -1038,11 +1053,9 @@ export function createDataChannelHandler({
|
|
|
1038
1053
|
partials.clear();
|
|
1039
1054
|
forgetChannel(channel);
|
|
1040
1055
|
log(`[dc] Session ${tag}: channel closed`);
|
|
1041
|
-
//
|
|
1042
|
-
//
|
|
1043
|
-
// transport
|
|
1044
|
-
// failing to arrive.
|
|
1045
|
-
connectionGone(sessionId, "the connection closed");
|
|
1056
|
+
// One closed data channel does not mean the peer connection is gone:
|
|
1057
|
+
// media, control and probe channels can close independently. The shared
|
|
1058
|
+
// transport watcher calls `connectionGone` when its last channel closes.
|
|
1046
1059
|
});
|
|
1047
1060
|
|
|
1048
1061
|
channel.onError((err) => {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createDataChannelHandler } from "../services/transport/data-channel-handler.js";
|
|
4
|
+
|
|
5
|
+
class FakeDataChannel {
|
|
6
|
+
constructor(label) {
|
|
7
|
+
this.label = label;
|
|
8
|
+
this.messages = [];
|
|
9
|
+
this.closed = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getLabel() {
|
|
13
|
+
return this.label;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
onMessage(callback) {
|
|
17
|
+
this.message = callback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onClosed(callback) {
|
|
21
|
+
this.closed = callback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onError() {}
|
|
25
|
+
|
|
26
|
+
sendMessage(message) {
|
|
27
|
+
this.messages.push(message);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
close() {
|
|
31
|
+
this.closed?.();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("a viewer remains present until every data channel on the connection closes", () => {
|
|
36
|
+
const gone = [];
|
|
37
|
+
const handler = createDataChannelHandler({
|
|
38
|
+
proxyPort: 9090,
|
|
39
|
+
getTransportSnapshot: () => ({ bytesSent: 0, bytesReceived: 0 }),
|
|
40
|
+
onViewerGone: (consumerId, reason) => gone.push({ consumerId, reason })
|
|
41
|
+
});
|
|
42
|
+
const media = new FakeDataChannel("proxy");
|
|
43
|
+
const control = new FakeDataChannel("proxy-control");
|
|
44
|
+
const probe = new FakeDataChannel("proxy-fast");
|
|
45
|
+
|
|
46
|
+
handler.handleChannel("peer-session", media);
|
|
47
|
+
handler.handleChannel("peer-session", control);
|
|
48
|
+
handler.handleChannel("peer-session", probe);
|
|
49
|
+
control.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
|
|
50
|
+
|
|
51
|
+
control.close();
|
|
52
|
+
assert.deepEqual(gone, [], "closing the control channel alone does not release the viewer");
|
|
53
|
+
|
|
54
|
+
media.close();
|
|
55
|
+
assert.deepEqual(gone, [], "the viewer remains while the probe channel is still open");
|
|
56
|
+
|
|
57
|
+
probe.close();
|
|
58
|
+
assert.deepEqual(gone, [
|
|
59
|
+
{ consumerId: "viewer-1", reason: "the connection closed" }
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a closing connection does not release a viewer already named on its replacement", () => {
|
|
64
|
+
const gone = [];
|
|
65
|
+
const handler = createDataChannelHandler({
|
|
66
|
+
proxyPort: 9090,
|
|
67
|
+
getTransportSnapshot: () => ({ bytesSent: 0, bytesReceived: 0 }),
|
|
68
|
+
onViewerGone: (consumerId, reason) => gone.push({ consumerId, reason })
|
|
69
|
+
});
|
|
70
|
+
const oldControl = new FakeDataChannel("proxy-control");
|
|
71
|
+
const oldMedia = new FakeDataChannel("proxy");
|
|
72
|
+
const newControl = new FakeDataChannel("proxy-control");
|
|
73
|
+
const newMedia = new FakeDataChannel("proxy");
|
|
74
|
+
|
|
75
|
+
handler.handleChannel("old-peer", oldControl);
|
|
76
|
+
handler.handleChannel("old-peer", oldMedia);
|
|
77
|
+
oldControl.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
|
|
78
|
+
handler.handleChannel("new-peer", newControl);
|
|
79
|
+
handler.handleChannel("new-peer", newMedia);
|
|
80
|
+
newControl.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
|
|
81
|
+
|
|
82
|
+
oldControl.close();
|
|
83
|
+
oldMedia.close();
|
|
84
|
+
assert.deepEqual(gone, [], "the old connection no longer owns the viewer");
|
|
85
|
+
|
|
86
|
+
newControl.close();
|
|
87
|
+
assert.deepEqual(gone, [], "the replacement still has its media channel");
|
|
88
|
+
newMedia.close();
|
|
89
|
+
assert.deepEqual(gone, [
|
|
90
|
+
{ consumerId: "viewer-1", reason: "the connection closed" }
|
|
91
|
+
]);
|
|
92
|
+
});
|