@reactor-team/js-sdk 2.4.0 → 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 +138 -20
- package/dist/index.d.ts +138 -20
- package/dist/index.js +332 -127
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +330 -127
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -86,16 +86,24 @@ __export(index_exports, {
|
|
|
86
86
|
ReactorProvider: () => ReactorProvider,
|
|
87
87
|
ReactorView: () => ReactorView,
|
|
88
88
|
WebcamStream: () => WebcamStream,
|
|
89
|
+
audio: () => audio,
|
|
89
90
|
fetchInsecureJwtToken: () => fetchInsecureJwtToken,
|
|
90
91
|
useReactor: () => useReactor,
|
|
91
92
|
useReactorInternalMessage: () => useReactorInternalMessage,
|
|
92
93
|
useReactorMessage: () => useReactorMessage,
|
|
93
94
|
useReactorStore: () => useReactorStore,
|
|
94
|
-
useStats: () => useStats
|
|
95
|
+
useStats: () => useStats,
|
|
96
|
+
video: () => video
|
|
95
97
|
});
|
|
96
98
|
module.exports = __toCommonJS(index_exports);
|
|
97
99
|
|
|
98
100
|
// src/types.ts
|
|
101
|
+
function video(name, _options) {
|
|
102
|
+
return { name, kind: "video" };
|
|
103
|
+
}
|
|
104
|
+
function audio(name, _options) {
|
|
105
|
+
return { name, kind: "audio" };
|
|
106
|
+
}
|
|
99
107
|
var ConflictError = class extends Error {
|
|
100
108
|
constructor(message) {
|
|
101
109
|
super(message);
|
|
@@ -169,10 +177,53 @@ function createPeerConnection(config) {
|
|
|
169
177
|
function createDataChannel(pc, label) {
|
|
170
178
|
return pc.createDataChannel(label != null ? label : DEFAULT_DATA_CHANNEL_LABEL);
|
|
171
179
|
}
|
|
172
|
-
function
|
|
180
|
+
function rewriteMids(sdp, trackNames) {
|
|
181
|
+
const lines = sdp.split("\r\n");
|
|
182
|
+
let mediaIdx = 0;
|
|
183
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
184
|
+
let inApplication = false;
|
|
185
|
+
for (let i = 0; i < lines.length; i++) {
|
|
186
|
+
if (lines[i].startsWith("m=")) {
|
|
187
|
+
inApplication = lines[i].startsWith("m=application");
|
|
188
|
+
}
|
|
189
|
+
if (!inApplication && lines[i].startsWith("a=mid:")) {
|
|
190
|
+
const oldMid = lines[i].substring("a=mid:".length);
|
|
191
|
+
if (mediaIdx < trackNames.length) {
|
|
192
|
+
const newMid = trackNames[mediaIdx];
|
|
193
|
+
replacements.set(oldMid, newMid);
|
|
194
|
+
lines[i] = `a=mid:${newMid}`;
|
|
195
|
+
mediaIdx++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (let i = 0; i < lines.length; i++) {
|
|
200
|
+
if (lines[i].startsWith("a=group:BUNDLE ")) {
|
|
201
|
+
const parts = lines[i].split(" ");
|
|
202
|
+
for (let j = 1; j < parts.length; j++) {
|
|
203
|
+
const replacement = replacements.get(parts[j]);
|
|
204
|
+
if (replacement !== void 0) {
|
|
205
|
+
parts[j] = replacement;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
lines[i] = parts.join(" ");
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return lines.join("\r\n");
|
|
213
|
+
}
|
|
214
|
+
function createOffer(pc, trackNames) {
|
|
173
215
|
return __async(this, null, function* () {
|
|
174
216
|
const offer = yield pc.createOffer();
|
|
175
|
-
|
|
217
|
+
if (trackNames && trackNames.length > 0 && offer.sdp) {
|
|
218
|
+
const munged = rewriteMids(offer.sdp, trackNames);
|
|
219
|
+
const mungedOffer = new RTCSessionDescription({
|
|
220
|
+
type: "offer",
|
|
221
|
+
sdp: munged
|
|
222
|
+
});
|
|
223
|
+
yield pc.setLocalDescription(mungedOffer);
|
|
224
|
+
} else {
|
|
225
|
+
yield pc.setLocalDescription(offer);
|
|
226
|
+
}
|
|
176
227
|
yield waitForIceGathering(pc);
|
|
177
228
|
const localDescription = pc.localDescription;
|
|
178
229
|
if (!localDescription) {
|
|
@@ -677,6 +728,10 @@ var GPUMachineClient = class {
|
|
|
677
728
|
constructor(config) {
|
|
678
729
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
679
730
|
this.status = "disconnected";
|
|
731
|
+
this.transceiverMap = /* @__PURE__ */ new Map();
|
|
732
|
+
this.publishedTracks = /* @__PURE__ */ new Map();
|
|
733
|
+
this.peerConnected = false;
|
|
734
|
+
this.dataChannelOpen = false;
|
|
680
735
|
this.config = config;
|
|
681
736
|
}
|
|
682
737
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -700,10 +755,18 @@ var GPUMachineClient = class {
|
|
|
700
755
|
// SDP & Connection
|
|
701
756
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
702
757
|
/**
|
|
703
|
-
* Creates an SDP offer
|
|
758
|
+
* Creates an SDP offer based on the declared tracks.
|
|
759
|
+
*
|
|
760
|
+
* **RECEIVE** = client receives from the model (model → client) → `recvonly`
|
|
761
|
+
* **SEND** = client sends to the model (client → model) → `sendonly`
|
|
762
|
+
*
|
|
763
|
+
* Track names must be unique across both arrays. A name that appears in
|
|
764
|
+
* both `receive` and `send` will throw — use distinct names instead.
|
|
765
|
+
*
|
|
766
|
+
* The data channel is always created first (before transceivers).
|
|
704
767
|
* Must be called before connect().
|
|
705
768
|
*/
|
|
706
|
-
createOffer() {
|
|
769
|
+
createOffer(tracks) {
|
|
707
770
|
return __async(this, null, function* () {
|
|
708
771
|
if (!this.peerConnection) {
|
|
709
772
|
this.peerConnection = createPeerConnection(this.config);
|
|
@@ -714,14 +777,54 @@ var GPUMachineClient = class {
|
|
|
714
777
|
this.config.dataChannelLabel
|
|
715
778
|
);
|
|
716
779
|
this.setupDataChannelHandlers();
|
|
717
|
-
this.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
780
|
+
this.transceiverMap.clear();
|
|
781
|
+
const entries = this.buildTransceiverEntries(tracks);
|
|
782
|
+
for (const entry of entries) {
|
|
783
|
+
const transceiver = this.peerConnection.addTransceiver(entry.kind, {
|
|
784
|
+
direction: entry.direction
|
|
785
|
+
});
|
|
786
|
+
entry.transceiver = transceiver;
|
|
787
|
+
this.transceiverMap.set(entry.name, entry);
|
|
788
|
+
console.debug(
|
|
789
|
+
`[GPUMachineClient] Transceiver added: "${entry.name}" (${entry.kind}, ${entry.direction})`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
const trackNames = entries.map((e) => e.name);
|
|
793
|
+
const offer = yield createOffer(this.peerConnection, trackNames);
|
|
794
|
+
console.debug(
|
|
795
|
+
"[GPUMachineClient] Created SDP offer with MIDs:",
|
|
796
|
+
trackNames
|
|
797
|
+
);
|
|
722
798
|
return offer;
|
|
723
799
|
});
|
|
724
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Builds an ordered list of transceiver entries from the receive/send arrays.
|
|
803
|
+
*
|
|
804
|
+
* Each track produces exactly one transceiver — `recvonly` for receive,
|
|
805
|
+
* `sendonly` for send. Bidirectional (`sendrecv`) transceivers are not
|
|
806
|
+
* supported; the same track name in both arrays is an error.
|
|
807
|
+
*/
|
|
808
|
+
buildTransceiverEntries(tracks) {
|
|
809
|
+
const map = /* @__PURE__ */ new Map();
|
|
810
|
+
for (const t of tracks.receive) {
|
|
811
|
+
if (map.has(t.name)) {
|
|
812
|
+
throw new Error(
|
|
813
|
+
`Duplicate receive track name "${t.name}". Track names must be unique.`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
map.set(t.name, { name: t.name, kind: t.kind, direction: "recvonly" });
|
|
817
|
+
}
|
|
818
|
+
for (const t of tracks.send) {
|
|
819
|
+
if (map.has(t.name)) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
`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").`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
map.set(t.name, { name: t.name, kind: t.kind, direction: "sendonly" });
|
|
825
|
+
}
|
|
826
|
+
return Array.from(map.values());
|
|
827
|
+
}
|
|
725
828
|
/**
|
|
726
829
|
* Connects to the GPU machine using the provided SDP answer.
|
|
727
830
|
* createOffer() must be called first.
|
|
@@ -757,8 +860,8 @@ var GPUMachineClient = class {
|
|
|
757
860
|
return __async(this, null, function* () {
|
|
758
861
|
this.stopPing();
|
|
759
862
|
this.stopStatsPolling();
|
|
760
|
-
|
|
761
|
-
yield this.unpublishTrack();
|
|
863
|
+
for (const name of Array.from(this.publishedTracks.keys())) {
|
|
864
|
+
yield this.unpublishTrack(name);
|
|
762
865
|
}
|
|
763
866
|
if (this.dataChannel) {
|
|
764
867
|
this.dataChannel.close();
|
|
@@ -768,7 +871,9 @@ var GPUMachineClient = class {
|
|
|
768
871
|
closePeerConnection(this.peerConnection);
|
|
769
872
|
this.peerConnection = void 0;
|
|
770
873
|
}
|
|
771
|
-
this.
|
|
874
|
+
this.transceiverMap.clear();
|
|
875
|
+
this.peerConnected = false;
|
|
876
|
+
this.dataChannelOpen = false;
|
|
772
877
|
this.setStatus("disconnected");
|
|
773
878
|
console.debug("[GPUMachineClient] Disconnected");
|
|
774
879
|
});
|
|
@@ -796,7 +901,7 @@ var GPUMachineClient = class {
|
|
|
796
901
|
/**
|
|
797
902
|
* Sends a command to the GPU machine via the data channel.
|
|
798
903
|
* @param command The command to send
|
|
799
|
-
* @param data The data to send with the command. These are the parameters for the command, matching the
|
|
904
|
+
* @param data The data to send with the command. These are the parameters for the command, matching the schema in the capabilities dictionary.
|
|
800
905
|
* @param scope The message scope – "application" (default) for model commands, "runtime" for platform-level messages.
|
|
801
906
|
*/
|
|
802
907
|
sendCommand(command, data, scope = "application") {
|
|
@@ -813,63 +918,77 @@ var GPUMachineClient = class {
|
|
|
813
918
|
// Track Publishing
|
|
814
919
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
815
920
|
/**
|
|
816
|
-
* Publishes a
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
* @param track The MediaStreamTrack to publish
|
|
921
|
+
* Publishes a MediaStreamTrack to the named send track.
|
|
922
|
+
*
|
|
923
|
+
* @param name The declared track name (must exist in transceiverMap with a sendable direction).
|
|
924
|
+
* @param track The MediaStreamTrack to publish.
|
|
820
925
|
*/
|
|
821
|
-
publishTrack(track) {
|
|
926
|
+
publishTrack(name, track) {
|
|
822
927
|
return __async(this, null, function* () {
|
|
823
928
|
if (!this.peerConnection) {
|
|
824
929
|
throw new Error(
|
|
825
|
-
|
|
930
|
+
`[GPUMachineClient] Cannot publish track "${name}" - not initialized`
|
|
826
931
|
);
|
|
827
932
|
}
|
|
828
933
|
if (this.status !== "connected") {
|
|
829
934
|
throw new Error(
|
|
830
|
-
|
|
935
|
+
`[GPUMachineClient] Cannot publish track "${name}" - not connected`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
const entry = this.transceiverMap.get(name);
|
|
939
|
+
if (!entry || !entry.transceiver) {
|
|
940
|
+
throw new Error(
|
|
941
|
+
`[GPUMachineClient] Cannot publish track "${name}" - no transceiver (was it declared in tracks.send?)`
|
|
831
942
|
);
|
|
832
943
|
}
|
|
833
|
-
if (
|
|
944
|
+
if (entry.direction === "recvonly") {
|
|
834
945
|
throw new Error(
|
|
835
|
-
|
|
946
|
+
`[GPUMachineClient] Cannot publish track "${name}" - transceiver is recvonly`
|
|
836
947
|
);
|
|
837
948
|
}
|
|
838
949
|
try {
|
|
839
|
-
yield
|
|
840
|
-
this.
|
|
950
|
+
yield entry.transceiver.sender.replaceTrack(track);
|
|
951
|
+
this.publishedTracks.set(name, track);
|
|
841
952
|
console.debug(
|
|
842
|
-
|
|
843
|
-
track.kind
|
|
953
|
+
`[GPUMachineClient] Track "${name}" published successfully`
|
|
844
954
|
);
|
|
845
955
|
} catch (error) {
|
|
846
|
-
console.error(
|
|
956
|
+
console.error(
|
|
957
|
+
`[GPUMachineClient] Failed to publish track "${name}":`,
|
|
958
|
+
error
|
|
959
|
+
);
|
|
847
960
|
throw error;
|
|
848
961
|
}
|
|
849
962
|
});
|
|
850
963
|
}
|
|
851
964
|
/**
|
|
852
|
-
* Unpublishes the
|
|
965
|
+
* Unpublishes the track with the given name.
|
|
853
966
|
*/
|
|
854
|
-
unpublishTrack() {
|
|
967
|
+
unpublishTrack(name) {
|
|
855
968
|
return __async(this, null, function* () {
|
|
856
|
-
|
|
969
|
+
const entry = this.transceiverMap.get(name);
|
|
970
|
+
if (!(entry == null ? void 0 : entry.transceiver) || !this.publishedTracks.has(name)) return;
|
|
857
971
|
try {
|
|
858
|
-
yield
|
|
859
|
-
console.debug(
|
|
972
|
+
yield entry.transceiver.sender.replaceTrack(null);
|
|
973
|
+
console.debug(
|
|
974
|
+
`[GPUMachineClient] Track "${name}" unpublished successfully`
|
|
975
|
+
);
|
|
860
976
|
} catch (error) {
|
|
861
|
-
console.error(
|
|
977
|
+
console.error(
|
|
978
|
+
`[GPUMachineClient] Failed to unpublish track "${name}":`,
|
|
979
|
+
error
|
|
980
|
+
);
|
|
862
981
|
throw error;
|
|
863
982
|
} finally {
|
|
864
|
-
this.
|
|
983
|
+
this.publishedTracks.delete(name);
|
|
865
984
|
}
|
|
866
985
|
});
|
|
867
986
|
}
|
|
868
987
|
/**
|
|
869
|
-
* Returns the currently published track.
|
|
988
|
+
* Returns the currently published track for the given name.
|
|
870
989
|
*/
|
|
871
|
-
getPublishedTrack() {
|
|
872
|
-
return this.
|
|
990
|
+
getPublishedTrack(name) {
|
|
991
|
+
return this.publishedTracks.get(name);
|
|
873
992
|
}
|
|
874
993
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
875
994
|
// Getters
|
|
@@ -940,6 +1059,12 @@ var GPUMachineClient = class {
|
|
|
940
1059
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
941
1060
|
// Private Helpers
|
|
942
1061
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1062
|
+
checkFullyConnected() {
|
|
1063
|
+
if (this.peerConnected && this.dataChannelOpen) {
|
|
1064
|
+
this.setStatus("connected");
|
|
1065
|
+
this.startStatsPolling();
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
943
1068
|
setStatus(newStatus) {
|
|
944
1069
|
if (this.status !== newStatus) {
|
|
945
1070
|
this.status = newStatus;
|
|
@@ -955,14 +1080,16 @@ var GPUMachineClient = class {
|
|
|
955
1080
|
if (state) {
|
|
956
1081
|
switch (state) {
|
|
957
1082
|
case "connected":
|
|
958
|
-
this.
|
|
959
|
-
this.
|
|
1083
|
+
this.peerConnected = true;
|
|
1084
|
+
this.checkFullyConnected();
|
|
960
1085
|
break;
|
|
961
1086
|
case "disconnected":
|
|
962
1087
|
case "closed":
|
|
1088
|
+
this.peerConnected = false;
|
|
963
1089
|
this.setStatus("disconnected");
|
|
964
1090
|
break;
|
|
965
1091
|
case "failed":
|
|
1092
|
+
this.peerConnected = false;
|
|
966
1093
|
this.setStatus("error");
|
|
967
1094
|
break;
|
|
968
1095
|
}
|
|
@@ -970,9 +1097,13 @@ var GPUMachineClient = class {
|
|
|
970
1097
|
};
|
|
971
1098
|
this.peerConnection.ontrack = (event) => {
|
|
972
1099
|
var _a;
|
|
973
|
-
|
|
1100
|
+
const mid = event.transceiver.mid;
|
|
1101
|
+
const trackName = mid != null ? mid : `unknown-${event.track.id}`;
|
|
1102
|
+
console.debug(
|
|
1103
|
+
`[GPUMachineClient] Track received: "${trackName}" (${event.track.kind}, mid=${mid})`
|
|
1104
|
+
);
|
|
974
1105
|
const stream = (_a = event.streams[0]) != null ? _a : new MediaStream([event.track]);
|
|
975
|
-
this.emit("trackReceived", event.track, stream);
|
|
1106
|
+
this.emit("trackReceived", trackName, event.track, stream);
|
|
976
1107
|
};
|
|
977
1108
|
this.peerConnection.onicecandidate = (event) => {
|
|
978
1109
|
if (event.candidate) {
|
|
@@ -992,10 +1123,13 @@ var GPUMachineClient = class {
|
|
|
992
1123
|
if (!this.dataChannel) return;
|
|
993
1124
|
this.dataChannel.onopen = () => {
|
|
994
1125
|
console.debug("[GPUMachineClient] Data channel open");
|
|
1126
|
+
this.dataChannelOpen = true;
|
|
995
1127
|
this.startPing();
|
|
1128
|
+
this.checkFullyConnected();
|
|
996
1129
|
};
|
|
997
1130
|
this.dataChannel.onclose = () => {
|
|
998
1131
|
console.debug("[GPUMachineClient] Data channel closed");
|
|
1132
|
+
this.dataChannelOpen = false;
|
|
999
1133
|
this.stopPing();
|
|
1000
1134
|
};
|
|
1001
1135
|
this.dataChannel.onerror = (error) => {
|
|
@@ -1029,10 +1163,29 @@ var GPUMachineClient = class {
|
|
|
1029
1163
|
var import_zod2 = require("zod");
|
|
1030
1164
|
var LOCAL_COORDINATOR_URL = "http://localhost:8080";
|
|
1031
1165
|
var PROD_COORDINATOR_URL = "https://api.reactor.inc";
|
|
1166
|
+
var TrackConfigSchema = import_zod2.z.object({
|
|
1167
|
+
name: import_zod2.z.string(),
|
|
1168
|
+
kind: import_zod2.z.enum(["audio", "video"])
|
|
1169
|
+
});
|
|
1032
1170
|
var OptionsSchema = import_zod2.z.object({
|
|
1033
1171
|
coordinatorUrl: import_zod2.z.string().default(PROD_COORDINATOR_URL),
|
|
1034
1172
|
modelName: import_zod2.z.string(),
|
|
1035
|
-
local: import_zod2.z.boolean().default(false)
|
|
1173
|
+
local: import_zod2.z.boolean().default(false),
|
|
1174
|
+
/**
|
|
1175
|
+
* Tracks the client **RECEIVES** from the model (model → client).
|
|
1176
|
+
* Each entry produces a `recvonly` transceiver.
|
|
1177
|
+
* Names must be unique across both `receive` and `send`.
|
|
1178
|
+
*
|
|
1179
|
+
* When omitted, defaults to a single video track named `"main_video"`.
|
|
1180
|
+
* Pass an explicit empty array to opt out of the default.
|
|
1181
|
+
*/
|
|
1182
|
+
receive: import_zod2.z.array(TrackConfigSchema).default([{ name: "main_video", kind: "video" }]),
|
|
1183
|
+
/**
|
|
1184
|
+
* Tracks the client **SENDS** to the model (client → model).
|
|
1185
|
+
* Each entry produces a `sendonly` transceiver.
|
|
1186
|
+
* Names must be unique across both `receive` and `send`.
|
|
1187
|
+
*/
|
|
1188
|
+
send: import_zod2.z.array(TrackConfigSchema).default([])
|
|
1036
1189
|
});
|
|
1037
1190
|
var Reactor = class {
|
|
1038
1191
|
constructor(options) {
|
|
@@ -1043,7 +1196,9 @@ var Reactor = class {
|
|
|
1043
1196
|
this.coordinatorUrl = validatedOptions.coordinatorUrl;
|
|
1044
1197
|
this.model = validatedOptions.modelName;
|
|
1045
1198
|
this.local = validatedOptions.local;
|
|
1046
|
-
|
|
1199
|
+
this.receive = validatedOptions.receive;
|
|
1200
|
+
this.send = validatedOptions.send;
|
|
1201
|
+
if (this.local && options.coordinatorUrl === void 0) {
|
|
1047
1202
|
this.coordinatorUrl = LOCAL_COORDINATOR_URL;
|
|
1048
1203
|
}
|
|
1049
1204
|
}
|
|
@@ -1063,13 +1218,11 @@ var Reactor = class {
|
|
|
1063
1218
|
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
|
|
1064
1219
|
}
|
|
1065
1220
|
/**
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
* @param command The command name
|
|
1221
|
+
* Sends a command to the model via the data channel.
|
|
1222
|
+
*
|
|
1223
|
+
* @param command The command name.
|
|
1069
1224
|
* @param data The command payload.
|
|
1070
|
-
* @param scope
|
|
1071
|
-
* "runtime" for platform-level messages (e.g. requestCapabilities).
|
|
1072
|
-
* @throws Error if not in ready state
|
|
1225
|
+
* @param scope "application" (default) for model commands, "runtime" for platform messages.
|
|
1073
1226
|
*/
|
|
1074
1227
|
sendCommand(command, data, scope = "application") {
|
|
1075
1228
|
return __async(this, null, function* () {
|
|
@@ -1093,24 +1246,27 @@ var Reactor = class {
|
|
|
1093
1246
|
});
|
|
1094
1247
|
}
|
|
1095
1248
|
/**
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1249
|
+
* Publishes a MediaStreamTrack to a named send track.
|
|
1250
|
+
*
|
|
1251
|
+
* @param name The declared send track name (e.g. "webcam").
|
|
1252
|
+
* @param track The MediaStreamTrack to publish.
|
|
1098
1253
|
*/
|
|
1099
|
-
publishTrack(track) {
|
|
1254
|
+
publishTrack(name, track) {
|
|
1100
1255
|
return __async(this, null, function* () {
|
|
1101
1256
|
var _a;
|
|
1102
1257
|
if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
|
|
1103
|
-
|
|
1104
|
-
|
|
1258
|
+
console.warn(
|
|
1259
|
+
`[Reactor] Cannot publish track "${name}", status is ${this.status}`
|
|
1260
|
+
);
|
|
1105
1261
|
return;
|
|
1106
1262
|
}
|
|
1107
1263
|
try {
|
|
1108
|
-
yield (_a = this.machineClient) == null ? void 0 : _a.publishTrack(track);
|
|
1264
|
+
yield (_a = this.machineClient) == null ? void 0 : _a.publishTrack(name, track);
|
|
1109
1265
|
} catch (error) {
|
|
1110
|
-
console.error(
|
|
1266
|
+
console.error(`[Reactor] Failed to publish track "${name}":`, error);
|
|
1111
1267
|
this.createError(
|
|
1112
1268
|
"TRACK_PUBLISH_FAILED",
|
|
1113
|
-
`Failed to publish track: ${error}`,
|
|
1269
|
+
`Failed to publish track "${name}": ${error}`,
|
|
1114
1270
|
"gpu",
|
|
1115
1271
|
true
|
|
1116
1272
|
);
|
|
@@ -1118,18 +1274,20 @@ var Reactor = class {
|
|
|
1118
1274
|
});
|
|
1119
1275
|
}
|
|
1120
1276
|
/**
|
|
1121
|
-
*
|
|
1277
|
+
* Unpublishes the track with the given name.
|
|
1278
|
+
*
|
|
1279
|
+
* @param name The declared send track name to unpublish.
|
|
1122
1280
|
*/
|
|
1123
|
-
unpublishTrack() {
|
|
1281
|
+
unpublishTrack(name) {
|
|
1124
1282
|
return __async(this, null, function* () {
|
|
1125
1283
|
var _a;
|
|
1126
1284
|
try {
|
|
1127
|
-
yield (_a = this.machineClient) == null ? void 0 : _a.unpublishTrack();
|
|
1285
|
+
yield (_a = this.machineClient) == null ? void 0 : _a.unpublishTrack(name);
|
|
1128
1286
|
} catch (error) {
|
|
1129
|
-
console.error(
|
|
1287
|
+
console.error(`[Reactor] Failed to unpublish track "${name}":`, error);
|
|
1130
1288
|
this.createError(
|
|
1131
1289
|
"TRACK_UNPUBLISH_FAILED",
|
|
1132
|
-
`Failed to unpublish track: ${error}`,
|
|
1290
|
+
`Failed to unpublish track "${name}": ${error}`,
|
|
1133
1291
|
"gpu",
|
|
1134
1292
|
true
|
|
1135
1293
|
);
|
|
@@ -1156,7 +1314,10 @@ var Reactor = class {
|
|
|
1156
1314
|
this.machineClient = new GPUMachineClient({ iceServers });
|
|
1157
1315
|
this.setupMachineClientHandlers();
|
|
1158
1316
|
}
|
|
1159
|
-
const sdpOffer = yield this.machineClient.createOffer(
|
|
1317
|
+
const sdpOffer = yield this.machineClient.createOffer({
|
|
1318
|
+
send: this.send,
|
|
1319
|
+
receive: this.receive
|
|
1320
|
+
});
|
|
1160
1321
|
try {
|
|
1161
1322
|
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
1162
1323
|
this.sessionId,
|
|
@@ -1211,7 +1372,10 @@ var Reactor = class {
|
|
|
1211
1372
|
const iceServers = yield this.coordinatorClient.getIceServers();
|
|
1212
1373
|
this.machineClient = new GPUMachineClient({ iceServers });
|
|
1213
1374
|
this.setupMachineClientHandlers();
|
|
1214
|
-
const sdpOffer = yield this.machineClient.createOffer(
|
|
1375
|
+
const sdpOffer = yield this.machineClient.createOffer({
|
|
1376
|
+
send: this.send,
|
|
1377
|
+
receive: this.receive
|
|
1378
|
+
});
|
|
1215
1379
|
const sessionId = yield this.coordinatorClient.createSession(sdpOffer);
|
|
1216
1380
|
this.setSessionId(sessionId);
|
|
1217
1381
|
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
@@ -1273,8 +1437,8 @@ var Reactor = class {
|
|
|
1273
1437
|
});
|
|
1274
1438
|
this.machineClient.on(
|
|
1275
1439
|
"trackReceived",
|
|
1276
|
-
(track, stream) => {
|
|
1277
|
-
this.emit("
|
|
1440
|
+
(name, track, stream) => {
|
|
1441
|
+
this.emit("trackReceived", name, track, stream);
|
|
1278
1442
|
}
|
|
1279
1443
|
);
|
|
1280
1444
|
this.machineClient.on("statsUpdate", (stats) => {
|
|
@@ -1292,7 +1456,11 @@ var Reactor = class {
|
|
|
1292
1456
|
return;
|
|
1293
1457
|
}
|
|
1294
1458
|
if (this.coordinatorClient && !recoverable) {
|
|
1295
|
-
|
|
1459
|
+
try {
|
|
1460
|
+
yield this.coordinatorClient.terminateSession();
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
console.error("[Reactor] Error terminating session:", error);
|
|
1463
|
+
}
|
|
1296
1464
|
this.coordinatorClient = void 0;
|
|
1297
1465
|
}
|
|
1298
1466
|
if (this.machineClient) {
|
|
@@ -1397,10 +1565,9 @@ var ReactorContext = (0, import_react2.createContext)(
|
|
|
1397
1565
|
);
|
|
1398
1566
|
var defaultInitState = {
|
|
1399
1567
|
status: "disconnected",
|
|
1400
|
-
|
|
1568
|
+
tracks: {},
|
|
1401
1569
|
lastError: void 0,
|
|
1402
1570
|
sessionExpiration: void 0,
|
|
1403
|
-
insecureApiKey: void 0,
|
|
1404
1571
|
jwtToken: void 0,
|
|
1405
1572
|
sessionId: void 0
|
|
1406
1573
|
};
|
|
@@ -1421,7 +1588,11 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1421
1588
|
oldStatus: get().status,
|
|
1422
1589
|
newStatus
|
|
1423
1590
|
});
|
|
1424
|
-
|
|
1591
|
+
if (newStatus === "disconnected") {
|
|
1592
|
+
set({ status: newStatus, tracks: {} });
|
|
1593
|
+
} else {
|
|
1594
|
+
set({ status: newStatus });
|
|
1595
|
+
}
|
|
1425
1596
|
});
|
|
1426
1597
|
reactor.on(
|
|
1427
1598
|
"sessionExpirationChanged",
|
|
@@ -1433,13 +1604,13 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1433
1604
|
set({ sessionExpiration: newSessionExpiration });
|
|
1434
1605
|
}
|
|
1435
1606
|
);
|
|
1436
|
-
reactor.on("
|
|
1437
|
-
console.debug("[ReactorStore]
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1607
|
+
reactor.on("trackReceived", (name, track) => {
|
|
1608
|
+
console.debug("[ReactorStore] Track received", {
|
|
1609
|
+
name,
|
|
1610
|
+
kind: track.kind,
|
|
1611
|
+
id: track.id
|
|
1441
1612
|
});
|
|
1442
|
-
set({
|
|
1613
|
+
set({ tracks: __spreadProps(__spreadValues({}, get().tracks), { [name]: track }) });
|
|
1443
1614
|
});
|
|
1444
1615
|
reactor.on("error", (error) => {
|
|
1445
1616
|
console.debug("[ReactorStore] Error occurred", error);
|
|
@@ -1503,27 +1674,31 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1503
1674
|
throw error;
|
|
1504
1675
|
}
|
|
1505
1676
|
}),
|
|
1506
|
-
|
|
1507
|
-
console.debug(
|
|
1677
|
+
publish: (name, track) => __async(null, null, function* () {
|
|
1678
|
+
console.debug(`[ReactorStore] Publishing track "${name}"`);
|
|
1508
1679
|
try {
|
|
1509
|
-
yield get().internal.reactor.publishTrack(
|
|
1510
|
-
console.debug(
|
|
1680
|
+
yield get().internal.reactor.publishTrack(name, track);
|
|
1681
|
+
console.debug(
|
|
1682
|
+
`[ReactorStore] Track "${name}" published successfully`
|
|
1683
|
+
);
|
|
1511
1684
|
} catch (error) {
|
|
1512
1685
|
console.error(
|
|
1513
|
-
|
|
1686
|
+
`[ReactorStore] Failed to publish track "${name}":`,
|
|
1514
1687
|
error
|
|
1515
1688
|
);
|
|
1516
1689
|
throw error;
|
|
1517
1690
|
}
|
|
1518
1691
|
}),
|
|
1519
|
-
|
|
1520
|
-
console.debug(
|
|
1692
|
+
unpublish: (name) => __async(null, null, function* () {
|
|
1693
|
+
console.debug(`[ReactorStore] Unpublishing track "${name}"`);
|
|
1521
1694
|
try {
|
|
1522
|
-
yield get().internal.reactor.unpublishTrack();
|
|
1523
|
-
console.debug(
|
|
1695
|
+
yield get().internal.reactor.unpublishTrack(name);
|
|
1696
|
+
console.debug(
|
|
1697
|
+
`[ReactorStore] Track "${name}" unpublished successfully`
|
|
1698
|
+
);
|
|
1524
1699
|
} catch (error) {
|
|
1525
1700
|
console.error(
|
|
1526
|
-
|
|
1701
|
+
`[ReactorStore] Failed to unpublish track "${name}":`,
|
|
1527
1702
|
error
|
|
1528
1703
|
);
|
|
1529
1704
|
throw error;
|
|
@@ -1569,7 +1744,7 @@ function ReactorProvider(_a) {
|
|
|
1569
1744
|
console.debug("[ReactorProvider] Reactor store created successfully");
|
|
1570
1745
|
}
|
|
1571
1746
|
const _a2 = connectOptions != null ? connectOptions : {}, { autoConnect = false } = _a2, pollingOptions = __objRest(_a2, ["autoConnect"]);
|
|
1572
|
-
const { coordinatorUrl, modelName, local } = props;
|
|
1747
|
+
const { coordinatorUrl, modelName, local, receive, send } = props;
|
|
1573
1748
|
const maxAttempts = pollingOptions.maxAttempts;
|
|
1574
1749
|
(0, import_react3.useEffect)(() => {
|
|
1575
1750
|
const handleBeforeUnload = () => {
|
|
@@ -1625,6 +1800,8 @@ function ReactorProvider(_a) {
|
|
|
1625
1800
|
coordinatorUrl,
|
|
1626
1801
|
modelName,
|
|
1627
1802
|
local,
|
|
1803
|
+
receive,
|
|
1804
|
+
send,
|
|
1628
1805
|
jwtToken
|
|
1629
1806
|
})
|
|
1630
1807
|
);
|
|
@@ -1651,7 +1828,16 @@ function ReactorProvider(_a) {
|
|
|
1651
1828
|
console.error("[ReactorProvider] Failed to disconnect:", error);
|
|
1652
1829
|
});
|
|
1653
1830
|
};
|
|
1654
|
-
}, [
|
|
1831
|
+
}, [
|
|
1832
|
+
coordinatorUrl,
|
|
1833
|
+
modelName,
|
|
1834
|
+
autoConnect,
|
|
1835
|
+
local,
|
|
1836
|
+
receive,
|
|
1837
|
+
send,
|
|
1838
|
+
jwtToken,
|
|
1839
|
+
maxAttempts
|
|
1840
|
+
]);
|
|
1655
1841
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReactorContext.Provider, { value: storeRef.current, children });
|
|
1656
1842
|
}
|
|
1657
1843
|
function useReactorStore(selector) {
|
|
@@ -1720,47 +1906,60 @@ function useStats() {
|
|
|
1720
1906
|
var import_react5 = require("react");
|
|
1721
1907
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1722
1908
|
function ReactorView({
|
|
1909
|
+
track = "main_video",
|
|
1910
|
+
audioTrack,
|
|
1723
1911
|
width,
|
|
1724
1912
|
height,
|
|
1725
1913
|
className,
|
|
1726
1914
|
style,
|
|
1727
|
-
videoObjectFit = "contain"
|
|
1915
|
+
videoObjectFit = "contain",
|
|
1916
|
+
muted = true
|
|
1728
1917
|
}) {
|
|
1729
|
-
const {
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1918
|
+
const { videoMediaTrack, audioMediaTrack, status } = useReactor((state) => {
|
|
1919
|
+
var _a, _b;
|
|
1920
|
+
return {
|
|
1921
|
+
videoMediaTrack: (_a = state.tracks[track]) != null ? _a : null,
|
|
1922
|
+
audioMediaTrack: audioTrack ? (_b = state.tracks[audioTrack]) != null ? _b : null : null,
|
|
1923
|
+
status: state.status
|
|
1924
|
+
};
|
|
1925
|
+
});
|
|
1733
1926
|
const videoRef = (0, import_react5.useRef)(null);
|
|
1927
|
+
const mediaStream = (0, import_react5.useMemo)(() => {
|
|
1928
|
+
const tracks = [];
|
|
1929
|
+
if (videoMediaTrack) tracks.push(videoMediaTrack);
|
|
1930
|
+
if (audioMediaTrack) tracks.push(audioMediaTrack);
|
|
1931
|
+
if (tracks.length === 0) return null;
|
|
1932
|
+
return new MediaStream(tracks);
|
|
1933
|
+
}, [videoMediaTrack, audioMediaTrack]);
|
|
1734
1934
|
(0, import_react5.useEffect)(() => {
|
|
1735
|
-
console.debug("[ReactorView]
|
|
1935
|
+
console.debug("[ReactorView] Media track effect triggered", {
|
|
1936
|
+
track,
|
|
1736
1937
|
hasVideoElement: !!videoRef.current,
|
|
1737
|
-
hasVideoTrack: !!
|
|
1738
|
-
|
|
1938
|
+
hasVideoTrack: !!videoMediaTrack,
|
|
1939
|
+
hasAudioTrack: !!audioMediaTrack
|
|
1739
1940
|
});
|
|
1740
|
-
if (videoRef.current &&
|
|
1741
|
-
console.debug("[ReactorView] Attaching
|
|
1941
|
+
if (videoRef.current && mediaStream) {
|
|
1942
|
+
console.debug("[ReactorView] Attaching media stream to element");
|
|
1742
1943
|
try {
|
|
1743
|
-
|
|
1744
|
-
videoRef.current.srcObject = stream;
|
|
1944
|
+
videoRef.current.srcObject = mediaStream;
|
|
1745
1945
|
videoRef.current.play().catch((e) => {
|
|
1746
1946
|
console.warn("[ReactorView] Auto-play failed:", e);
|
|
1747
1947
|
});
|
|
1748
|
-
console.debug("[ReactorView]
|
|
1948
|
+
console.debug("[ReactorView] Media stream attached successfully");
|
|
1749
1949
|
} catch (error) {
|
|
1750
|
-
console.error("[ReactorView] Failed to attach
|
|
1950
|
+
console.error("[ReactorView] Failed to attach media stream:", error);
|
|
1751
1951
|
}
|
|
1752
1952
|
return () => {
|
|
1753
|
-
console.debug("[ReactorView] Detaching
|
|
1953
|
+
console.debug("[ReactorView] Detaching media stream from element");
|
|
1754
1954
|
if (videoRef.current) {
|
|
1755
1955
|
videoRef.current.srcObject = null;
|
|
1756
|
-
console.debug("[ReactorView] Video track detached successfully");
|
|
1757
1956
|
}
|
|
1758
1957
|
};
|
|
1759
1958
|
} else {
|
|
1760
|
-
console.debug("[ReactorView] No
|
|
1959
|
+
console.debug("[ReactorView] No tracks or element to attach");
|
|
1761
1960
|
}
|
|
1762
|
-
}, [
|
|
1763
|
-
const showPlaceholder = !
|
|
1961
|
+
}, [mediaStream]);
|
|
1962
|
+
const showPlaceholder = !videoMediaTrack;
|
|
1764
1963
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1765
1964
|
"div",
|
|
1766
1965
|
{
|
|
@@ -1780,7 +1979,7 @@ function ReactorView({
|
|
|
1780
1979
|
objectFit: videoObjectFit,
|
|
1781
1980
|
display: showPlaceholder ? "none" : "block"
|
|
1782
1981
|
},
|
|
1783
|
-
muted
|
|
1982
|
+
muted,
|
|
1784
1983
|
playsInline: true
|
|
1785
1984
|
}
|
|
1786
1985
|
),
|
|
@@ -2268,6 +2467,7 @@ function ReactorController({
|
|
|
2268
2467
|
var import_react7 = require("react");
|
|
2269
2468
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
2270
2469
|
function WebcamStream({
|
|
2470
|
+
track,
|
|
2271
2471
|
className,
|
|
2272
2472
|
style,
|
|
2273
2473
|
videoConstraints = {
|
|
@@ -2280,10 +2480,10 @@ function WebcamStream({
|
|
|
2280
2480
|
const [stream, setStream] = (0, import_react7.useState)(null);
|
|
2281
2481
|
const [isPublishing, setIsPublishing] = (0, import_react7.useState)(false);
|
|
2282
2482
|
const [permissionDenied, setPermissionDenied] = (0, import_react7.useState)(false);
|
|
2283
|
-
const { status,
|
|
2483
|
+
const { status, publish, unpublish, reactor } = useReactor((state) => ({
|
|
2284
2484
|
status: state.status,
|
|
2285
|
-
|
|
2286
|
-
|
|
2485
|
+
publish: state.publish,
|
|
2486
|
+
unpublish: state.unpublish,
|
|
2287
2487
|
reactor: state.internal.reactor
|
|
2288
2488
|
}));
|
|
2289
2489
|
const videoRef = (0, import_react7.useRef)(null);
|
|
@@ -2308,15 +2508,15 @@ function WebcamStream({
|
|
|
2308
2508
|
const stopWebcam = () => __async(null, null, function* () {
|
|
2309
2509
|
console.debug("[WebcamPublisher] Stopping webcam");
|
|
2310
2510
|
try {
|
|
2311
|
-
yield
|
|
2511
|
+
yield unpublish(track);
|
|
2312
2512
|
console.debug("[WebcamPublisher] Unpublished before stopping");
|
|
2313
2513
|
} catch (err) {
|
|
2314
2514
|
console.error("[WebcamPublisher] Error unpublishing before stop:", err);
|
|
2315
2515
|
}
|
|
2316
2516
|
setIsPublishing(false);
|
|
2317
|
-
stream == null ? void 0 : stream.getTracks().forEach((
|
|
2318
|
-
|
|
2319
|
-
console.debug("[WebcamPublisher] Stopped track:",
|
|
2517
|
+
stream == null ? void 0 : stream.getTracks().forEach((t) => {
|
|
2518
|
+
t.stop();
|
|
2519
|
+
console.debug("[WebcamPublisher] Stopped track:", t.kind);
|
|
2320
2520
|
});
|
|
2321
2521
|
setStream(null);
|
|
2322
2522
|
console.debug("[WebcamPublisher] Webcam stopped");
|
|
@@ -2346,28 +2546,31 @@ function WebcamStream({
|
|
|
2346
2546
|
console.debug(
|
|
2347
2547
|
"[WebcamPublisher] Reactor ready, auto-publishing webcam stream"
|
|
2348
2548
|
);
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2549
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
2550
|
+
if (videoTrack) {
|
|
2551
|
+
publish(track, videoTrack).then(() => {
|
|
2552
|
+
console.debug("[WebcamPublisher] Auto-publish successful");
|
|
2553
|
+
setIsPublishing(true);
|
|
2554
|
+
}).catch((err) => {
|
|
2555
|
+
console.error("[WebcamPublisher] Auto-publish failed:", err);
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2355
2558
|
} else if (status !== "ready" && isPublishing) {
|
|
2356
2559
|
console.debug("[WebcamPublisher] Reactor not ready, auto-unpublishing");
|
|
2357
|
-
|
|
2560
|
+
unpublish(track).then(() => {
|
|
2358
2561
|
console.debug("[WebcamPublisher] Auto-unpublish successful");
|
|
2359
2562
|
setIsPublishing(false);
|
|
2360
2563
|
}).catch((err) => {
|
|
2361
2564
|
console.error("[WebcamPublisher] Auto-unpublish failed:", err);
|
|
2362
2565
|
});
|
|
2363
2566
|
}
|
|
2364
|
-
}, [status, stream, isPublishing,
|
|
2567
|
+
}, [status, stream, isPublishing, publish, unpublish, track]);
|
|
2365
2568
|
(0, import_react7.useEffect)(() => {
|
|
2366
2569
|
const handleError = (error) => {
|
|
2367
2570
|
console.debug("[WebcamPublisher] Received error event:", error);
|
|
2368
|
-
if (error.code === "
|
|
2571
|
+
if (error.code === "TRACK_PUBLISH_FAILED") {
|
|
2369
2572
|
console.debug(
|
|
2370
|
-
"[WebcamPublisher]
|
|
2573
|
+
"[WebcamPublisher] Track publish failed, resetting isPublishing state"
|
|
2371
2574
|
);
|
|
2372
2575
|
setIsPublishing(false);
|
|
2373
2576
|
}
|
|
@@ -2483,11 +2686,13 @@ function fetchInsecureJwtToken(_0) {
|
|
|
2483
2686
|
ReactorProvider,
|
|
2484
2687
|
ReactorView,
|
|
2485
2688
|
WebcamStream,
|
|
2689
|
+
audio,
|
|
2486
2690
|
fetchInsecureJwtToken,
|
|
2487
2691
|
useReactor,
|
|
2488
2692
|
useReactorInternalMessage,
|
|
2489
2693
|
useReactorMessage,
|
|
2490
2694
|
useReactorStore,
|
|
2491
|
-
useStats
|
|
2695
|
+
useStats,
|
|
2696
|
+
video
|
|
2492
2697
|
});
|
|
2493
2698
|
//# sourceMappingURL=index.js.map
|