@furious.luke/argus-js 0.5.0 → 0.5.2
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/README.md +1 -0
- package/dist/index.cjs +63 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +63 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -14,11 +14,23 @@ type TrackType = "camera" | "screen" | "audio";
|
|
|
14
14
|
*/
|
|
15
15
|
type VideoTrackType = "camera" | "screen";
|
|
16
16
|
/**
|
|
17
|
-
* Declares the logical type of a single published track
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Declares the logical type and identity of a single published track.
|
|
18
|
+
*
|
|
19
|
+
* `mid` is the negotiated media-section id — the ROUTING key the server matches a
|
|
20
|
+
* track to its type by. It is used rather than the browser `MediaStreamTrack.id`
|
|
21
|
+
* because it is the only track identifier both peers agree on: Firefox mints an
|
|
22
|
+
* SDP msid track id unrelated to the `MediaStreamTrack.id`, so routing keyed on
|
|
23
|
+
* the latter cannot be matched server-side.
|
|
24
|
+
*
|
|
25
|
+
* `id` is the browser `MediaStreamTrack.id` — the GENERATION identity. The mid is
|
|
26
|
+
* stable across renegotiation and is reused when a track of the same type is
|
|
27
|
+
* republished on the same m-line, so it cannot distinguish a delayed end of a
|
|
28
|
+
* removed track from a failure of its replacement; the id changes with each new
|
|
29
|
+
* capture and can. The server echoes it in `media_track_ended`, and the publisher
|
|
30
|
+
* correlates that against the id it intentionally removed.
|
|
20
31
|
*/
|
|
21
32
|
interface TrackLabel {
|
|
33
|
+
mid: string;
|
|
22
34
|
id: string;
|
|
23
35
|
type: TrackType;
|
|
24
36
|
}
|
|
@@ -177,6 +189,16 @@ interface PublisherOptions {
|
|
|
177
189
|
gatewayHandshakeTimeoutMs?: number;
|
|
178
190
|
/** Deadline after the initial offer for WebRTC to reach connected. Defaults to 30 seconds. */
|
|
179
191
|
peerConnectionTimeoutMs?: number;
|
|
192
|
+
/**
|
|
193
|
+
* Preferred video codecs, most-preferred first, as RTP MIME types (e.g.
|
|
194
|
+
* `"video/VP9"`, `"video/VP8"`, `"video/H264"`). Each published video track's
|
|
195
|
+
* transceiver offers these codecs ahead of the rest, so the browser sends the
|
|
196
|
+
* first one the media server also accepts. Codecs the browser supports but that
|
|
197
|
+
* are not listed keep their default order behind the preferred ones, so
|
|
198
|
+
* negotiation still falls back cleanly. Defaults to `["video/VP9"]`. Pass an
|
|
199
|
+
* empty array to leave the browser's native codec order untouched.
|
|
200
|
+
*/
|
|
201
|
+
preferredVideoCodecs?: string[];
|
|
180
202
|
/** How long to retry a dropped signaling connection to the selected gateway. Defaults to 20 seconds. */
|
|
181
203
|
signalingReconnectTimeoutMs?: number;
|
|
182
204
|
/** Callbacks for lifecycle events. */
|
|
@@ -435,6 +457,20 @@ declare class Publisher {
|
|
|
435
457
|
* the server's outbound speech sender on the dedicated speech transceiver.
|
|
436
458
|
*/
|
|
437
459
|
private addMicrophoneTrack;
|
|
460
|
+
/**
|
|
461
|
+
* Reorders the codecs a video sender offers so the browser prefers the
|
|
462
|
+
* configured codecs (VP9 by default). This is what actually controls the wire
|
|
463
|
+
* format: the browser is the offerer and sends its own top-of-offer codec, and
|
|
464
|
+
* the media server (Pion) answers by mirroring the offer's codec order — so the
|
|
465
|
+
* server's own registration order has no say. Floating VP9 to the front of the
|
|
466
|
+
* offer is therefore the lever. Codecs not in the preference keep their native
|
|
467
|
+
* order behind it, so anything VP9 can't satisfy still negotiates.
|
|
468
|
+
*
|
|
469
|
+
* Best-effort: browsers lacking `getCapabilities`/`setCodecPreferences` (older
|
|
470
|
+
* Safari) keep their default order, and any failure is swallowed — codec
|
|
471
|
+
* preference is an optimization, never a requirement for publishing.
|
|
472
|
+
*/
|
|
473
|
+
private preferVideoCodecs;
|
|
438
474
|
/** Registers one physical video track under its logical type and source stream. */
|
|
439
475
|
private registerTrack;
|
|
440
476
|
/** The live video tracks currently published under the given type. */
|
|
@@ -442,6 +478,13 @@ declare class Publisher {
|
|
|
442
478
|
/** Builds the id → type label array declared to the server on every offer. */
|
|
443
479
|
private buildTrackLabels;
|
|
444
480
|
private labelsReplacingType;
|
|
481
|
+
/**
|
|
482
|
+
* The negotiated mid of the transceiver currently sending `track`, or null if
|
|
483
|
+
* none is found or it has not been negotiated yet. The mid is the identifier
|
|
484
|
+
* both peers agree on; it is assigned once setLocalDescription runs, which the
|
|
485
|
+
* publisher always does before sending an offer's labels.
|
|
486
|
+
*/
|
|
487
|
+
private midForTrack;
|
|
445
488
|
private stagePublish;
|
|
446
489
|
private stageUnpublish;
|
|
447
490
|
/** Stops every published local track and clears the published map. */
|
package/dist/index.d.ts
CHANGED
|
@@ -14,11 +14,23 @@ type TrackType = "camera" | "screen" | "audio";
|
|
|
14
14
|
*/
|
|
15
15
|
type VideoTrackType = "camera" | "screen";
|
|
16
16
|
/**
|
|
17
|
-
* Declares the logical type of a single published track
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Declares the logical type and identity of a single published track.
|
|
18
|
+
*
|
|
19
|
+
* `mid` is the negotiated media-section id — the ROUTING key the server matches a
|
|
20
|
+
* track to its type by. It is used rather than the browser `MediaStreamTrack.id`
|
|
21
|
+
* because it is the only track identifier both peers agree on: Firefox mints an
|
|
22
|
+
* SDP msid track id unrelated to the `MediaStreamTrack.id`, so routing keyed on
|
|
23
|
+
* the latter cannot be matched server-side.
|
|
24
|
+
*
|
|
25
|
+
* `id` is the browser `MediaStreamTrack.id` — the GENERATION identity. The mid is
|
|
26
|
+
* stable across renegotiation and is reused when a track of the same type is
|
|
27
|
+
* republished on the same m-line, so it cannot distinguish a delayed end of a
|
|
28
|
+
* removed track from a failure of its replacement; the id changes with each new
|
|
29
|
+
* capture and can. The server echoes it in `media_track_ended`, and the publisher
|
|
30
|
+
* correlates that against the id it intentionally removed.
|
|
20
31
|
*/
|
|
21
32
|
interface TrackLabel {
|
|
33
|
+
mid: string;
|
|
22
34
|
id: string;
|
|
23
35
|
type: TrackType;
|
|
24
36
|
}
|
|
@@ -177,6 +189,16 @@ interface PublisherOptions {
|
|
|
177
189
|
gatewayHandshakeTimeoutMs?: number;
|
|
178
190
|
/** Deadline after the initial offer for WebRTC to reach connected. Defaults to 30 seconds. */
|
|
179
191
|
peerConnectionTimeoutMs?: number;
|
|
192
|
+
/**
|
|
193
|
+
* Preferred video codecs, most-preferred first, as RTP MIME types (e.g.
|
|
194
|
+
* `"video/VP9"`, `"video/VP8"`, `"video/H264"`). Each published video track's
|
|
195
|
+
* transceiver offers these codecs ahead of the rest, so the browser sends the
|
|
196
|
+
* first one the media server also accepts. Codecs the browser supports but that
|
|
197
|
+
* are not listed keep their default order behind the preferred ones, so
|
|
198
|
+
* negotiation still falls back cleanly. Defaults to `["video/VP9"]`. Pass an
|
|
199
|
+
* empty array to leave the browser's native codec order untouched.
|
|
200
|
+
*/
|
|
201
|
+
preferredVideoCodecs?: string[];
|
|
180
202
|
/** How long to retry a dropped signaling connection to the selected gateway. Defaults to 20 seconds. */
|
|
181
203
|
signalingReconnectTimeoutMs?: number;
|
|
182
204
|
/** Callbacks for lifecycle events. */
|
|
@@ -435,6 +457,20 @@ declare class Publisher {
|
|
|
435
457
|
* the server's outbound speech sender on the dedicated speech transceiver.
|
|
436
458
|
*/
|
|
437
459
|
private addMicrophoneTrack;
|
|
460
|
+
/**
|
|
461
|
+
* Reorders the codecs a video sender offers so the browser prefers the
|
|
462
|
+
* configured codecs (VP9 by default). This is what actually controls the wire
|
|
463
|
+
* format: the browser is the offerer and sends its own top-of-offer codec, and
|
|
464
|
+
* the media server (Pion) answers by mirroring the offer's codec order — so the
|
|
465
|
+
* server's own registration order has no say. Floating VP9 to the front of the
|
|
466
|
+
* offer is therefore the lever. Codecs not in the preference keep their native
|
|
467
|
+
* order behind it, so anything VP9 can't satisfy still negotiates.
|
|
468
|
+
*
|
|
469
|
+
* Best-effort: browsers lacking `getCapabilities`/`setCodecPreferences` (older
|
|
470
|
+
* Safari) keep their default order, and any failure is swallowed — codec
|
|
471
|
+
* preference is an optimization, never a requirement for publishing.
|
|
472
|
+
*/
|
|
473
|
+
private preferVideoCodecs;
|
|
438
474
|
/** Registers one physical video track under its logical type and source stream. */
|
|
439
475
|
private registerTrack;
|
|
440
476
|
/** The live video tracks currently published under the given type. */
|
|
@@ -442,6 +478,13 @@ declare class Publisher {
|
|
|
442
478
|
/** Builds the id → type label array declared to the server on every offer. */
|
|
443
479
|
private buildTrackLabels;
|
|
444
480
|
private labelsReplacingType;
|
|
481
|
+
/**
|
|
482
|
+
* The negotiated mid of the transceiver currently sending `track`, or null if
|
|
483
|
+
* none is found or it has not been negotiated yet. The mid is the identifier
|
|
484
|
+
* both peers agree on; it is assigned once setLocalDescription runs, which the
|
|
485
|
+
* publisher always does before sending an offer's labels.
|
|
486
|
+
*/
|
|
487
|
+
private midForTrack;
|
|
445
488
|
private stagePublish;
|
|
446
489
|
private stageUnpublish;
|
|
447
490
|
/** Stops every published local track and clears the published map. */
|
package/dist/index.js
CHANGED
|
@@ -151,10 +151,11 @@ var Publisher = class {
|
|
|
151
151
|
// may later reuse any compatible inactive transceiver, not necessarily the one
|
|
152
152
|
// that previously carried the same logical type.
|
|
153
153
|
typeSenders = /* @__PURE__ */ new Map();
|
|
154
|
-
// intentionalTrackEnds holds
|
|
155
|
-
// whose server-side `media_track_ended` has not yet arrived. Correlating by
|
|
156
|
-
//
|
|
157
|
-
//
|
|
154
|
+
// intentionalTrackEnds holds the browser track ids removed by publish/unpublish
|
|
155
|
+
// whose server-side `media_track_ended` has not yet arrived. Correlating by the
|
|
156
|
+
// track id (the generation identity the server echoes in track_id) keeps a
|
|
157
|
+
// delayed end for an old screen track from being mistaken for failure of a
|
|
158
|
+
// newly-published screen track.
|
|
158
159
|
intentionalTrackEnds = /* @__PURE__ */ new Map();
|
|
159
160
|
// negotiationChain serializes every offer/answer exchange, including recovery.
|
|
160
161
|
// User operations do not resolve until their answer is applied, so no caller
|
|
@@ -340,6 +341,7 @@ var Publisher = class {
|
|
|
340
341
|
};
|
|
341
342
|
if (initialTrack) {
|
|
342
343
|
const sender = initialTrack.type === "audio" ? this.addMicrophoneTrack(pc, initialTrack.track, initialTrack.stream) : pc.addTrack(initialTrack.track, initialTrack.stream);
|
|
344
|
+
if (initialTrack.type !== "audio") this.preferVideoCodecs(pc, sender);
|
|
343
345
|
this.typeSenders.set(
|
|
344
346
|
initialTrack.type,
|
|
345
347
|
sender
|
|
@@ -1095,7 +1097,7 @@ var Publisher = class {
|
|
|
1095
1097
|
sdp: local.sdp,
|
|
1096
1098
|
sdp_type: "offer",
|
|
1097
1099
|
negotiation_id: id,
|
|
1098
|
-
tracks: change?.labels ?? this.buildTrackLabels(),
|
|
1100
|
+
tracks: change?.labels?.() ?? this.buildTrackLabels(),
|
|
1099
1101
|
speech_enabled: this.speechEnabled || this.speechPending || void 0
|
|
1100
1102
|
});
|
|
1101
1103
|
this.releaseLocalCandidateBatch();
|
|
@@ -1504,7 +1506,7 @@ var Publisher = class {
|
|
|
1504
1506
|
this.typeSenders.set(type, addedSender);
|
|
1505
1507
|
}
|
|
1506
1508
|
return {
|
|
1507
|
-
labels: this.labelsReplacingType(type, track),
|
|
1509
|
+
labels: () => this.labelsReplacingType(type, track),
|
|
1508
1510
|
commit: () => {
|
|
1509
1511
|
for (const { track: oldTrack } of previous) {
|
|
1510
1512
|
this.unwatchTrack(oldTrack);
|
|
@@ -1550,6 +1552,39 @@ var Publisher = class {
|
|
|
1550
1552
|
this.microphoneTransceiver = transceiver;
|
|
1551
1553
|
return transceiver.sender;
|
|
1552
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Reorders the codecs a video sender offers so the browser prefers the
|
|
1557
|
+
* configured codecs (VP9 by default). This is what actually controls the wire
|
|
1558
|
+
* format: the browser is the offerer and sends its own top-of-offer codec, and
|
|
1559
|
+
* the media server (Pion) answers by mirroring the offer's codec order — so the
|
|
1560
|
+
* server's own registration order has no say. Floating VP9 to the front of the
|
|
1561
|
+
* offer is therefore the lever. Codecs not in the preference keep their native
|
|
1562
|
+
* order behind it, so anything VP9 can't satisfy still negotiates.
|
|
1563
|
+
*
|
|
1564
|
+
* Best-effort: browsers lacking `getCapabilities`/`setCodecPreferences` (older
|
|
1565
|
+
* Safari) keep their default order, and any failure is swallowed — codec
|
|
1566
|
+
* preference is an optimization, never a requirement for publishing.
|
|
1567
|
+
*/
|
|
1568
|
+
preferVideoCodecs(pc, sender) {
|
|
1569
|
+
const preferred = this.opts.preferredVideoCodecs ?? ["video/VP9"];
|
|
1570
|
+
if (preferred.length === 0) return;
|
|
1571
|
+
if (sender.track && sender.track.kind !== "video") return;
|
|
1572
|
+
if (typeof RTCRtpSender === "undefined" || typeof RTCRtpSender.getCapabilities !== "function") return;
|
|
1573
|
+
if (typeof pc.getTransceivers !== "function") return;
|
|
1574
|
+
const caps = RTCRtpSender.getCapabilities("video");
|
|
1575
|
+
if (!caps?.codecs) return;
|
|
1576
|
+
const transceiver = pc.getTransceivers().find((t) => t.sender === sender);
|
|
1577
|
+
if (!transceiver || typeof transceiver.setCodecPreferences !== "function") return;
|
|
1578
|
+
const rank = (mimeType) => {
|
|
1579
|
+
const idx = preferred.findIndex((p) => p.toLowerCase() === mimeType.toLowerCase());
|
|
1580
|
+
return idx === -1 ? preferred.length : idx;
|
|
1581
|
+
};
|
|
1582
|
+
const ordered = caps.codecs.map((codec, index) => ({ codec, index })).sort((a, b) => rank(a.codec.mimeType) - rank(b.codec.mimeType) || a.index - b.index).map((entry) => entry.codec);
|
|
1583
|
+
try {
|
|
1584
|
+
transceiver.setCodecPreferences(ordered);
|
|
1585
|
+
} catch {
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1553
1588
|
/** Registers one physical video track under its logical type and source stream. */
|
|
1554
1589
|
registerTrack(track, stream, type) {
|
|
1555
1590
|
this.published.set(track, type);
|
|
@@ -1568,18 +1603,34 @@ var Publisher = class {
|
|
|
1568
1603
|
buildTrackLabels() {
|
|
1569
1604
|
const labels = [];
|
|
1570
1605
|
for (const [track, type] of this.published) {
|
|
1571
|
-
|
|
1606
|
+
const mid = this.midForTrack(track);
|
|
1607
|
+
if (mid !== null) labels.push({ mid, id: track.id, type });
|
|
1572
1608
|
}
|
|
1573
1609
|
return labels;
|
|
1574
1610
|
}
|
|
1575
1611
|
labelsReplacingType(type, replacement) {
|
|
1576
1612
|
const labels = [];
|
|
1577
1613
|
for (const [track, publishedType] of this.published) {
|
|
1578
|
-
if (publishedType
|
|
1614
|
+
if (publishedType === type) continue;
|
|
1615
|
+
const mid = this.midForTrack(track);
|
|
1616
|
+
if (mid !== null) labels.push({ mid, id: track.id, type: publishedType });
|
|
1617
|
+
}
|
|
1618
|
+
if (replacement) {
|
|
1619
|
+
const mid = this.midForTrack(replacement);
|
|
1620
|
+
if (mid !== null) labels.push({ mid, id: replacement.id, type });
|
|
1579
1621
|
}
|
|
1580
|
-
if (replacement) labels.push({ id: replacement.id, type });
|
|
1581
1622
|
return labels;
|
|
1582
1623
|
}
|
|
1624
|
+
/**
|
|
1625
|
+
* The negotiated mid of the transceiver currently sending `track`, or null if
|
|
1626
|
+
* none is found or it has not been negotiated yet. The mid is the identifier
|
|
1627
|
+
* both peers agree on; it is assigned once setLocalDescription runs, which the
|
|
1628
|
+
* publisher always does before sending an offer's labels.
|
|
1629
|
+
*/
|
|
1630
|
+
midForTrack(track) {
|
|
1631
|
+
const transceiver = this.pc?.getTransceivers().find((candidate) => candidate.sender.track === track);
|
|
1632
|
+
return transceiver?.mid ?? null;
|
|
1633
|
+
}
|
|
1583
1634
|
async stagePublish(track, stream, type) {
|
|
1584
1635
|
const pc = this.pc;
|
|
1585
1636
|
if (!pc) throw new Error("publisher not started");
|
|
@@ -1607,10 +1658,11 @@ var Publisher = class {
|
|
|
1607
1658
|
throw new SenderRestoreError(`inactive ${type} sender still has a track`);
|
|
1608
1659
|
}
|
|
1609
1660
|
addedSender = pc.addTrack(track, stream);
|
|
1661
|
+
this.preferVideoCodecs(pc, addedSender);
|
|
1610
1662
|
this.typeSenders.set(type, addedSender);
|
|
1611
1663
|
}
|
|
1612
1664
|
return {
|
|
1613
|
-
labels: this.labelsReplacingType(type, track),
|
|
1665
|
+
labels: () => this.labelsReplacingType(type, track),
|
|
1614
1666
|
commit: () => {
|
|
1615
1667
|
this.cancelMediaRecovery(type);
|
|
1616
1668
|
this.recoveryState(type).required = false;
|
|
@@ -1658,7 +1710,7 @@ var Publisher = class {
|
|
|
1658
1710
|
for (const { track } of previous) this.expectIntentionalTrackEnd(track.id, type);
|
|
1659
1711
|
pc.removeTrack(typeSender);
|
|
1660
1712
|
return {
|
|
1661
|
-
labels: this.labelsReplacingType(type),
|
|
1713
|
+
labels: () => this.labelsReplacingType(type),
|
|
1662
1714
|
commit: () => {
|
|
1663
1715
|
this.cancelMediaRecovery(type);
|
|
1664
1716
|
this.recoveryState(type).required = false;
|