@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/README.md
CHANGED
|
@@ -181,6 +181,7 @@ the customer server owns a live control-token notify subscription.
|
|
|
181
181
|
| `gatewayHandshakeTimeoutMs` | `number` | Overall deadline for the initial gateway race and `accepted` → `ready` handshake. Unaccepted sockets are replaced after 3 seconds so a blackholed TCP flow cannot consume the full deadline. Defaults to 20 seconds. |
|
|
182
182
|
| `peerConnectionTimeoutMs` | `number` | Deadline after the initial offer for WebRTC to reach `connected`. Defaults to 30 seconds. |
|
|
183
183
|
| `signalingReconnectTimeoutMs` | `number` | How long to retry a dropped signaling socket against the selected regional gateway. Defaults to 20 seconds. |
|
|
184
|
+
| `preferredVideoCodecs` | `string[]` | Preferred video codecs, most-preferred first, as RTP MIME types (e.g. `"video/VP9"`, `"video/H264"`). Each published video track offers these ahead of the rest, so the browser sends the first one the media server also accepts. Defaults to `["video/VP9"]`. Pass `[]` to leave the browser's native order untouched. Codecs the browser lacks (or `setCodecPreferences` support, e.g. older Safari) are ignored — negotiation always falls back cleanly. |
|
|
184
185
|
| `callbacks` | `PublisherCallbacks` | Optional lifecycle callbacks (see below). |
|
|
185
186
|
|
|
186
187
|
### Methods & properties
|
package/dist/index.cjs
CHANGED
|
@@ -180,10 +180,11 @@ var Publisher = class {
|
|
|
180
180
|
// may later reuse any compatible inactive transceiver, not necessarily the one
|
|
181
181
|
// that previously carried the same logical type.
|
|
182
182
|
typeSenders = /* @__PURE__ */ new Map();
|
|
183
|
-
// intentionalTrackEnds holds
|
|
184
|
-
// whose server-side `media_track_ended` has not yet arrived. Correlating by
|
|
185
|
-
//
|
|
186
|
-
//
|
|
183
|
+
// intentionalTrackEnds holds the browser track ids removed by publish/unpublish
|
|
184
|
+
// whose server-side `media_track_ended` has not yet arrived. Correlating by the
|
|
185
|
+
// track id (the generation identity the server echoes in track_id) keeps a
|
|
186
|
+
// delayed end for an old screen track from being mistaken for failure of a
|
|
187
|
+
// newly-published screen track.
|
|
187
188
|
intentionalTrackEnds = /* @__PURE__ */ new Map();
|
|
188
189
|
// negotiationChain serializes every offer/answer exchange, including recovery.
|
|
189
190
|
// User operations do not resolve until their answer is applied, so no caller
|
|
@@ -369,6 +370,7 @@ var Publisher = class {
|
|
|
369
370
|
};
|
|
370
371
|
if (initialTrack) {
|
|
371
372
|
const sender = initialTrack.type === "audio" ? this.addMicrophoneTrack(pc, initialTrack.track, initialTrack.stream) : pc.addTrack(initialTrack.track, initialTrack.stream);
|
|
373
|
+
if (initialTrack.type !== "audio") this.preferVideoCodecs(pc, sender);
|
|
372
374
|
this.typeSenders.set(
|
|
373
375
|
initialTrack.type,
|
|
374
376
|
sender
|
|
@@ -1124,7 +1126,7 @@ var Publisher = class {
|
|
|
1124
1126
|
sdp: local.sdp,
|
|
1125
1127
|
sdp_type: "offer",
|
|
1126
1128
|
negotiation_id: id,
|
|
1127
|
-
tracks: change?.labels ?? this.buildTrackLabels(),
|
|
1129
|
+
tracks: change?.labels?.() ?? this.buildTrackLabels(),
|
|
1128
1130
|
speech_enabled: this.speechEnabled || this.speechPending || void 0
|
|
1129
1131
|
});
|
|
1130
1132
|
this.releaseLocalCandidateBatch();
|
|
@@ -1533,7 +1535,7 @@ var Publisher = class {
|
|
|
1533
1535
|
this.typeSenders.set(type, addedSender);
|
|
1534
1536
|
}
|
|
1535
1537
|
return {
|
|
1536
|
-
labels: this.labelsReplacingType(type, track),
|
|
1538
|
+
labels: () => this.labelsReplacingType(type, track),
|
|
1537
1539
|
commit: () => {
|
|
1538
1540
|
for (const { track: oldTrack } of previous) {
|
|
1539
1541
|
this.unwatchTrack(oldTrack);
|
|
@@ -1579,6 +1581,39 @@ var Publisher = class {
|
|
|
1579
1581
|
this.microphoneTransceiver = transceiver;
|
|
1580
1582
|
return transceiver.sender;
|
|
1581
1583
|
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Reorders the codecs a video sender offers so the browser prefers the
|
|
1586
|
+
* configured codecs (VP9 by default). This is what actually controls the wire
|
|
1587
|
+
* format: the browser is the offerer and sends its own top-of-offer codec, and
|
|
1588
|
+
* the media server (Pion) answers by mirroring the offer's codec order — so the
|
|
1589
|
+
* server's own registration order has no say. Floating VP9 to the front of the
|
|
1590
|
+
* offer is therefore the lever. Codecs not in the preference keep their native
|
|
1591
|
+
* order behind it, so anything VP9 can't satisfy still negotiates.
|
|
1592
|
+
*
|
|
1593
|
+
* Best-effort: browsers lacking `getCapabilities`/`setCodecPreferences` (older
|
|
1594
|
+
* Safari) keep their default order, and any failure is swallowed — codec
|
|
1595
|
+
* preference is an optimization, never a requirement for publishing.
|
|
1596
|
+
*/
|
|
1597
|
+
preferVideoCodecs(pc, sender) {
|
|
1598
|
+
const preferred = this.opts.preferredVideoCodecs ?? ["video/VP9"];
|
|
1599
|
+
if (preferred.length === 0) return;
|
|
1600
|
+
if (sender.track && sender.track.kind !== "video") return;
|
|
1601
|
+
if (typeof RTCRtpSender === "undefined" || typeof RTCRtpSender.getCapabilities !== "function") return;
|
|
1602
|
+
if (typeof pc.getTransceivers !== "function") return;
|
|
1603
|
+
const caps = RTCRtpSender.getCapabilities("video");
|
|
1604
|
+
if (!caps?.codecs) return;
|
|
1605
|
+
const transceiver = pc.getTransceivers().find((t) => t.sender === sender);
|
|
1606
|
+
if (!transceiver || typeof transceiver.setCodecPreferences !== "function") return;
|
|
1607
|
+
const rank = (mimeType) => {
|
|
1608
|
+
const idx = preferred.findIndex((p) => p.toLowerCase() === mimeType.toLowerCase());
|
|
1609
|
+
return idx === -1 ? preferred.length : idx;
|
|
1610
|
+
};
|
|
1611
|
+
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);
|
|
1612
|
+
try {
|
|
1613
|
+
transceiver.setCodecPreferences(ordered);
|
|
1614
|
+
} catch {
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1582
1617
|
/** Registers one physical video track under its logical type and source stream. */
|
|
1583
1618
|
registerTrack(track, stream, type) {
|
|
1584
1619
|
this.published.set(track, type);
|
|
@@ -1597,18 +1632,34 @@ var Publisher = class {
|
|
|
1597
1632
|
buildTrackLabels() {
|
|
1598
1633
|
const labels = [];
|
|
1599
1634
|
for (const [track, type] of this.published) {
|
|
1600
|
-
|
|
1635
|
+
const mid = this.midForTrack(track);
|
|
1636
|
+
if (mid !== null) labels.push({ mid, id: track.id, type });
|
|
1601
1637
|
}
|
|
1602
1638
|
return labels;
|
|
1603
1639
|
}
|
|
1604
1640
|
labelsReplacingType(type, replacement) {
|
|
1605
1641
|
const labels = [];
|
|
1606
1642
|
for (const [track, publishedType] of this.published) {
|
|
1607
|
-
if (publishedType
|
|
1643
|
+
if (publishedType === type) continue;
|
|
1644
|
+
const mid = this.midForTrack(track);
|
|
1645
|
+
if (mid !== null) labels.push({ mid, id: track.id, type: publishedType });
|
|
1646
|
+
}
|
|
1647
|
+
if (replacement) {
|
|
1648
|
+
const mid = this.midForTrack(replacement);
|
|
1649
|
+
if (mid !== null) labels.push({ mid, id: replacement.id, type });
|
|
1608
1650
|
}
|
|
1609
|
-
if (replacement) labels.push({ id: replacement.id, type });
|
|
1610
1651
|
return labels;
|
|
1611
1652
|
}
|
|
1653
|
+
/**
|
|
1654
|
+
* The negotiated mid of the transceiver currently sending `track`, or null if
|
|
1655
|
+
* none is found or it has not been negotiated yet. The mid is the identifier
|
|
1656
|
+
* both peers agree on; it is assigned once setLocalDescription runs, which the
|
|
1657
|
+
* publisher always does before sending an offer's labels.
|
|
1658
|
+
*/
|
|
1659
|
+
midForTrack(track) {
|
|
1660
|
+
const transceiver = this.pc?.getTransceivers().find((candidate) => candidate.sender.track === track);
|
|
1661
|
+
return transceiver?.mid ?? null;
|
|
1662
|
+
}
|
|
1612
1663
|
async stagePublish(track, stream, type) {
|
|
1613
1664
|
const pc = this.pc;
|
|
1614
1665
|
if (!pc) throw new Error("publisher not started");
|
|
@@ -1636,10 +1687,11 @@ var Publisher = class {
|
|
|
1636
1687
|
throw new SenderRestoreError(`inactive ${type} sender still has a track`);
|
|
1637
1688
|
}
|
|
1638
1689
|
addedSender = pc.addTrack(track, stream);
|
|
1690
|
+
this.preferVideoCodecs(pc, addedSender);
|
|
1639
1691
|
this.typeSenders.set(type, addedSender);
|
|
1640
1692
|
}
|
|
1641
1693
|
return {
|
|
1642
|
-
labels: this.labelsReplacingType(type, track),
|
|
1694
|
+
labels: () => this.labelsReplacingType(type, track),
|
|
1643
1695
|
commit: () => {
|
|
1644
1696
|
this.cancelMediaRecovery(type);
|
|
1645
1697
|
this.recoveryState(type).required = false;
|
|
@@ -1687,7 +1739,7 @@ var Publisher = class {
|
|
|
1687
1739
|
for (const { track } of previous) this.expectIntentionalTrackEnd(track.id, type);
|
|
1688
1740
|
pc.removeTrack(typeSender);
|
|
1689
1741
|
return {
|
|
1690
|
-
labels: this.labelsReplacingType(type),
|
|
1742
|
+
labels: () => this.labelsReplacingType(type),
|
|
1691
1743
|
commit: () => {
|
|
1692
1744
|
this.cancelMediaRecovery(type);
|
|
1693
1745
|
this.recoveryState(type).required = false;
|