@reactor-team/js-sdk 2.3.2 → 2.5.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/dist/index.d.mts +175 -26
- package/dist/index.d.ts +175 -26
- package/dist/index.js +460 -142
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +465 -151
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -51,6 +51,12 @@ var __async = (__this, __arguments, generator) => {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
// src/types.ts
|
|
54
|
+
function video(name, _options) {
|
|
55
|
+
return { name, kind: "video" };
|
|
56
|
+
}
|
|
57
|
+
function audio(name, _options) {
|
|
58
|
+
return { name, kind: "audio" };
|
|
59
|
+
}
|
|
54
60
|
var ConflictError = class extends Error {
|
|
55
61
|
constructor(message) {
|
|
56
62
|
super(message);
|
|
@@ -124,10 +130,53 @@ function createPeerConnection(config) {
|
|
|
124
130
|
function createDataChannel(pc, label) {
|
|
125
131
|
return pc.createDataChannel(label != null ? label : DEFAULT_DATA_CHANNEL_LABEL);
|
|
126
132
|
}
|
|
127
|
-
function
|
|
133
|
+
function rewriteMids(sdp, trackNames) {
|
|
134
|
+
const lines = sdp.split("\r\n");
|
|
135
|
+
let mediaIdx = 0;
|
|
136
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
137
|
+
let inApplication = false;
|
|
138
|
+
for (let i = 0; i < lines.length; i++) {
|
|
139
|
+
if (lines[i].startsWith("m=")) {
|
|
140
|
+
inApplication = lines[i].startsWith("m=application");
|
|
141
|
+
}
|
|
142
|
+
if (!inApplication && lines[i].startsWith("a=mid:")) {
|
|
143
|
+
const oldMid = lines[i].substring("a=mid:".length);
|
|
144
|
+
if (mediaIdx < trackNames.length) {
|
|
145
|
+
const newMid = trackNames[mediaIdx];
|
|
146
|
+
replacements.set(oldMid, newMid);
|
|
147
|
+
lines[i] = `a=mid:${newMid}`;
|
|
148
|
+
mediaIdx++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (let i = 0; i < lines.length; i++) {
|
|
153
|
+
if (lines[i].startsWith("a=group:BUNDLE ")) {
|
|
154
|
+
const parts = lines[i].split(" ");
|
|
155
|
+
for (let j = 1; j < parts.length; j++) {
|
|
156
|
+
const replacement = replacements.get(parts[j]);
|
|
157
|
+
if (replacement !== void 0) {
|
|
158
|
+
parts[j] = replacement;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
lines[i] = parts.join(" ");
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return lines.join("\r\n");
|
|
166
|
+
}
|
|
167
|
+
function createOffer(pc, trackNames) {
|
|
128
168
|
return __async(this, null, function* () {
|
|
129
169
|
const offer = yield pc.createOffer();
|
|
130
|
-
|
|
170
|
+
if (trackNames && trackNames.length > 0 && offer.sdp) {
|
|
171
|
+
const munged = rewriteMids(offer.sdp, trackNames);
|
|
172
|
+
const mungedOffer = new RTCSessionDescription({
|
|
173
|
+
type: "offer",
|
|
174
|
+
sdp: munged
|
|
175
|
+
});
|
|
176
|
+
yield pc.setLocalDescription(mungedOffer);
|
|
177
|
+
} else {
|
|
178
|
+
yield pc.setLocalDescription(offer);
|
|
179
|
+
}
|
|
131
180
|
yield waitForIceGathering(pc);
|
|
132
181
|
const localDescription = pc.localDescription;
|
|
133
182
|
if (!localDescription) {
|
|
@@ -206,6 +255,52 @@ function parseMessage(data) {
|
|
|
206
255
|
function closePeerConnection(pc) {
|
|
207
256
|
pc.close();
|
|
208
257
|
}
|
|
258
|
+
function extractConnectionStats(report) {
|
|
259
|
+
let rtt;
|
|
260
|
+
let availableOutgoingBitrate;
|
|
261
|
+
let localCandidateId;
|
|
262
|
+
let framesPerSecond;
|
|
263
|
+
let jitter;
|
|
264
|
+
let packetLossRatio;
|
|
265
|
+
report.forEach((stat) => {
|
|
266
|
+
if (stat.type === "candidate-pair" && stat.state === "succeeded") {
|
|
267
|
+
if (stat.currentRoundTripTime !== void 0) {
|
|
268
|
+
rtt = stat.currentRoundTripTime * 1e3;
|
|
269
|
+
}
|
|
270
|
+
if (stat.availableOutgoingBitrate !== void 0) {
|
|
271
|
+
availableOutgoingBitrate = stat.availableOutgoingBitrate;
|
|
272
|
+
}
|
|
273
|
+
localCandidateId = stat.localCandidateId;
|
|
274
|
+
}
|
|
275
|
+
if (stat.type === "inbound-rtp" && stat.kind === "video") {
|
|
276
|
+
if (stat.framesPerSecond !== void 0) {
|
|
277
|
+
framesPerSecond = stat.framesPerSecond;
|
|
278
|
+
}
|
|
279
|
+
if (stat.jitter !== void 0) {
|
|
280
|
+
jitter = stat.jitter;
|
|
281
|
+
}
|
|
282
|
+
if (stat.packetsReceived !== void 0 && stat.packetsLost !== void 0 && stat.packetsReceived + stat.packetsLost > 0) {
|
|
283
|
+
packetLossRatio = stat.packetsLost / (stat.packetsReceived + stat.packetsLost);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
let candidateType;
|
|
288
|
+
if (localCandidateId) {
|
|
289
|
+
const localCandidate = report.get(localCandidateId);
|
|
290
|
+
if (localCandidate == null ? void 0 : localCandidate.candidateType) {
|
|
291
|
+
candidateType = localCandidate.candidateType;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
rtt,
|
|
296
|
+
candidateType,
|
|
297
|
+
availableOutgoingBitrate,
|
|
298
|
+
framesPerSecond,
|
|
299
|
+
packetLossRatio,
|
|
300
|
+
jitter,
|
|
301
|
+
timestamp: Date.now()
|
|
302
|
+
};
|
|
303
|
+
}
|
|
209
304
|
|
|
210
305
|
// src/core/CoordinatorClient.ts
|
|
211
306
|
var INITIAL_BACKOFF_MS = 500;
|
|
@@ -581,10 +676,15 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
|
|
|
581
676
|
|
|
582
677
|
// src/core/GPUMachineClient.ts
|
|
583
678
|
var PING_INTERVAL_MS = 5e3;
|
|
679
|
+
var STATS_INTERVAL_MS = 2e3;
|
|
584
680
|
var GPUMachineClient = class {
|
|
585
681
|
constructor(config) {
|
|
586
682
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
587
683
|
this.status = "disconnected";
|
|
684
|
+
this.transceiverMap = /* @__PURE__ */ new Map();
|
|
685
|
+
this.publishedTracks = /* @__PURE__ */ new Map();
|
|
686
|
+
this.peerConnected = false;
|
|
687
|
+
this.dataChannelOpen = false;
|
|
588
688
|
this.config = config;
|
|
589
689
|
}
|
|
590
690
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -608,10 +708,18 @@ var GPUMachineClient = class {
|
|
|
608
708
|
// SDP & Connection
|
|
609
709
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
610
710
|
/**
|
|
611
|
-
* Creates an SDP offer
|
|
711
|
+
* Creates an SDP offer based on the declared tracks.
|
|
712
|
+
*
|
|
713
|
+
* **RECEIVE** = client receives from the model (model → client) → `recvonly`
|
|
714
|
+
* **SEND** = client sends to the model (client → model) → `sendonly`
|
|
715
|
+
*
|
|
716
|
+
* Track names must be unique across both arrays. A name that appears in
|
|
717
|
+
* both `receive` and `send` will throw — use distinct names instead.
|
|
718
|
+
*
|
|
719
|
+
* The data channel is always created first (before transceivers).
|
|
612
720
|
* Must be called before connect().
|
|
613
721
|
*/
|
|
614
|
-
createOffer() {
|
|
722
|
+
createOffer(tracks) {
|
|
615
723
|
return __async(this, null, function* () {
|
|
616
724
|
if (!this.peerConnection) {
|
|
617
725
|
this.peerConnection = createPeerConnection(this.config);
|
|
@@ -622,14 +730,54 @@ var GPUMachineClient = class {
|
|
|
622
730
|
this.config.dataChannelLabel
|
|
623
731
|
);
|
|
624
732
|
this.setupDataChannelHandlers();
|
|
625
|
-
this.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
733
|
+
this.transceiverMap.clear();
|
|
734
|
+
const entries = this.buildTransceiverEntries(tracks);
|
|
735
|
+
for (const entry of entries) {
|
|
736
|
+
const transceiver = this.peerConnection.addTransceiver(entry.kind, {
|
|
737
|
+
direction: entry.direction
|
|
738
|
+
});
|
|
739
|
+
entry.transceiver = transceiver;
|
|
740
|
+
this.transceiverMap.set(entry.name, entry);
|
|
741
|
+
console.debug(
|
|
742
|
+
`[GPUMachineClient] Transceiver added: "${entry.name}" (${entry.kind}, ${entry.direction})`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
const trackNames = entries.map((e) => e.name);
|
|
746
|
+
const offer = yield createOffer(this.peerConnection, trackNames);
|
|
747
|
+
console.debug(
|
|
748
|
+
"[GPUMachineClient] Created SDP offer with MIDs:",
|
|
749
|
+
trackNames
|
|
750
|
+
);
|
|
630
751
|
return offer;
|
|
631
752
|
});
|
|
632
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Builds an ordered list of transceiver entries from the receive/send arrays.
|
|
756
|
+
*
|
|
757
|
+
* Each track produces exactly one transceiver — `recvonly` for receive,
|
|
758
|
+
* `sendonly` for send. Bidirectional (`sendrecv`) transceivers are not
|
|
759
|
+
* supported; the same track name in both arrays is an error.
|
|
760
|
+
*/
|
|
761
|
+
buildTransceiverEntries(tracks) {
|
|
762
|
+
const map = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const t of tracks.receive) {
|
|
764
|
+
if (map.has(t.name)) {
|
|
765
|
+
throw new Error(
|
|
766
|
+
`Duplicate receive track name "${t.name}". Track names must be unique.`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
map.set(t.name, { name: t.name, kind: t.kind, direction: "recvonly" });
|
|
770
|
+
}
|
|
771
|
+
for (const t of tracks.send) {
|
|
772
|
+
if (map.has(t.name)) {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`Track name "${t.name}" appears in both receive and send. Bidirectional tracks are not supported \u2014 use distinct names for the inbound and outbound directions (e.g. "${t.name}_in" and "${t.name}_out").`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
map.set(t.name, { name: t.name, kind: t.kind, direction: "sendonly" });
|
|
778
|
+
}
|
|
779
|
+
return Array.from(map.values());
|
|
780
|
+
}
|
|
633
781
|
/**
|
|
634
782
|
* Connects to the GPU machine using the provided SDP answer.
|
|
635
783
|
* createOffer() must be called first.
|
|
@@ -664,8 +812,9 @@ var GPUMachineClient = class {
|
|
|
664
812
|
disconnect() {
|
|
665
813
|
return __async(this, null, function* () {
|
|
666
814
|
this.stopPing();
|
|
667
|
-
|
|
668
|
-
|
|
815
|
+
this.stopStatsPolling();
|
|
816
|
+
for (const name of Array.from(this.publishedTracks.keys())) {
|
|
817
|
+
yield this.unpublishTrack(name);
|
|
669
818
|
}
|
|
670
819
|
if (this.dataChannel) {
|
|
671
820
|
this.dataChannel.close();
|
|
@@ -675,7 +824,9 @@ var GPUMachineClient = class {
|
|
|
675
824
|
closePeerConnection(this.peerConnection);
|
|
676
825
|
this.peerConnection = void 0;
|
|
677
826
|
}
|
|
678
|
-
this.
|
|
827
|
+
this.transceiverMap.clear();
|
|
828
|
+
this.peerConnected = false;
|
|
829
|
+
this.dataChannelOpen = false;
|
|
679
830
|
this.setStatus("disconnected");
|
|
680
831
|
console.debug("[GPUMachineClient] Disconnected");
|
|
681
832
|
});
|
|
@@ -703,7 +854,7 @@ var GPUMachineClient = class {
|
|
|
703
854
|
/**
|
|
704
855
|
* Sends a command to the GPU machine via the data channel.
|
|
705
856
|
* @param command The command to send
|
|
706
|
-
* @param data The data to send with the command. These are the parameters for the command, matching the
|
|
857
|
+
* @param data The data to send with the command. These are the parameters for the command, matching the schema in the capabilities dictionary.
|
|
707
858
|
* @param scope The message scope – "application" (default) for model commands, "runtime" for platform-level messages.
|
|
708
859
|
*/
|
|
709
860
|
sendCommand(command, data, scope = "application") {
|
|
@@ -720,63 +871,77 @@ var GPUMachineClient = class {
|
|
|
720
871
|
// Track Publishing
|
|
721
872
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
722
873
|
/**
|
|
723
|
-
* Publishes a
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
* @param track The MediaStreamTrack to publish
|
|
874
|
+
* Publishes a MediaStreamTrack to the named send track.
|
|
875
|
+
*
|
|
876
|
+
* @param name The declared track name (must exist in transceiverMap with a sendable direction).
|
|
877
|
+
* @param track The MediaStreamTrack to publish.
|
|
727
878
|
*/
|
|
728
|
-
publishTrack(track) {
|
|
879
|
+
publishTrack(name, track) {
|
|
729
880
|
return __async(this, null, function* () {
|
|
730
881
|
if (!this.peerConnection) {
|
|
731
882
|
throw new Error(
|
|
732
|
-
|
|
883
|
+
`[GPUMachineClient] Cannot publish track "${name}" - not initialized`
|
|
733
884
|
);
|
|
734
885
|
}
|
|
735
886
|
if (this.status !== "connected") {
|
|
736
887
|
throw new Error(
|
|
737
|
-
|
|
888
|
+
`[GPUMachineClient] Cannot publish track "${name}" - not connected`
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
const entry = this.transceiverMap.get(name);
|
|
892
|
+
if (!entry || !entry.transceiver) {
|
|
893
|
+
throw new Error(
|
|
894
|
+
`[GPUMachineClient] Cannot publish track "${name}" - no transceiver (was it declared in tracks.send?)`
|
|
738
895
|
);
|
|
739
896
|
}
|
|
740
|
-
if (
|
|
897
|
+
if (entry.direction === "recvonly") {
|
|
741
898
|
throw new Error(
|
|
742
|
-
|
|
899
|
+
`[GPUMachineClient] Cannot publish track "${name}" - transceiver is recvonly`
|
|
743
900
|
);
|
|
744
901
|
}
|
|
745
902
|
try {
|
|
746
|
-
yield
|
|
747
|
-
this.
|
|
903
|
+
yield entry.transceiver.sender.replaceTrack(track);
|
|
904
|
+
this.publishedTracks.set(name, track);
|
|
748
905
|
console.debug(
|
|
749
|
-
|
|
750
|
-
track.kind
|
|
906
|
+
`[GPUMachineClient] Track "${name}" published successfully`
|
|
751
907
|
);
|
|
752
908
|
} catch (error) {
|
|
753
|
-
console.error(
|
|
909
|
+
console.error(
|
|
910
|
+
`[GPUMachineClient] Failed to publish track "${name}":`,
|
|
911
|
+
error
|
|
912
|
+
);
|
|
754
913
|
throw error;
|
|
755
914
|
}
|
|
756
915
|
});
|
|
757
916
|
}
|
|
758
917
|
/**
|
|
759
|
-
* Unpublishes the
|
|
918
|
+
* Unpublishes the track with the given name.
|
|
760
919
|
*/
|
|
761
|
-
unpublishTrack() {
|
|
920
|
+
unpublishTrack(name) {
|
|
762
921
|
return __async(this, null, function* () {
|
|
763
|
-
|
|
922
|
+
const entry = this.transceiverMap.get(name);
|
|
923
|
+
if (!(entry == null ? void 0 : entry.transceiver) || !this.publishedTracks.has(name)) return;
|
|
764
924
|
try {
|
|
765
|
-
yield
|
|
766
|
-
console.debug(
|
|
925
|
+
yield entry.transceiver.sender.replaceTrack(null);
|
|
926
|
+
console.debug(
|
|
927
|
+
`[GPUMachineClient] Track "${name}" unpublished successfully`
|
|
928
|
+
);
|
|
767
929
|
} catch (error) {
|
|
768
|
-
console.error(
|
|
930
|
+
console.error(
|
|
931
|
+
`[GPUMachineClient] Failed to unpublish track "${name}":`,
|
|
932
|
+
error
|
|
933
|
+
);
|
|
769
934
|
throw error;
|
|
770
935
|
} finally {
|
|
771
|
-
this.
|
|
936
|
+
this.publishedTracks.delete(name);
|
|
772
937
|
}
|
|
773
938
|
});
|
|
774
939
|
}
|
|
775
940
|
/**
|
|
776
|
-
* Returns the currently published track.
|
|
941
|
+
* Returns the currently published track for the given name.
|
|
777
942
|
*/
|
|
778
|
-
getPublishedTrack() {
|
|
779
|
-
return this.
|
|
943
|
+
getPublishedTrack(name) {
|
|
944
|
+
return this.publishedTracks.get(name);
|
|
780
945
|
}
|
|
781
946
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
782
947
|
// Getters
|
|
@@ -820,8 +985,39 @@ var GPUMachineClient = class {
|
|
|
820
985
|
}
|
|
821
986
|
}
|
|
822
987
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
988
|
+
// Stats Polling (RTT)
|
|
989
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
990
|
+
getStats() {
|
|
991
|
+
return this.stats;
|
|
992
|
+
}
|
|
993
|
+
startStatsPolling() {
|
|
994
|
+
this.stopStatsPolling();
|
|
995
|
+
this.statsInterval = setInterval(() => __async(this, null, function* () {
|
|
996
|
+
if (!this.peerConnection) return;
|
|
997
|
+
try {
|
|
998
|
+
const report = yield this.peerConnection.getStats();
|
|
999
|
+
this.stats = extractConnectionStats(report);
|
|
1000
|
+
this.emit("statsUpdate", this.stats);
|
|
1001
|
+
} catch (e) {
|
|
1002
|
+
}
|
|
1003
|
+
}), STATS_INTERVAL_MS);
|
|
1004
|
+
}
|
|
1005
|
+
stopStatsPolling() {
|
|
1006
|
+
if (this.statsInterval !== void 0) {
|
|
1007
|
+
clearInterval(this.statsInterval);
|
|
1008
|
+
this.statsInterval = void 0;
|
|
1009
|
+
}
|
|
1010
|
+
this.stats = void 0;
|
|
1011
|
+
}
|
|
1012
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
823
1013
|
// Private Helpers
|
|
824
1014
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1015
|
+
checkFullyConnected() {
|
|
1016
|
+
if (this.peerConnected && this.dataChannelOpen) {
|
|
1017
|
+
this.setStatus("connected");
|
|
1018
|
+
this.startStatsPolling();
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
825
1021
|
setStatus(newStatus) {
|
|
826
1022
|
if (this.status !== newStatus) {
|
|
827
1023
|
this.status = newStatus;
|
|
@@ -837,13 +1033,16 @@ var GPUMachineClient = class {
|
|
|
837
1033
|
if (state) {
|
|
838
1034
|
switch (state) {
|
|
839
1035
|
case "connected":
|
|
840
|
-
this.
|
|
1036
|
+
this.peerConnected = true;
|
|
1037
|
+
this.checkFullyConnected();
|
|
841
1038
|
break;
|
|
842
1039
|
case "disconnected":
|
|
843
1040
|
case "closed":
|
|
1041
|
+
this.peerConnected = false;
|
|
844
1042
|
this.setStatus("disconnected");
|
|
845
1043
|
break;
|
|
846
1044
|
case "failed":
|
|
1045
|
+
this.peerConnected = false;
|
|
847
1046
|
this.setStatus("error");
|
|
848
1047
|
break;
|
|
849
1048
|
}
|
|
@@ -851,9 +1050,13 @@ var GPUMachineClient = class {
|
|
|
851
1050
|
};
|
|
852
1051
|
this.peerConnection.ontrack = (event) => {
|
|
853
1052
|
var _a;
|
|
854
|
-
|
|
1053
|
+
const mid = event.transceiver.mid;
|
|
1054
|
+
const trackName = mid != null ? mid : `unknown-${event.track.id}`;
|
|
1055
|
+
console.debug(
|
|
1056
|
+
`[GPUMachineClient] Track received: "${trackName}" (${event.track.kind}, mid=${mid})`
|
|
1057
|
+
);
|
|
855
1058
|
const stream = (_a = event.streams[0]) != null ? _a : new MediaStream([event.track]);
|
|
856
|
-
this.emit("trackReceived", event.track, stream);
|
|
1059
|
+
this.emit("trackReceived", trackName, event.track, stream);
|
|
857
1060
|
};
|
|
858
1061
|
this.peerConnection.onicecandidate = (event) => {
|
|
859
1062
|
if (event.candidate) {
|
|
@@ -873,10 +1076,13 @@ var GPUMachineClient = class {
|
|
|
873
1076
|
if (!this.dataChannel) return;
|
|
874
1077
|
this.dataChannel.onopen = () => {
|
|
875
1078
|
console.debug("[GPUMachineClient] Data channel open");
|
|
1079
|
+
this.dataChannelOpen = true;
|
|
876
1080
|
this.startPing();
|
|
1081
|
+
this.checkFullyConnected();
|
|
877
1082
|
};
|
|
878
1083
|
this.dataChannel.onclose = () => {
|
|
879
1084
|
console.debug("[GPUMachineClient] Data channel closed");
|
|
1085
|
+
this.dataChannelOpen = false;
|
|
880
1086
|
this.stopPing();
|
|
881
1087
|
};
|
|
882
1088
|
this.dataChannel.onerror = (error) => {
|
|
@@ -910,10 +1116,29 @@ var GPUMachineClient = class {
|
|
|
910
1116
|
import { z as z2 } from "zod";
|
|
911
1117
|
var LOCAL_COORDINATOR_URL = "http://localhost:8080";
|
|
912
1118
|
var PROD_COORDINATOR_URL = "https://api.reactor.inc";
|
|
1119
|
+
var TrackConfigSchema = z2.object({
|
|
1120
|
+
name: z2.string(),
|
|
1121
|
+
kind: z2.enum(["audio", "video"])
|
|
1122
|
+
});
|
|
913
1123
|
var OptionsSchema = z2.object({
|
|
914
1124
|
coordinatorUrl: z2.string().default(PROD_COORDINATOR_URL),
|
|
915
1125
|
modelName: z2.string(),
|
|
916
|
-
local: z2.boolean().default(false)
|
|
1126
|
+
local: z2.boolean().default(false),
|
|
1127
|
+
/**
|
|
1128
|
+
* Tracks the client **RECEIVES** from the model (model → client).
|
|
1129
|
+
* Each entry produces a `recvonly` transceiver.
|
|
1130
|
+
* Names must be unique across both `receive` and `send`.
|
|
1131
|
+
*
|
|
1132
|
+
* When omitted, defaults to a single video track named `"main_video"`.
|
|
1133
|
+
* Pass an explicit empty array to opt out of the default.
|
|
1134
|
+
*/
|
|
1135
|
+
receive: z2.array(TrackConfigSchema).default([{ name: "main_video", kind: "video" }]),
|
|
1136
|
+
/**
|
|
1137
|
+
* Tracks the client **SENDS** to the model (client → model).
|
|
1138
|
+
* Each entry produces a `sendonly` transceiver.
|
|
1139
|
+
* Names must be unique across both `receive` and `send`.
|
|
1140
|
+
*/
|
|
1141
|
+
send: z2.array(TrackConfigSchema).default([])
|
|
917
1142
|
});
|
|
918
1143
|
var Reactor = class {
|
|
919
1144
|
constructor(options) {
|
|
@@ -924,7 +1149,9 @@ var Reactor = class {
|
|
|
924
1149
|
this.coordinatorUrl = validatedOptions.coordinatorUrl;
|
|
925
1150
|
this.model = validatedOptions.modelName;
|
|
926
1151
|
this.local = validatedOptions.local;
|
|
927
|
-
|
|
1152
|
+
this.receive = validatedOptions.receive;
|
|
1153
|
+
this.send = validatedOptions.send;
|
|
1154
|
+
if (this.local && options.coordinatorUrl === void 0) {
|
|
928
1155
|
this.coordinatorUrl = LOCAL_COORDINATOR_URL;
|
|
929
1156
|
}
|
|
930
1157
|
}
|
|
@@ -944,13 +1171,11 @@ var Reactor = class {
|
|
|
944
1171
|
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
|
|
945
1172
|
}
|
|
946
1173
|
/**
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
* @param command The command name
|
|
1174
|
+
* Sends a command to the model via the data channel.
|
|
1175
|
+
*
|
|
1176
|
+
* @param command The command name.
|
|
950
1177
|
* @param data The command payload.
|
|
951
|
-
* @param scope
|
|
952
|
-
* "runtime" for platform-level messages (e.g. requestCapabilities).
|
|
953
|
-
* @throws Error if not in ready state
|
|
1178
|
+
* @param scope "application" (default) for model commands, "runtime" for platform messages.
|
|
954
1179
|
*/
|
|
955
1180
|
sendCommand(command, data, scope = "application") {
|
|
956
1181
|
return __async(this, null, function* () {
|
|
@@ -974,24 +1199,27 @@ var Reactor = class {
|
|
|
974
1199
|
});
|
|
975
1200
|
}
|
|
976
1201
|
/**
|
|
977
|
-
*
|
|
978
|
-
*
|
|
1202
|
+
* Publishes a MediaStreamTrack to a named send track.
|
|
1203
|
+
*
|
|
1204
|
+
* @param name The declared send track name (e.g. "webcam").
|
|
1205
|
+
* @param track The MediaStreamTrack to publish.
|
|
979
1206
|
*/
|
|
980
|
-
publishTrack(track) {
|
|
1207
|
+
publishTrack(name, track) {
|
|
981
1208
|
return __async(this, null, function* () {
|
|
982
1209
|
var _a;
|
|
983
1210
|
if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
|
|
984
|
-
|
|
985
|
-
|
|
1211
|
+
console.warn(
|
|
1212
|
+
`[Reactor] Cannot publish track "${name}", status is ${this.status}`
|
|
1213
|
+
);
|
|
986
1214
|
return;
|
|
987
1215
|
}
|
|
988
1216
|
try {
|
|
989
|
-
yield (_a = this.machineClient) == null ? void 0 : _a.publishTrack(track);
|
|
1217
|
+
yield (_a = this.machineClient) == null ? void 0 : _a.publishTrack(name, track);
|
|
990
1218
|
} catch (error) {
|
|
991
|
-
console.error(
|
|
1219
|
+
console.error(`[Reactor] Failed to publish track "${name}":`, error);
|
|
992
1220
|
this.createError(
|
|
993
1221
|
"TRACK_PUBLISH_FAILED",
|
|
994
|
-
`Failed to publish track: ${error}`,
|
|
1222
|
+
`Failed to publish track "${name}": ${error}`,
|
|
995
1223
|
"gpu",
|
|
996
1224
|
true
|
|
997
1225
|
);
|
|
@@ -999,18 +1227,20 @@ var Reactor = class {
|
|
|
999
1227
|
});
|
|
1000
1228
|
}
|
|
1001
1229
|
/**
|
|
1002
|
-
*
|
|
1230
|
+
* Unpublishes the track with the given name.
|
|
1231
|
+
*
|
|
1232
|
+
* @param name The declared send track name to unpublish.
|
|
1003
1233
|
*/
|
|
1004
|
-
unpublishTrack() {
|
|
1234
|
+
unpublishTrack(name) {
|
|
1005
1235
|
return __async(this, null, function* () {
|
|
1006
1236
|
var _a;
|
|
1007
1237
|
try {
|
|
1008
|
-
yield (_a = this.machineClient) == null ? void 0 : _a.unpublishTrack();
|
|
1238
|
+
yield (_a = this.machineClient) == null ? void 0 : _a.unpublishTrack(name);
|
|
1009
1239
|
} catch (error) {
|
|
1010
|
-
console.error(
|
|
1240
|
+
console.error(`[Reactor] Failed to unpublish track "${name}":`, error);
|
|
1011
1241
|
this.createError(
|
|
1012
1242
|
"TRACK_UNPUBLISH_FAILED",
|
|
1013
|
-
`Failed to unpublish track: ${error}`,
|
|
1243
|
+
`Failed to unpublish track "${name}": ${error}`,
|
|
1014
1244
|
"gpu",
|
|
1015
1245
|
true
|
|
1016
1246
|
);
|
|
@@ -1037,7 +1267,10 @@ var Reactor = class {
|
|
|
1037
1267
|
this.machineClient = new GPUMachineClient({ iceServers });
|
|
1038
1268
|
this.setupMachineClientHandlers();
|
|
1039
1269
|
}
|
|
1040
|
-
const sdpOffer = yield this.machineClient.createOffer(
|
|
1270
|
+
const sdpOffer = yield this.machineClient.createOffer({
|
|
1271
|
+
send: this.send,
|
|
1272
|
+
receive: this.receive
|
|
1273
|
+
});
|
|
1041
1274
|
try {
|
|
1042
1275
|
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
1043
1276
|
this.sessionId,
|
|
@@ -1092,7 +1325,10 @@ var Reactor = class {
|
|
|
1092
1325
|
const iceServers = yield this.coordinatorClient.getIceServers();
|
|
1093
1326
|
this.machineClient = new GPUMachineClient({ iceServers });
|
|
1094
1327
|
this.setupMachineClientHandlers();
|
|
1095
|
-
const sdpOffer = yield this.machineClient.createOffer(
|
|
1328
|
+
const sdpOffer = yield this.machineClient.createOffer({
|
|
1329
|
+
send: this.send,
|
|
1330
|
+
receive: this.receive
|
|
1331
|
+
});
|
|
1096
1332
|
const sessionId = yield this.coordinatorClient.createSession(sdpOffer);
|
|
1097
1333
|
this.setSessionId(sessionId);
|
|
1098
1334
|
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
@@ -1127,7 +1363,11 @@ var Reactor = class {
|
|
|
1127
1363
|
setupMachineClientHandlers() {
|
|
1128
1364
|
if (!this.machineClient) return;
|
|
1129
1365
|
this.machineClient.on("message", (message, scope) => {
|
|
1130
|
-
|
|
1366
|
+
if (scope === "application") {
|
|
1367
|
+
this.emit("message", message);
|
|
1368
|
+
} else if (scope === "runtime") {
|
|
1369
|
+
this.emit("runtimeMessage", message);
|
|
1370
|
+
}
|
|
1131
1371
|
});
|
|
1132
1372
|
this.machineClient.on("statusChanged", (status) => {
|
|
1133
1373
|
switch (status) {
|
|
@@ -1150,10 +1390,13 @@ var Reactor = class {
|
|
|
1150
1390
|
});
|
|
1151
1391
|
this.machineClient.on(
|
|
1152
1392
|
"trackReceived",
|
|
1153
|
-
(track, stream) => {
|
|
1154
|
-
this.emit("
|
|
1393
|
+
(name, track, stream) => {
|
|
1394
|
+
this.emit("trackReceived", name, track, stream);
|
|
1155
1395
|
}
|
|
1156
1396
|
);
|
|
1397
|
+
this.machineClient.on("statsUpdate", (stats) => {
|
|
1398
|
+
this.emit("statsUpdate", stats);
|
|
1399
|
+
});
|
|
1157
1400
|
}
|
|
1158
1401
|
/**
|
|
1159
1402
|
* Disconnects from the coordinator and the gpu machine.
|
|
@@ -1166,7 +1409,11 @@ var Reactor = class {
|
|
|
1166
1409
|
return;
|
|
1167
1410
|
}
|
|
1168
1411
|
if (this.coordinatorClient && !recoverable) {
|
|
1169
|
-
|
|
1412
|
+
try {
|
|
1413
|
+
yield this.coordinatorClient.terminateSession();
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
console.error("[Reactor] Error terminating session:", error);
|
|
1416
|
+
}
|
|
1170
1417
|
this.coordinatorClient = void 0;
|
|
1171
1418
|
}
|
|
1172
1419
|
if (this.machineClient) {
|
|
@@ -1240,6 +1487,10 @@ var Reactor = class {
|
|
|
1240
1487
|
getLastError() {
|
|
1241
1488
|
return this.lastError;
|
|
1242
1489
|
}
|
|
1490
|
+
getStats() {
|
|
1491
|
+
var _a;
|
|
1492
|
+
return (_a = this.machineClient) == null ? void 0 : _a.getStats();
|
|
1493
|
+
}
|
|
1243
1494
|
/**
|
|
1244
1495
|
* Create and store an error
|
|
1245
1496
|
*/
|
|
@@ -1267,10 +1518,9 @@ var ReactorContext = createContext(
|
|
|
1267
1518
|
);
|
|
1268
1519
|
var defaultInitState = {
|
|
1269
1520
|
status: "disconnected",
|
|
1270
|
-
|
|
1521
|
+
tracks: {},
|
|
1271
1522
|
lastError: void 0,
|
|
1272
1523
|
sessionExpiration: void 0,
|
|
1273
|
-
insecureApiKey: void 0,
|
|
1274
1524
|
jwtToken: void 0,
|
|
1275
1525
|
sessionId: void 0
|
|
1276
1526
|
};
|
|
@@ -1291,7 +1541,11 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1291
1541
|
oldStatus: get().status,
|
|
1292
1542
|
newStatus
|
|
1293
1543
|
});
|
|
1294
|
-
|
|
1544
|
+
if (newStatus === "disconnected") {
|
|
1545
|
+
set({ status: newStatus, tracks: {} });
|
|
1546
|
+
} else {
|
|
1547
|
+
set({ status: newStatus });
|
|
1548
|
+
}
|
|
1295
1549
|
});
|
|
1296
1550
|
reactor.on(
|
|
1297
1551
|
"sessionExpirationChanged",
|
|
@@ -1303,13 +1557,13 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1303
1557
|
set({ sessionExpiration: newSessionExpiration });
|
|
1304
1558
|
}
|
|
1305
1559
|
);
|
|
1306
|
-
reactor.on("
|
|
1307
|
-
console.debug("[ReactorStore]
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1560
|
+
reactor.on("trackReceived", (name, track) => {
|
|
1561
|
+
console.debug("[ReactorStore] Track received", {
|
|
1562
|
+
name,
|
|
1563
|
+
kind: track.kind,
|
|
1564
|
+
id: track.id
|
|
1311
1565
|
});
|
|
1312
|
-
set({
|
|
1566
|
+
set({ tracks: __spreadProps(__spreadValues({}, get().tracks), { [name]: track }) });
|
|
1313
1567
|
});
|
|
1314
1568
|
reactor.on("error", (error) => {
|
|
1315
1569
|
console.debug("[ReactorStore] Error occurred", error);
|
|
@@ -1328,10 +1582,10 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1328
1582
|
// actions
|
|
1329
1583
|
onMessage: (handler) => {
|
|
1330
1584
|
console.debug("[ReactorStore] Registering message handler");
|
|
1331
|
-
get().internal.reactor.on("
|
|
1585
|
+
get().internal.reactor.on("message", handler);
|
|
1332
1586
|
return () => {
|
|
1333
1587
|
console.debug("[ReactorStore] Cleaning up message handler");
|
|
1334
|
-
get().internal.reactor.off("
|
|
1588
|
+
get().internal.reactor.off("message", handler);
|
|
1335
1589
|
};
|
|
1336
1590
|
},
|
|
1337
1591
|
sendCommand: (command, data, scope) => __async(null, null, function* () {
|
|
@@ -1373,27 +1627,31 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1373
1627
|
throw error;
|
|
1374
1628
|
}
|
|
1375
1629
|
}),
|
|
1376
|
-
|
|
1377
|
-
console.debug(
|
|
1630
|
+
publish: (name, track) => __async(null, null, function* () {
|
|
1631
|
+
console.debug(`[ReactorStore] Publishing track "${name}"`);
|
|
1378
1632
|
try {
|
|
1379
|
-
yield get().internal.reactor.publishTrack(
|
|
1380
|
-
console.debug(
|
|
1633
|
+
yield get().internal.reactor.publishTrack(name, track);
|
|
1634
|
+
console.debug(
|
|
1635
|
+
`[ReactorStore] Track "${name}" published successfully`
|
|
1636
|
+
);
|
|
1381
1637
|
} catch (error) {
|
|
1382
1638
|
console.error(
|
|
1383
|
-
|
|
1639
|
+
`[ReactorStore] Failed to publish track "${name}":`,
|
|
1384
1640
|
error
|
|
1385
1641
|
);
|
|
1386
1642
|
throw error;
|
|
1387
1643
|
}
|
|
1388
1644
|
}),
|
|
1389
|
-
|
|
1390
|
-
console.debug(
|
|
1645
|
+
unpublish: (name) => __async(null, null, function* () {
|
|
1646
|
+
console.debug(`[ReactorStore] Unpublishing track "${name}"`);
|
|
1391
1647
|
try {
|
|
1392
|
-
yield get().internal.reactor.unpublishTrack();
|
|
1393
|
-
console.debug(
|
|
1648
|
+
yield get().internal.reactor.unpublishTrack(name);
|
|
1649
|
+
console.debug(
|
|
1650
|
+
`[ReactorStore] Track "${name}" unpublished successfully`
|
|
1651
|
+
);
|
|
1394
1652
|
} catch (error) {
|
|
1395
1653
|
console.error(
|
|
1396
|
-
|
|
1654
|
+
`[ReactorStore] Failed to unpublish track "${name}":`,
|
|
1397
1655
|
error
|
|
1398
1656
|
);
|
|
1399
1657
|
throw error;
|
|
@@ -1439,7 +1697,7 @@ function ReactorProvider(_a) {
|
|
|
1439
1697
|
console.debug("[ReactorProvider] Reactor store created successfully");
|
|
1440
1698
|
}
|
|
1441
1699
|
const _a2 = connectOptions != null ? connectOptions : {}, { autoConnect = false } = _a2, pollingOptions = __objRest(_a2, ["autoConnect"]);
|
|
1442
|
-
const { coordinatorUrl, modelName, local } = props;
|
|
1700
|
+
const { coordinatorUrl, modelName, local, receive, send } = props;
|
|
1443
1701
|
const maxAttempts = pollingOptions.maxAttempts;
|
|
1444
1702
|
useEffect(() => {
|
|
1445
1703
|
const handleBeforeUnload = () => {
|
|
@@ -1495,6 +1753,8 @@ function ReactorProvider(_a) {
|
|
|
1495
1753
|
coordinatorUrl,
|
|
1496
1754
|
modelName,
|
|
1497
1755
|
local,
|
|
1756
|
+
receive,
|
|
1757
|
+
send,
|
|
1498
1758
|
jwtToken
|
|
1499
1759
|
})
|
|
1500
1760
|
);
|
|
@@ -1521,7 +1781,16 @@ function ReactorProvider(_a) {
|
|
|
1521
1781
|
console.error("[ReactorProvider] Failed to disconnect:", error);
|
|
1522
1782
|
});
|
|
1523
1783
|
};
|
|
1524
|
-
}, [
|
|
1784
|
+
}, [
|
|
1785
|
+
coordinatorUrl,
|
|
1786
|
+
modelName,
|
|
1787
|
+
autoConnect,
|
|
1788
|
+
local,
|
|
1789
|
+
receive,
|
|
1790
|
+
send,
|
|
1791
|
+
jwtToken,
|
|
1792
|
+
maxAttempts
|
|
1793
|
+
]);
|
|
1525
1794
|
return /* @__PURE__ */ jsx(ReactorContext.Provider, { value: storeRef.current, children });
|
|
1526
1795
|
}
|
|
1527
1796
|
function useReactorStore(selector) {
|
|
@@ -1534,7 +1803,7 @@ function useReactorStore(selector) {
|
|
|
1534
1803
|
|
|
1535
1804
|
// src/react/hooks.ts
|
|
1536
1805
|
import { useShallow } from "zustand/shallow";
|
|
1537
|
-
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
1806
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
1538
1807
|
function useReactor(selector) {
|
|
1539
1808
|
return useReactorStore(useShallow(selector));
|
|
1540
1809
|
}
|
|
@@ -1545,68 +1814,105 @@ function useReactorMessage(handler) {
|
|
|
1545
1814
|
handlerRef.current = handler;
|
|
1546
1815
|
}, [handler]);
|
|
1547
1816
|
useEffect2(() => {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
console.debug("[useReactorMessage] Message received", {
|
|
1551
|
-
message,
|
|
1552
|
-
scope
|
|
1553
|
-
});
|
|
1554
|
-
handlerRef.current(message, scope);
|
|
1817
|
+
const stableHandler = (message) => {
|
|
1818
|
+
handlerRef.current(message);
|
|
1555
1819
|
};
|
|
1556
|
-
reactor.on("
|
|
1557
|
-
console.debug("[useReactorMessage] Message handler registered");
|
|
1820
|
+
reactor.on("message", stableHandler);
|
|
1558
1821
|
return () => {
|
|
1559
|
-
|
|
1560
|
-
reactor.off("newMessage", stableHandler);
|
|
1822
|
+
reactor.off("message", stableHandler);
|
|
1561
1823
|
};
|
|
1562
1824
|
}, [reactor]);
|
|
1563
1825
|
}
|
|
1826
|
+
function useReactorInternalMessage(handler) {
|
|
1827
|
+
const reactor = useReactor((state) => state.internal.reactor);
|
|
1828
|
+
const handlerRef = useRef2(handler);
|
|
1829
|
+
useEffect2(() => {
|
|
1830
|
+
handlerRef.current = handler;
|
|
1831
|
+
}, [handler]);
|
|
1832
|
+
useEffect2(() => {
|
|
1833
|
+
const stableHandler = (message) => {
|
|
1834
|
+
handlerRef.current(message);
|
|
1835
|
+
};
|
|
1836
|
+
reactor.on("runtimeMessage", stableHandler);
|
|
1837
|
+
return () => {
|
|
1838
|
+
reactor.off("runtimeMessage", stableHandler);
|
|
1839
|
+
};
|
|
1840
|
+
}, [reactor]);
|
|
1841
|
+
}
|
|
1842
|
+
function useStats() {
|
|
1843
|
+
const reactor = useReactor((state) => state.internal.reactor);
|
|
1844
|
+
const [stats, setStats] = useState2(void 0);
|
|
1845
|
+
useEffect2(() => {
|
|
1846
|
+
const handler = (newStats) => {
|
|
1847
|
+
setStats(newStats);
|
|
1848
|
+
};
|
|
1849
|
+
reactor.on("statsUpdate", handler);
|
|
1850
|
+
return () => {
|
|
1851
|
+
reactor.off("statsUpdate", handler);
|
|
1852
|
+
setStats(void 0);
|
|
1853
|
+
};
|
|
1854
|
+
}, [reactor]);
|
|
1855
|
+
return stats;
|
|
1856
|
+
}
|
|
1564
1857
|
|
|
1565
1858
|
// src/react/ReactorView.tsx
|
|
1566
|
-
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1859
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
|
|
1567
1860
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1568
1861
|
function ReactorView({
|
|
1862
|
+
track = "main_video",
|
|
1863
|
+
audioTrack,
|
|
1569
1864
|
width,
|
|
1570
1865
|
height,
|
|
1571
1866
|
className,
|
|
1572
1867
|
style,
|
|
1573
|
-
videoObjectFit = "contain"
|
|
1868
|
+
videoObjectFit = "contain",
|
|
1869
|
+
muted = true
|
|
1574
1870
|
}) {
|
|
1575
|
-
const {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1871
|
+
const { videoMediaTrack, audioMediaTrack, status } = useReactor((state) => {
|
|
1872
|
+
var _a, _b;
|
|
1873
|
+
return {
|
|
1874
|
+
videoMediaTrack: (_a = state.tracks[track]) != null ? _a : null,
|
|
1875
|
+
audioMediaTrack: audioTrack ? (_b = state.tracks[audioTrack]) != null ? _b : null : null,
|
|
1876
|
+
status: state.status
|
|
1877
|
+
};
|
|
1878
|
+
});
|
|
1579
1879
|
const videoRef = useRef3(null);
|
|
1880
|
+
const mediaStream = useMemo(() => {
|
|
1881
|
+
const tracks = [];
|
|
1882
|
+
if (videoMediaTrack) tracks.push(videoMediaTrack);
|
|
1883
|
+
if (audioMediaTrack) tracks.push(audioMediaTrack);
|
|
1884
|
+
if (tracks.length === 0) return null;
|
|
1885
|
+
return new MediaStream(tracks);
|
|
1886
|
+
}, [videoMediaTrack, audioMediaTrack]);
|
|
1580
1887
|
useEffect3(() => {
|
|
1581
|
-
console.debug("[ReactorView]
|
|
1888
|
+
console.debug("[ReactorView] Media track effect triggered", {
|
|
1889
|
+
track,
|
|
1582
1890
|
hasVideoElement: !!videoRef.current,
|
|
1583
|
-
hasVideoTrack: !!
|
|
1584
|
-
|
|
1891
|
+
hasVideoTrack: !!videoMediaTrack,
|
|
1892
|
+
hasAudioTrack: !!audioMediaTrack
|
|
1585
1893
|
});
|
|
1586
|
-
if (videoRef.current &&
|
|
1587
|
-
console.debug("[ReactorView] Attaching
|
|
1894
|
+
if (videoRef.current && mediaStream) {
|
|
1895
|
+
console.debug("[ReactorView] Attaching media stream to element");
|
|
1588
1896
|
try {
|
|
1589
|
-
|
|
1590
|
-
videoRef.current.srcObject = stream;
|
|
1897
|
+
videoRef.current.srcObject = mediaStream;
|
|
1591
1898
|
videoRef.current.play().catch((e) => {
|
|
1592
1899
|
console.warn("[ReactorView] Auto-play failed:", e);
|
|
1593
1900
|
});
|
|
1594
|
-
console.debug("[ReactorView]
|
|
1901
|
+
console.debug("[ReactorView] Media stream attached successfully");
|
|
1595
1902
|
} catch (error) {
|
|
1596
|
-
console.error("[ReactorView] Failed to attach
|
|
1903
|
+
console.error("[ReactorView] Failed to attach media stream:", error);
|
|
1597
1904
|
}
|
|
1598
1905
|
return () => {
|
|
1599
|
-
console.debug("[ReactorView] Detaching
|
|
1906
|
+
console.debug("[ReactorView] Detaching media stream from element");
|
|
1600
1907
|
if (videoRef.current) {
|
|
1601
1908
|
videoRef.current.srcObject = null;
|
|
1602
|
-
console.debug("[ReactorView] Video track detached successfully");
|
|
1603
1909
|
}
|
|
1604
1910
|
};
|
|
1605
1911
|
} else {
|
|
1606
|
-
console.debug("[ReactorView] No
|
|
1912
|
+
console.debug("[ReactorView] No tracks or element to attach");
|
|
1607
1913
|
}
|
|
1608
|
-
}, [
|
|
1609
|
-
const showPlaceholder = !
|
|
1914
|
+
}, [mediaStream]);
|
|
1915
|
+
const showPlaceholder = !videoMediaTrack;
|
|
1610
1916
|
return /* @__PURE__ */ jsxs(
|
|
1611
1917
|
"div",
|
|
1612
1918
|
{
|
|
@@ -1626,7 +1932,7 @@ function ReactorView({
|
|
|
1626
1932
|
objectFit: videoObjectFit,
|
|
1627
1933
|
display: showPlaceholder ? "none" : "block"
|
|
1628
1934
|
},
|
|
1629
|
-
muted
|
|
1935
|
+
muted,
|
|
1630
1936
|
playsInline: true
|
|
1631
1937
|
}
|
|
1632
1938
|
),
|
|
@@ -1658,7 +1964,7 @@ function ReactorView({
|
|
|
1658
1964
|
}
|
|
1659
1965
|
|
|
1660
1966
|
// src/react/ReactorController.tsx
|
|
1661
|
-
import React, { useState as
|
|
1967
|
+
import React, { useState as useState3, useCallback } from "react";
|
|
1662
1968
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1663
1969
|
function ReactorController({
|
|
1664
1970
|
className,
|
|
@@ -1668,9 +1974,9 @@ function ReactorController({
|
|
|
1668
1974
|
sendCommand: state.sendCommand,
|
|
1669
1975
|
status: state.status
|
|
1670
1976
|
}));
|
|
1671
|
-
const [commands, setCommands] =
|
|
1672
|
-
const [formValues, setFormValues] =
|
|
1673
|
-
const [expandedCommands, setExpandedCommands] =
|
|
1977
|
+
const [commands, setCommands] = useState3({});
|
|
1978
|
+
const [formValues, setFormValues] = useState3({});
|
|
1979
|
+
const [expandedCommands, setExpandedCommands] = useState3({});
|
|
1674
1980
|
React.useEffect(() => {
|
|
1675
1981
|
if (status === "disconnected") {
|
|
1676
1982
|
setCommands({});
|
|
@@ -1697,8 +2003,8 @@ function ReactorController({
|
|
|
1697
2003
|
}, 5e3);
|
|
1698
2004
|
return () => clearInterval(interval);
|
|
1699
2005
|
}, [status, commands, requestCapabilities]);
|
|
1700
|
-
|
|
1701
|
-
if (
|
|
2006
|
+
useReactorInternalMessage((message) => {
|
|
2007
|
+
if (message && typeof message === "object" && message.type === "modelCapabilities" && message.data && "commands" in message.data) {
|
|
1702
2008
|
const commandsMessage = message.data;
|
|
1703
2009
|
setCommands(commandsMessage.commands);
|
|
1704
2010
|
const initialValues = {};
|
|
@@ -2111,9 +2417,10 @@ function ReactorController({
|
|
|
2111
2417
|
}
|
|
2112
2418
|
|
|
2113
2419
|
// src/react/WebcamStream.tsx
|
|
2114
|
-
import { useEffect as useEffect4, useRef as useRef4, useState as
|
|
2420
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
2115
2421
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2116
2422
|
function WebcamStream({
|
|
2423
|
+
track,
|
|
2117
2424
|
className,
|
|
2118
2425
|
style,
|
|
2119
2426
|
videoConstraints = {
|
|
@@ -2123,13 +2430,13 @@ function WebcamStream({
|
|
|
2123
2430
|
showWebcam = true,
|
|
2124
2431
|
videoObjectFit = "contain"
|
|
2125
2432
|
}) {
|
|
2126
|
-
const [stream, setStream] =
|
|
2127
|
-
const [isPublishing, setIsPublishing] =
|
|
2128
|
-
const [permissionDenied, setPermissionDenied] =
|
|
2129
|
-
const { status,
|
|
2433
|
+
const [stream, setStream] = useState4(null);
|
|
2434
|
+
const [isPublishing, setIsPublishing] = useState4(false);
|
|
2435
|
+
const [permissionDenied, setPermissionDenied] = useState4(false);
|
|
2436
|
+
const { status, publish, unpublish, reactor } = useReactor((state) => ({
|
|
2130
2437
|
status: state.status,
|
|
2131
|
-
|
|
2132
|
-
|
|
2438
|
+
publish: state.publish,
|
|
2439
|
+
unpublish: state.unpublish,
|
|
2133
2440
|
reactor: state.internal.reactor
|
|
2134
2441
|
}));
|
|
2135
2442
|
const videoRef = useRef4(null);
|
|
@@ -2154,15 +2461,15 @@ function WebcamStream({
|
|
|
2154
2461
|
const stopWebcam = () => __async(null, null, function* () {
|
|
2155
2462
|
console.debug("[WebcamPublisher] Stopping webcam");
|
|
2156
2463
|
try {
|
|
2157
|
-
yield
|
|
2464
|
+
yield unpublish(track);
|
|
2158
2465
|
console.debug("[WebcamPublisher] Unpublished before stopping");
|
|
2159
2466
|
} catch (err) {
|
|
2160
2467
|
console.error("[WebcamPublisher] Error unpublishing before stop:", err);
|
|
2161
2468
|
}
|
|
2162
2469
|
setIsPublishing(false);
|
|
2163
|
-
stream == null ? void 0 : stream.getTracks().forEach((
|
|
2164
|
-
|
|
2165
|
-
console.debug("[WebcamPublisher] Stopped track:",
|
|
2470
|
+
stream == null ? void 0 : stream.getTracks().forEach((t) => {
|
|
2471
|
+
t.stop();
|
|
2472
|
+
console.debug("[WebcamPublisher] Stopped track:", t.kind);
|
|
2166
2473
|
});
|
|
2167
2474
|
setStream(null);
|
|
2168
2475
|
console.debug("[WebcamPublisher] Webcam stopped");
|
|
@@ -2192,28 +2499,31 @@ function WebcamStream({
|
|
|
2192
2499
|
console.debug(
|
|
2193
2500
|
"[WebcamPublisher] Reactor ready, auto-publishing webcam stream"
|
|
2194
2501
|
);
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2502
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
2503
|
+
if (videoTrack) {
|
|
2504
|
+
publish(track, videoTrack).then(() => {
|
|
2505
|
+
console.debug("[WebcamPublisher] Auto-publish successful");
|
|
2506
|
+
setIsPublishing(true);
|
|
2507
|
+
}).catch((err) => {
|
|
2508
|
+
console.error("[WebcamPublisher] Auto-publish failed:", err);
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2201
2511
|
} else if (status !== "ready" && isPublishing) {
|
|
2202
2512
|
console.debug("[WebcamPublisher] Reactor not ready, auto-unpublishing");
|
|
2203
|
-
|
|
2513
|
+
unpublish(track).then(() => {
|
|
2204
2514
|
console.debug("[WebcamPublisher] Auto-unpublish successful");
|
|
2205
2515
|
setIsPublishing(false);
|
|
2206
2516
|
}).catch((err) => {
|
|
2207
2517
|
console.error("[WebcamPublisher] Auto-unpublish failed:", err);
|
|
2208
2518
|
});
|
|
2209
2519
|
}
|
|
2210
|
-
}, [status, stream, isPublishing,
|
|
2520
|
+
}, [status, stream, isPublishing, publish, unpublish, track]);
|
|
2211
2521
|
useEffect4(() => {
|
|
2212
2522
|
const handleError = (error) => {
|
|
2213
2523
|
console.debug("[WebcamPublisher] Received error event:", error);
|
|
2214
|
-
if (error.code === "
|
|
2524
|
+
if (error.code === "TRACK_PUBLISH_FAILED") {
|
|
2215
2525
|
console.debug(
|
|
2216
|
-
"[WebcamPublisher]
|
|
2526
|
+
"[WebcamPublisher] Track publish failed, resetting isPublishing state"
|
|
2217
2527
|
);
|
|
2218
2528
|
setIsPublishing(false);
|
|
2219
2529
|
}
|
|
@@ -2328,9 +2638,13 @@ export {
|
|
|
2328
2638
|
ReactorProvider,
|
|
2329
2639
|
ReactorView,
|
|
2330
2640
|
WebcamStream,
|
|
2641
|
+
audio,
|
|
2331
2642
|
fetchInsecureJwtToken,
|
|
2332
2643
|
useReactor,
|
|
2644
|
+
useReactorInternalMessage,
|
|
2333
2645
|
useReactorMessage,
|
|
2334
|
-
useReactorStore
|
|
2646
|
+
useReactorStore,
|
|
2647
|
+
useStats,
|
|
2648
|
+
video
|
|
2335
2649
|
};
|
|
2336
2650
|
//# sourceMappingURL=index.mjs.map
|