@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.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) {
|
|
@@ -632,6 +681,10 @@ var GPUMachineClient = class {
|
|
|
632
681
|
constructor(config) {
|
|
633
682
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
634
683
|
this.status = "disconnected";
|
|
684
|
+
this.transceiverMap = /* @__PURE__ */ new Map();
|
|
685
|
+
this.publishedTracks = /* @__PURE__ */ new Map();
|
|
686
|
+
this.peerConnected = false;
|
|
687
|
+
this.dataChannelOpen = false;
|
|
635
688
|
this.config = config;
|
|
636
689
|
}
|
|
637
690
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -655,10 +708,18 @@ var GPUMachineClient = class {
|
|
|
655
708
|
// SDP & Connection
|
|
656
709
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
657
710
|
/**
|
|
658
|
-
* 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).
|
|
659
720
|
* Must be called before connect().
|
|
660
721
|
*/
|
|
661
|
-
createOffer() {
|
|
722
|
+
createOffer(tracks) {
|
|
662
723
|
return __async(this, null, function* () {
|
|
663
724
|
if (!this.peerConnection) {
|
|
664
725
|
this.peerConnection = createPeerConnection(this.config);
|
|
@@ -669,14 +730,54 @@ var GPUMachineClient = class {
|
|
|
669
730
|
this.config.dataChannelLabel
|
|
670
731
|
);
|
|
671
732
|
this.setupDataChannelHandlers();
|
|
672
|
-
this.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
);
|
|
677
751
|
return offer;
|
|
678
752
|
});
|
|
679
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
|
+
}
|
|
680
781
|
/**
|
|
681
782
|
* Connects to the GPU machine using the provided SDP answer.
|
|
682
783
|
* createOffer() must be called first.
|
|
@@ -712,8 +813,8 @@ var GPUMachineClient = class {
|
|
|
712
813
|
return __async(this, null, function* () {
|
|
713
814
|
this.stopPing();
|
|
714
815
|
this.stopStatsPolling();
|
|
715
|
-
|
|
716
|
-
yield this.unpublishTrack();
|
|
816
|
+
for (const name of Array.from(this.publishedTracks.keys())) {
|
|
817
|
+
yield this.unpublishTrack(name);
|
|
717
818
|
}
|
|
718
819
|
if (this.dataChannel) {
|
|
719
820
|
this.dataChannel.close();
|
|
@@ -723,7 +824,9 @@ var GPUMachineClient = class {
|
|
|
723
824
|
closePeerConnection(this.peerConnection);
|
|
724
825
|
this.peerConnection = void 0;
|
|
725
826
|
}
|
|
726
|
-
this.
|
|
827
|
+
this.transceiverMap.clear();
|
|
828
|
+
this.peerConnected = false;
|
|
829
|
+
this.dataChannelOpen = false;
|
|
727
830
|
this.setStatus("disconnected");
|
|
728
831
|
console.debug("[GPUMachineClient] Disconnected");
|
|
729
832
|
});
|
|
@@ -751,7 +854,7 @@ var GPUMachineClient = class {
|
|
|
751
854
|
/**
|
|
752
855
|
* Sends a command to the GPU machine via the data channel.
|
|
753
856
|
* @param command The command to send
|
|
754
|
-
* @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.
|
|
755
858
|
* @param scope The message scope – "application" (default) for model commands, "runtime" for platform-level messages.
|
|
756
859
|
*/
|
|
757
860
|
sendCommand(command, data, scope = "application") {
|
|
@@ -768,63 +871,77 @@ var GPUMachineClient = class {
|
|
|
768
871
|
// Track Publishing
|
|
769
872
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
770
873
|
/**
|
|
771
|
-
* Publishes a
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
* @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.
|
|
775
878
|
*/
|
|
776
|
-
publishTrack(track) {
|
|
879
|
+
publishTrack(name, track) {
|
|
777
880
|
return __async(this, null, function* () {
|
|
778
881
|
if (!this.peerConnection) {
|
|
779
882
|
throw new Error(
|
|
780
|
-
|
|
883
|
+
`[GPUMachineClient] Cannot publish track "${name}" - not initialized`
|
|
781
884
|
);
|
|
782
885
|
}
|
|
783
886
|
if (this.status !== "connected") {
|
|
784
887
|
throw new Error(
|
|
785
|
-
|
|
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?)`
|
|
786
895
|
);
|
|
787
896
|
}
|
|
788
|
-
if (
|
|
897
|
+
if (entry.direction === "recvonly") {
|
|
789
898
|
throw new Error(
|
|
790
|
-
|
|
899
|
+
`[GPUMachineClient] Cannot publish track "${name}" - transceiver is recvonly`
|
|
791
900
|
);
|
|
792
901
|
}
|
|
793
902
|
try {
|
|
794
|
-
yield
|
|
795
|
-
this.
|
|
903
|
+
yield entry.transceiver.sender.replaceTrack(track);
|
|
904
|
+
this.publishedTracks.set(name, track);
|
|
796
905
|
console.debug(
|
|
797
|
-
|
|
798
|
-
track.kind
|
|
906
|
+
`[GPUMachineClient] Track "${name}" published successfully`
|
|
799
907
|
);
|
|
800
908
|
} catch (error) {
|
|
801
|
-
console.error(
|
|
909
|
+
console.error(
|
|
910
|
+
`[GPUMachineClient] Failed to publish track "${name}":`,
|
|
911
|
+
error
|
|
912
|
+
);
|
|
802
913
|
throw error;
|
|
803
914
|
}
|
|
804
915
|
});
|
|
805
916
|
}
|
|
806
917
|
/**
|
|
807
|
-
* Unpublishes the
|
|
918
|
+
* Unpublishes the track with the given name.
|
|
808
919
|
*/
|
|
809
|
-
unpublishTrack() {
|
|
920
|
+
unpublishTrack(name) {
|
|
810
921
|
return __async(this, null, function* () {
|
|
811
|
-
|
|
922
|
+
const entry = this.transceiverMap.get(name);
|
|
923
|
+
if (!(entry == null ? void 0 : entry.transceiver) || !this.publishedTracks.has(name)) return;
|
|
812
924
|
try {
|
|
813
|
-
yield
|
|
814
|
-
console.debug(
|
|
925
|
+
yield entry.transceiver.sender.replaceTrack(null);
|
|
926
|
+
console.debug(
|
|
927
|
+
`[GPUMachineClient] Track "${name}" unpublished successfully`
|
|
928
|
+
);
|
|
815
929
|
} catch (error) {
|
|
816
|
-
console.error(
|
|
930
|
+
console.error(
|
|
931
|
+
`[GPUMachineClient] Failed to unpublish track "${name}":`,
|
|
932
|
+
error
|
|
933
|
+
);
|
|
817
934
|
throw error;
|
|
818
935
|
} finally {
|
|
819
|
-
this.
|
|
936
|
+
this.publishedTracks.delete(name);
|
|
820
937
|
}
|
|
821
938
|
});
|
|
822
939
|
}
|
|
823
940
|
/**
|
|
824
|
-
* Returns the currently published track.
|
|
941
|
+
* Returns the currently published track for the given name.
|
|
825
942
|
*/
|
|
826
|
-
getPublishedTrack() {
|
|
827
|
-
return this.
|
|
943
|
+
getPublishedTrack(name) {
|
|
944
|
+
return this.publishedTracks.get(name);
|
|
828
945
|
}
|
|
829
946
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
830
947
|
// Getters
|
|
@@ -895,6 +1012,12 @@ var GPUMachineClient = class {
|
|
|
895
1012
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
896
1013
|
// Private Helpers
|
|
897
1014
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1015
|
+
checkFullyConnected() {
|
|
1016
|
+
if (this.peerConnected && this.dataChannelOpen) {
|
|
1017
|
+
this.setStatus("connected");
|
|
1018
|
+
this.startStatsPolling();
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
898
1021
|
setStatus(newStatus) {
|
|
899
1022
|
if (this.status !== newStatus) {
|
|
900
1023
|
this.status = newStatus;
|
|
@@ -910,14 +1033,16 @@ var GPUMachineClient = class {
|
|
|
910
1033
|
if (state) {
|
|
911
1034
|
switch (state) {
|
|
912
1035
|
case "connected":
|
|
913
|
-
this.
|
|
914
|
-
this.
|
|
1036
|
+
this.peerConnected = true;
|
|
1037
|
+
this.checkFullyConnected();
|
|
915
1038
|
break;
|
|
916
1039
|
case "disconnected":
|
|
917
1040
|
case "closed":
|
|
1041
|
+
this.peerConnected = false;
|
|
918
1042
|
this.setStatus("disconnected");
|
|
919
1043
|
break;
|
|
920
1044
|
case "failed":
|
|
1045
|
+
this.peerConnected = false;
|
|
921
1046
|
this.setStatus("error");
|
|
922
1047
|
break;
|
|
923
1048
|
}
|
|
@@ -925,9 +1050,13 @@ var GPUMachineClient = class {
|
|
|
925
1050
|
};
|
|
926
1051
|
this.peerConnection.ontrack = (event) => {
|
|
927
1052
|
var _a;
|
|
928
|
-
|
|
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
|
+
);
|
|
929
1058
|
const stream = (_a = event.streams[0]) != null ? _a : new MediaStream([event.track]);
|
|
930
|
-
this.emit("trackReceived", event.track, stream);
|
|
1059
|
+
this.emit("trackReceived", trackName, event.track, stream);
|
|
931
1060
|
};
|
|
932
1061
|
this.peerConnection.onicecandidate = (event) => {
|
|
933
1062
|
if (event.candidate) {
|
|
@@ -947,10 +1076,13 @@ var GPUMachineClient = class {
|
|
|
947
1076
|
if (!this.dataChannel) return;
|
|
948
1077
|
this.dataChannel.onopen = () => {
|
|
949
1078
|
console.debug("[GPUMachineClient] Data channel open");
|
|
1079
|
+
this.dataChannelOpen = true;
|
|
950
1080
|
this.startPing();
|
|
1081
|
+
this.checkFullyConnected();
|
|
951
1082
|
};
|
|
952
1083
|
this.dataChannel.onclose = () => {
|
|
953
1084
|
console.debug("[GPUMachineClient] Data channel closed");
|
|
1085
|
+
this.dataChannelOpen = false;
|
|
954
1086
|
this.stopPing();
|
|
955
1087
|
};
|
|
956
1088
|
this.dataChannel.onerror = (error) => {
|
|
@@ -984,10 +1116,29 @@ var GPUMachineClient = class {
|
|
|
984
1116
|
import { z as z2 } from "zod";
|
|
985
1117
|
var LOCAL_COORDINATOR_URL = "http://localhost:8080";
|
|
986
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
|
+
});
|
|
987
1123
|
var OptionsSchema = z2.object({
|
|
988
1124
|
coordinatorUrl: z2.string().default(PROD_COORDINATOR_URL),
|
|
989
1125
|
modelName: z2.string(),
|
|
990
|
-
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([])
|
|
991
1142
|
});
|
|
992
1143
|
var Reactor = class {
|
|
993
1144
|
constructor(options) {
|
|
@@ -998,7 +1149,9 @@ var Reactor = class {
|
|
|
998
1149
|
this.coordinatorUrl = validatedOptions.coordinatorUrl;
|
|
999
1150
|
this.model = validatedOptions.modelName;
|
|
1000
1151
|
this.local = validatedOptions.local;
|
|
1001
|
-
|
|
1152
|
+
this.receive = validatedOptions.receive;
|
|
1153
|
+
this.send = validatedOptions.send;
|
|
1154
|
+
if (this.local && options.coordinatorUrl === void 0) {
|
|
1002
1155
|
this.coordinatorUrl = LOCAL_COORDINATOR_URL;
|
|
1003
1156
|
}
|
|
1004
1157
|
}
|
|
@@ -1018,13 +1171,11 @@ var Reactor = class {
|
|
|
1018
1171
|
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
|
|
1019
1172
|
}
|
|
1020
1173
|
/**
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
* @param command The command name
|
|
1174
|
+
* Sends a command to the model via the data channel.
|
|
1175
|
+
*
|
|
1176
|
+
* @param command The command name.
|
|
1024
1177
|
* @param data The command payload.
|
|
1025
|
-
* @param scope
|
|
1026
|
-
* "runtime" for platform-level messages (e.g. requestCapabilities).
|
|
1027
|
-
* @throws Error if not in ready state
|
|
1178
|
+
* @param scope "application" (default) for model commands, "runtime" for platform messages.
|
|
1028
1179
|
*/
|
|
1029
1180
|
sendCommand(command, data, scope = "application") {
|
|
1030
1181
|
return __async(this, null, function* () {
|
|
@@ -1048,24 +1199,27 @@ var Reactor = class {
|
|
|
1048
1199
|
});
|
|
1049
1200
|
}
|
|
1050
1201
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
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.
|
|
1053
1206
|
*/
|
|
1054
|
-
publishTrack(track) {
|
|
1207
|
+
publishTrack(name, track) {
|
|
1055
1208
|
return __async(this, null, function* () {
|
|
1056
1209
|
var _a;
|
|
1057
1210
|
if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
|
|
1058
|
-
|
|
1059
|
-
|
|
1211
|
+
console.warn(
|
|
1212
|
+
`[Reactor] Cannot publish track "${name}", status is ${this.status}`
|
|
1213
|
+
);
|
|
1060
1214
|
return;
|
|
1061
1215
|
}
|
|
1062
1216
|
try {
|
|
1063
|
-
yield (_a = this.machineClient) == null ? void 0 : _a.publishTrack(track);
|
|
1217
|
+
yield (_a = this.machineClient) == null ? void 0 : _a.publishTrack(name, track);
|
|
1064
1218
|
} catch (error) {
|
|
1065
|
-
console.error(
|
|
1219
|
+
console.error(`[Reactor] Failed to publish track "${name}":`, error);
|
|
1066
1220
|
this.createError(
|
|
1067
1221
|
"TRACK_PUBLISH_FAILED",
|
|
1068
|
-
`Failed to publish track: ${error}`,
|
|
1222
|
+
`Failed to publish track "${name}": ${error}`,
|
|
1069
1223
|
"gpu",
|
|
1070
1224
|
true
|
|
1071
1225
|
);
|
|
@@ -1073,18 +1227,20 @@ var Reactor = class {
|
|
|
1073
1227
|
});
|
|
1074
1228
|
}
|
|
1075
1229
|
/**
|
|
1076
|
-
*
|
|
1230
|
+
* Unpublishes the track with the given name.
|
|
1231
|
+
*
|
|
1232
|
+
* @param name The declared send track name to unpublish.
|
|
1077
1233
|
*/
|
|
1078
|
-
unpublishTrack() {
|
|
1234
|
+
unpublishTrack(name) {
|
|
1079
1235
|
return __async(this, null, function* () {
|
|
1080
1236
|
var _a;
|
|
1081
1237
|
try {
|
|
1082
|
-
yield (_a = this.machineClient) == null ? void 0 : _a.unpublishTrack();
|
|
1238
|
+
yield (_a = this.machineClient) == null ? void 0 : _a.unpublishTrack(name);
|
|
1083
1239
|
} catch (error) {
|
|
1084
|
-
console.error(
|
|
1240
|
+
console.error(`[Reactor] Failed to unpublish track "${name}":`, error);
|
|
1085
1241
|
this.createError(
|
|
1086
1242
|
"TRACK_UNPUBLISH_FAILED",
|
|
1087
|
-
`Failed to unpublish track: ${error}`,
|
|
1243
|
+
`Failed to unpublish track "${name}": ${error}`,
|
|
1088
1244
|
"gpu",
|
|
1089
1245
|
true
|
|
1090
1246
|
);
|
|
@@ -1111,7 +1267,10 @@ var Reactor = class {
|
|
|
1111
1267
|
this.machineClient = new GPUMachineClient({ iceServers });
|
|
1112
1268
|
this.setupMachineClientHandlers();
|
|
1113
1269
|
}
|
|
1114
|
-
const sdpOffer = yield this.machineClient.createOffer(
|
|
1270
|
+
const sdpOffer = yield this.machineClient.createOffer({
|
|
1271
|
+
send: this.send,
|
|
1272
|
+
receive: this.receive
|
|
1273
|
+
});
|
|
1115
1274
|
try {
|
|
1116
1275
|
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
1117
1276
|
this.sessionId,
|
|
@@ -1166,7 +1325,10 @@ var Reactor = class {
|
|
|
1166
1325
|
const iceServers = yield this.coordinatorClient.getIceServers();
|
|
1167
1326
|
this.machineClient = new GPUMachineClient({ iceServers });
|
|
1168
1327
|
this.setupMachineClientHandlers();
|
|
1169
|
-
const sdpOffer = yield this.machineClient.createOffer(
|
|
1328
|
+
const sdpOffer = yield this.machineClient.createOffer({
|
|
1329
|
+
send: this.send,
|
|
1330
|
+
receive: this.receive
|
|
1331
|
+
});
|
|
1170
1332
|
const sessionId = yield this.coordinatorClient.createSession(sdpOffer);
|
|
1171
1333
|
this.setSessionId(sessionId);
|
|
1172
1334
|
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
@@ -1228,8 +1390,8 @@ var Reactor = class {
|
|
|
1228
1390
|
});
|
|
1229
1391
|
this.machineClient.on(
|
|
1230
1392
|
"trackReceived",
|
|
1231
|
-
(track, stream) => {
|
|
1232
|
-
this.emit("
|
|
1393
|
+
(name, track, stream) => {
|
|
1394
|
+
this.emit("trackReceived", name, track, stream);
|
|
1233
1395
|
}
|
|
1234
1396
|
);
|
|
1235
1397
|
this.machineClient.on("statsUpdate", (stats) => {
|
|
@@ -1247,7 +1409,11 @@ var Reactor = class {
|
|
|
1247
1409
|
return;
|
|
1248
1410
|
}
|
|
1249
1411
|
if (this.coordinatorClient && !recoverable) {
|
|
1250
|
-
|
|
1412
|
+
try {
|
|
1413
|
+
yield this.coordinatorClient.terminateSession();
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
console.error("[Reactor] Error terminating session:", error);
|
|
1416
|
+
}
|
|
1251
1417
|
this.coordinatorClient = void 0;
|
|
1252
1418
|
}
|
|
1253
1419
|
if (this.machineClient) {
|
|
@@ -1352,10 +1518,9 @@ var ReactorContext = createContext(
|
|
|
1352
1518
|
);
|
|
1353
1519
|
var defaultInitState = {
|
|
1354
1520
|
status: "disconnected",
|
|
1355
|
-
|
|
1521
|
+
tracks: {},
|
|
1356
1522
|
lastError: void 0,
|
|
1357
1523
|
sessionExpiration: void 0,
|
|
1358
|
-
insecureApiKey: void 0,
|
|
1359
1524
|
jwtToken: void 0,
|
|
1360
1525
|
sessionId: void 0
|
|
1361
1526
|
};
|
|
@@ -1376,7 +1541,11 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1376
1541
|
oldStatus: get().status,
|
|
1377
1542
|
newStatus
|
|
1378
1543
|
});
|
|
1379
|
-
|
|
1544
|
+
if (newStatus === "disconnected") {
|
|
1545
|
+
set({ status: newStatus, tracks: {} });
|
|
1546
|
+
} else {
|
|
1547
|
+
set({ status: newStatus });
|
|
1548
|
+
}
|
|
1380
1549
|
});
|
|
1381
1550
|
reactor.on(
|
|
1382
1551
|
"sessionExpirationChanged",
|
|
@@ -1388,13 +1557,13 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1388
1557
|
set({ sessionExpiration: newSessionExpiration });
|
|
1389
1558
|
}
|
|
1390
1559
|
);
|
|
1391
|
-
reactor.on("
|
|
1392
|
-
console.debug("[ReactorStore]
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1560
|
+
reactor.on("trackReceived", (name, track) => {
|
|
1561
|
+
console.debug("[ReactorStore] Track received", {
|
|
1562
|
+
name,
|
|
1563
|
+
kind: track.kind,
|
|
1564
|
+
id: track.id
|
|
1396
1565
|
});
|
|
1397
|
-
set({
|
|
1566
|
+
set({ tracks: __spreadProps(__spreadValues({}, get().tracks), { [name]: track }) });
|
|
1398
1567
|
});
|
|
1399
1568
|
reactor.on("error", (error) => {
|
|
1400
1569
|
console.debug("[ReactorStore] Error occurred", error);
|
|
@@ -1458,27 +1627,31 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
|
1458
1627
|
throw error;
|
|
1459
1628
|
}
|
|
1460
1629
|
}),
|
|
1461
|
-
|
|
1462
|
-
console.debug(
|
|
1630
|
+
publish: (name, track) => __async(null, null, function* () {
|
|
1631
|
+
console.debug(`[ReactorStore] Publishing track "${name}"`);
|
|
1463
1632
|
try {
|
|
1464
|
-
yield get().internal.reactor.publishTrack(
|
|
1465
|
-
console.debug(
|
|
1633
|
+
yield get().internal.reactor.publishTrack(name, track);
|
|
1634
|
+
console.debug(
|
|
1635
|
+
`[ReactorStore] Track "${name}" published successfully`
|
|
1636
|
+
);
|
|
1466
1637
|
} catch (error) {
|
|
1467
1638
|
console.error(
|
|
1468
|
-
|
|
1639
|
+
`[ReactorStore] Failed to publish track "${name}":`,
|
|
1469
1640
|
error
|
|
1470
1641
|
);
|
|
1471
1642
|
throw error;
|
|
1472
1643
|
}
|
|
1473
1644
|
}),
|
|
1474
|
-
|
|
1475
|
-
console.debug(
|
|
1645
|
+
unpublish: (name) => __async(null, null, function* () {
|
|
1646
|
+
console.debug(`[ReactorStore] Unpublishing track "${name}"`);
|
|
1476
1647
|
try {
|
|
1477
|
-
yield get().internal.reactor.unpublishTrack();
|
|
1478
|
-
console.debug(
|
|
1648
|
+
yield get().internal.reactor.unpublishTrack(name);
|
|
1649
|
+
console.debug(
|
|
1650
|
+
`[ReactorStore] Track "${name}" unpublished successfully`
|
|
1651
|
+
);
|
|
1479
1652
|
} catch (error) {
|
|
1480
1653
|
console.error(
|
|
1481
|
-
|
|
1654
|
+
`[ReactorStore] Failed to unpublish track "${name}":`,
|
|
1482
1655
|
error
|
|
1483
1656
|
);
|
|
1484
1657
|
throw error;
|
|
@@ -1524,7 +1697,7 @@ function ReactorProvider(_a) {
|
|
|
1524
1697
|
console.debug("[ReactorProvider] Reactor store created successfully");
|
|
1525
1698
|
}
|
|
1526
1699
|
const _a2 = connectOptions != null ? connectOptions : {}, { autoConnect = false } = _a2, pollingOptions = __objRest(_a2, ["autoConnect"]);
|
|
1527
|
-
const { coordinatorUrl, modelName, local } = props;
|
|
1700
|
+
const { coordinatorUrl, modelName, local, receive, send } = props;
|
|
1528
1701
|
const maxAttempts = pollingOptions.maxAttempts;
|
|
1529
1702
|
useEffect(() => {
|
|
1530
1703
|
const handleBeforeUnload = () => {
|
|
@@ -1580,6 +1753,8 @@ function ReactorProvider(_a) {
|
|
|
1580
1753
|
coordinatorUrl,
|
|
1581
1754
|
modelName,
|
|
1582
1755
|
local,
|
|
1756
|
+
receive,
|
|
1757
|
+
send,
|
|
1583
1758
|
jwtToken
|
|
1584
1759
|
})
|
|
1585
1760
|
);
|
|
@@ -1606,7 +1781,16 @@ function ReactorProvider(_a) {
|
|
|
1606
1781
|
console.error("[ReactorProvider] Failed to disconnect:", error);
|
|
1607
1782
|
});
|
|
1608
1783
|
};
|
|
1609
|
-
}, [
|
|
1784
|
+
}, [
|
|
1785
|
+
coordinatorUrl,
|
|
1786
|
+
modelName,
|
|
1787
|
+
autoConnect,
|
|
1788
|
+
local,
|
|
1789
|
+
receive,
|
|
1790
|
+
send,
|
|
1791
|
+
jwtToken,
|
|
1792
|
+
maxAttempts
|
|
1793
|
+
]);
|
|
1610
1794
|
return /* @__PURE__ */ jsx(ReactorContext.Provider, { value: storeRef.current, children });
|
|
1611
1795
|
}
|
|
1612
1796
|
function useReactorStore(selector) {
|
|
@@ -1672,50 +1856,63 @@ function useStats() {
|
|
|
1672
1856
|
}
|
|
1673
1857
|
|
|
1674
1858
|
// src/react/ReactorView.tsx
|
|
1675
|
-
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
1859
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
|
|
1676
1860
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1677
1861
|
function ReactorView({
|
|
1862
|
+
track = "main_video",
|
|
1863
|
+
audioTrack,
|
|
1678
1864
|
width,
|
|
1679
1865
|
height,
|
|
1680
1866
|
className,
|
|
1681
1867
|
style,
|
|
1682
|
-
videoObjectFit = "contain"
|
|
1868
|
+
videoObjectFit = "contain",
|
|
1869
|
+
muted = true
|
|
1683
1870
|
}) {
|
|
1684
|
-
const {
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
+
});
|
|
1688
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]);
|
|
1689
1887
|
useEffect3(() => {
|
|
1690
|
-
console.debug("[ReactorView]
|
|
1888
|
+
console.debug("[ReactorView] Media track effect triggered", {
|
|
1889
|
+
track,
|
|
1691
1890
|
hasVideoElement: !!videoRef.current,
|
|
1692
|
-
hasVideoTrack: !!
|
|
1693
|
-
|
|
1891
|
+
hasVideoTrack: !!videoMediaTrack,
|
|
1892
|
+
hasAudioTrack: !!audioMediaTrack
|
|
1694
1893
|
});
|
|
1695
|
-
if (videoRef.current &&
|
|
1696
|
-
console.debug("[ReactorView] Attaching
|
|
1894
|
+
if (videoRef.current && mediaStream) {
|
|
1895
|
+
console.debug("[ReactorView] Attaching media stream to element");
|
|
1697
1896
|
try {
|
|
1698
|
-
|
|
1699
|
-
videoRef.current.srcObject = stream;
|
|
1897
|
+
videoRef.current.srcObject = mediaStream;
|
|
1700
1898
|
videoRef.current.play().catch((e) => {
|
|
1701
1899
|
console.warn("[ReactorView] Auto-play failed:", e);
|
|
1702
1900
|
});
|
|
1703
|
-
console.debug("[ReactorView]
|
|
1901
|
+
console.debug("[ReactorView] Media stream attached successfully");
|
|
1704
1902
|
} catch (error) {
|
|
1705
|
-
console.error("[ReactorView] Failed to attach
|
|
1903
|
+
console.error("[ReactorView] Failed to attach media stream:", error);
|
|
1706
1904
|
}
|
|
1707
1905
|
return () => {
|
|
1708
|
-
console.debug("[ReactorView] Detaching
|
|
1906
|
+
console.debug("[ReactorView] Detaching media stream from element");
|
|
1709
1907
|
if (videoRef.current) {
|
|
1710
1908
|
videoRef.current.srcObject = null;
|
|
1711
|
-
console.debug("[ReactorView] Video track detached successfully");
|
|
1712
1909
|
}
|
|
1713
1910
|
};
|
|
1714
1911
|
} else {
|
|
1715
|
-
console.debug("[ReactorView] No
|
|
1912
|
+
console.debug("[ReactorView] No tracks or element to attach");
|
|
1716
1913
|
}
|
|
1717
|
-
}, [
|
|
1718
|
-
const showPlaceholder = !
|
|
1914
|
+
}, [mediaStream]);
|
|
1915
|
+
const showPlaceholder = !videoMediaTrack;
|
|
1719
1916
|
return /* @__PURE__ */ jsxs(
|
|
1720
1917
|
"div",
|
|
1721
1918
|
{
|
|
@@ -1735,7 +1932,7 @@ function ReactorView({
|
|
|
1735
1932
|
objectFit: videoObjectFit,
|
|
1736
1933
|
display: showPlaceholder ? "none" : "block"
|
|
1737
1934
|
},
|
|
1738
|
-
muted
|
|
1935
|
+
muted,
|
|
1739
1936
|
playsInline: true
|
|
1740
1937
|
}
|
|
1741
1938
|
),
|
|
@@ -2223,6 +2420,7 @@ function ReactorController({
|
|
|
2223
2420
|
import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
2224
2421
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2225
2422
|
function WebcamStream({
|
|
2423
|
+
track,
|
|
2226
2424
|
className,
|
|
2227
2425
|
style,
|
|
2228
2426
|
videoConstraints = {
|
|
@@ -2235,10 +2433,10 @@ function WebcamStream({
|
|
|
2235
2433
|
const [stream, setStream] = useState4(null);
|
|
2236
2434
|
const [isPublishing, setIsPublishing] = useState4(false);
|
|
2237
2435
|
const [permissionDenied, setPermissionDenied] = useState4(false);
|
|
2238
|
-
const { status,
|
|
2436
|
+
const { status, publish, unpublish, reactor } = useReactor((state) => ({
|
|
2239
2437
|
status: state.status,
|
|
2240
|
-
|
|
2241
|
-
|
|
2438
|
+
publish: state.publish,
|
|
2439
|
+
unpublish: state.unpublish,
|
|
2242
2440
|
reactor: state.internal.reactor
|
|
2243
2441
|
}));
|
|
2244
2442
|
const videoRef = useRef4(null);
|
|
@@ -2263,15 +2461,15 @@ function WebcamStream({
|
|
|
2263
2461
|
const stopWebcam = () => __async(null, null, function* () {
|
|
2264
2462
|
console.debug("[WebcamPublisher] Stopping webcam");
|
|
2265
2463
|
try {
|
|
2266
|
-
yield
|
|
2464
|
+
yield unpublish(track);
|
|
2267
2465
|
console.debug("[WebcamPublisher] Unpublished before stopping");
|
|
2268
2466
|
} catch (err) {
|
|
2269
2467
|
console.error("[WebcamPublisher] Error unpublishing before stop:", err);
|
|
2270
2468
|
}
|
|
2271
2469
|
setIsPublishing(false);
|
|
2272
|
-
stream == null ? void 0 : stream.getTracks().forEach((
|
|
2273
|
-
|
|
2274
|
-
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);
|
|
2275
2473
|
});
|
|
2276
2474
|
setStream(null);
|
|
2277
2475
|
console.debug("[WebcamPublisher] Webcam stopped");
|
|
@@ -2301,28 +2499,31 @@ function WebcamStream({
|
|
|
2301
2499
|
console.debug(
|
|
2302
2500
|
"[WebcamPublisher] Reactor ready, auto-publishing webcam stream"
|
|
2303
2501
|
);
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
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
|
+
}
|
|
2310
2511
|
} else if (status !== "ready" && isPublishing) {
|
|
2311
2512
|
console.debug("[WebcamPublisher] Reactor not ready, auto-unpublishing");
|
|
2312
|
-
|
|
2513
|
+
unpublish(track).then(() => {
|
|
2313
2514
|
console.debug("[WebcamPublisher] Auto-unpublish successful");
|
|
2314
2515
|
setIsPublishing(false);
|
|
2315
2516
|
}).catch((err) => {
|
|
2316
2517
|
console.error("[WebcamPublisher] Auto-unpublish failed:", err);
|
|
2317
2518
|
});
|
|
2318
2519
|
}
|
|
2319
|
-
}, [status, stream, isPublishing,
|
|
2520
|
+
}, [status, stream, isPublishing, publish, unpublish, track]);
|
|
2320
2521
|
useEffect4(() => {
|
|
2321
2522
|
const handleError = (error) => {
|
|
2322
2523
|
console.debug("[WebcamPublisher] Received error event:", error);
|
|
2323
|
-
if (error.code === "
|
|
2524
|
+
if (error.code === "TRACK_PUBLISH_FAILED") {
|
|
2324
2525
|
console.debug(
|
|
2325
|
-
"[WebcamPublisher]
|
|
2526
|
+
"[WebcamPublisher] Track publish failed, resetting isPublishing state"
|
|
2326
2527
|
);
|
|
2327
2528
|
setIsPublishing(false);
|
|
2328
2529
|
}
|
|
@@ -2437,11 +2638,13 @@ export {
|
|
|
2437
2638
|
ReactorProvider,
|
|
2438
2639
|
ReactorView,
|
|
2439
2640
|
WebcamStream,
|
|
2641
|
+
audio,
|
|
2440
2642
|
fetchInsecureJwtToken,
|
|
2441
2643
|
useReactor,
|
|
2442
2644
|
useReactorInternalMessage,
|
|
2443
2645
|
useReactorMessage,
|
|
2444
2646
|
useReactorStore,
|
|
2445
|
-
useStats
|
|
2647
|
+
useStats,
|
|
2648
|
+
video
|
|
2446
2649
|
};
|
|
2447
2650
|
//# sourceMappingURL=index.mjs.map
|