@reactor-team/js-sdk 1.0.11 → 1.0.14

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
- import react, { ReactNode } from 'react';
3
+ import React, { ReactNode } from 'react';
4
4
  import { RemoteVideoTrack } from 'livekit-client';
5
5
 
6
6
  type ReactorStatus = "disconnected" | "connecting" | "waiting" | "ready";
@@ -121,6 +121,8 @@ interface ReactorActions {
121
121
  sendMessage(message: any): Promise<void>;
122
122
  connect(): Promise<void>;
123
123
  disconnect(): Promise<void>;
124
+ publishVideoStream(stream: MediaStream): Promise<void>;
125
+ unpublishVideoStream(): Promise<void>;
124
126
  }
125
127
  interface ReactorInternalState {
126
128
  reactor: Reactor;
@@ -142,9 +144,19 @@ interface ReactorViewProps {
142
144
  width?: number;
143
145
  height?: number;
144
146
  className?: string;
145
- style?: react.CSSProperties;
147
+ style?: React.CSSProperties;
148
+ videoObjectFit?: NonNullable<React.VideoHTMLAttributes<HTMLVideoElement>["style"]>["objectFit"];
149
+ }
150
+ declare function ReactorView({ width, height, className, style, videoObjectFit, }: ReactorViewProps): react_jsx_runtime.JSX.Element;
151
+
152
+ interface Props {
153
+ className?: string;
154
+ style?: React.CSSProperties;
155
+ videoConstraints?: MediaTrackConstraints;
156
+ showWebcam?: boolean;
157
+ videoObjectFit?: NonNullable<React.VideoHTMLAttributes<HTMLVideoElement>["style"]>["objectFit"];
146
158
  }
147
- declare function ReactorView({ width, height, className, style, }: ReactorViewProps): react_jsx_runtime.JSX.Element;
159
+ declare function WebcamStream({ className, style, videoConstraints, showWebcam, videoObjectFit, }: Props): react_jsx_runtime.JSX.Element;
148
160
 
149
161
  /**
150
162
  * Generic hook for accessing selected parts of the Reactor store.
@@ -160,4 +172,4 @@ declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
160
172
  */
161
173
  declare function useReactorMessage(handler: (message: any) => void): void;
162
174
 
163
- export { type Options, Reactor, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, type ReactorWaitingInfo, useReactor, useReactorMessage, useReactorStore };
175
+ export { type Options, Reactor, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, type ReactorWaitingInfo, WebcamStream, useReactor, useReactorMessage, useReactorStore };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
- import react, { ReactNode } from 'react';
3
+ import React, { ReactNode } from 'react';
4
4
  import { RemoteVideoTrack } from 'livekit-client';
5
5
 
6
6
  type ReactorStatus = "disconnected" | "connecting" | "waiting" | "ready";
@@ -121,6 +121,8 @@ interface ReactorActions {
121
121
  sendMessage(message: any): Promise<void>;
122
122
  connect(): Promise<void>;
123
123
  disconnect(): Promise<void>;
124
+ publishVideoStream(stream: MediaStream): Promise<void>;
125
+ unpublishVideoStream(): Promise<void>;
124
126
  }
125
127
  interface ReactorInternalState {
126
128
  reactor: Reactor;
@@ -142,9 +144,19 @@ interface ReactorViewProps {
142
144
  width?: number;
143
145
  height?: number;
144
146
  className?: string;
145
- style?: react.CSSProperties;
147
+ style?: React.CSSProperties;
148
+ videoObjectFit?: NonNullable<React.VideoHTMLAttributes<HTMLVideoElement>["style"]>["objectFit"];
149
+ }
150
+ declare function ReactorView({ width, height, className, style, videoObjectFit, }: ReactorViewProps): react_jsx_runtime.JSX.Element;
151
+
152
+ interface Props {
153
+ className?: string;
154
+ style?: React.CSSProperties;
155
+ videoConstraints?: MediaTrackConstraints;
156
+ showWebcam?: boolean;
157
+ videoObjectFit?: NonNullable<React.VideoHTMLAttributes<HTMLVideoElement>["style"]>["objectFit"];
146
158
  }
147
- declare function ReactorView({ width, height, className, style, }: ReactorViewProps): react_jsx_runtime.JSX.Element;
159
+ declare function WebcamStream({ className, style, videoConstraints, showWebcam, videoObjectFit, }: Props): react_jsx_runtime.JSX.Element;
148
160
 
149
161
  /**
150
162
  * Generic hook for accessing selected parts of the Reactor store.
@@ -160,4 +172,4 @@ declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
160
172
  */
161
173
  declare function useReactorMessage(handler: (message: any) => void): void;
162
174
 
163
- export { type Options, Reactor, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, type ReactorWaitingInfo, useReactor, useReactorMessage, useReactorStore };
175
+ export { type Options, Reactor, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, type ReactorWaitingInfo, WebcamStream, useReactor, useReactorMessage, useReactorStore };
package/dist/index.js CHANGED
@@ -72,6 +72,7 @@ __export(index_exports, {
72
72
  Reactor: () => Reactor,
73
73
  ReactorProvider: () => ReactorProvider,
74
74
  ReactorView: () => ReactorView,
75
+ WebcamStream: () => WebcamStream,
75
76
  useReactor: () => useReactor,
76
77
  useReactorMessage: () => useReactorMessage,
77
78
  useReactorStore: () => useReactorStore
@@ -453,18 +454,22 @@ var GPUMachineClient = class {
453
454
  }
454
455
  /**
455
456
  * Unpublishes the currently published video track.
457
+ * Note: We pass false to unpublishTrack to prevent LiveKit from stopping
458
+ * the source MediaStreamTrack, as it's owned by the component that created it.
456
459
  */
457
460
  unpublishVideoTrack() {
458
461
  return __async(this, null, function* () {
459
462
  if (!this.roomInstance || !this.publishedVideoTrack) {
460
463
  return;
461
464
  }
465
+ const publishedVideoTrack = this.publishedVideoTrack;
466
+ this.publishedVideoTrack = void 0;
462
467
  try {
463
468
  yield this.roomInstance.localParticipant.unpublishTrack(
464
- this.publishedVideoTrack
469
+ publishedVideoTrack,
470
+ false
471
+ // Don't stop the source track - it's managed externally
465
472
  );
466
- this.publishedVideoTrack.stop();
467
- this.publishedVideoTrack = void 0;
468
473
  console.debug("[GPUMachineClient] Video track unpublished successfully");
469
474
  } catch (error) {
470
475
  console.error(
@@ -512,6 +517,8 @@ var GPUMachineClient = class {
512
517
  }
513
518
  /**
514
519
  * Unpublishes the currently published audio track.
520
+ * Note: We pass false to unpublishTrack to prevent LiveKit from stopping
521
+ * the source MediaStreamTrack, as it's owned by the component that created it.
515
522
  * @private
516
523
  */
517
524
  unpublishAudioTrack() {
@@ -519,12 +526,14 @@ var GPUMachineClient = class {
519
526
  if (!this.roomInstance || !this.publishedAudioTrack) {
520
527
  return;
521
528
  }
529
+ const publishedAudioTrack = this.publishedAudioTrack;
530
+ this.publishedAudioTrack = void 0;
522
531
  try {
523
532
  yield this.roomInstance.localParticipant.unpublishTrack(
524
- this.publishedAudioTrack
533
+ publishedAudioTrack,
534
+ false
535
+ // Don't stop the source track - it's managed externally
525
536
  );
526
- this.publishedAudioTrack.stop();
527
- this.publishedAudioTrack = void 0;
528
537
  console.debug("[GPUMachineClient] Audio track unpublished successfully");
529
538
  } catch (error) {
530
539
  console.error(
@@ -996,6 +1005,32 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
996
1005
  console.error("[ReactorStore] Disconnect failed:", error);
997
1006
  throw error;
998
1007
  }
1008
+ }),
1009
+ publishVideoStream: (stream) => __async(null, null, function* () {
1010
+ console.debug("[ReactorStore] Publishing video stream");
1011
+ try {
1012
+ yield get().internal.reactor.publishVideoStream(stream);
1013
+ console.debug("[ReactorStore] Video stream published successfully");
1014
+ } catch (error) {
1015
+ console.error(
1016
+ "[ReactorStore] Failed to publish video stream:",
1017
+ error
1018
+ );
1019
+ throw error;
1020
+ }
1021
+ }),
1022
+ unpublishVideoStream: () => __async(null, null, function* () {
1023
+ console.debug("[ReactorStore] Unpublishing video stream");
1024
+ try {
1025
+ yield get().internal.reactor.unpublishVideoStream();
1026
+ console.debug("[ReactorStore] Video stream unpublished successfully");
1027
+ } catch (error) {
1028
+ console.error(
1029
+ "[ReactorStore] Failed to unpublish video stream:",
1030
+ error
1031
+ );
1032
+ throw error;
1033
+ }
999
1034
  })
1000
1035
  });
1001
1036
  });
@@ -1150,18 +1185,14 @@ function ReactorView({
1150
1185
  width,
1151
1186
  height,
1152
1187
  className,
1153
- style
1188
+ style,
1189
+ videoObjectFit = "contain"
1154
1190
  }) {
1155
1191
  const { videoTrack, status } = useReactor((state) => ({
1156
1192
  videoTrack: state.videoTrack,
1157
1193
  status: state.status
1158
1194
  }));
1159
1195
  const videoRef = (0, import_react5.useRef)(null);
1160
- console.debug("[ReactorView] Render", {
1161
- hasVideoTrack: !!videoTrack,
1162
- status,
1163
- hasVideoElement: !!videoRef.current
1164
- });
1165
1196
  (0, import_react5.useEffect)(() => {
1166
1197
  console.debug("[ReactorView] Video track effect triggered", {
1167
1198
  hasVideoElement: !!videoRef.current,
@@ -1192,7 +1223,6 @@ function ReactorView({
1192
1223
  }
1193
1224
  }, [videoTrack]);
1194
1225
  const showPlaceholder = !videoTrack;
1195
- console.debug("[ReactorView] Placeholder state", { showPlaceholder, status });
1196
1226
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1197
1227
  "div",
1198
1228
  {
@@ -1209,7 +1239,7 @@ function ReactorView({
1209
1239
  style: {
1210
1240
  width: "100%",
1211
1241
  height: "100%",
1212
- objectFit: "contain",
1242
+ objectFit: videoObjectFit,
1213
1243
  display: showPlaceholder ? "none" : "block"
1214
1244
  },
1215
1245
  muted: true,
@@ -1242,11 +1272,202 @@ function ReactorView({
1242
1272
  }
1243
1273
  );
1244
1274
  }
1275
+
1276
+ // src/react/WebcamStream.tsx
1277
+ var import_react6 = require("react");
1278
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1279
+ function WebcamStream({
1280
+ className,
1281
+ style,
1282
+ videoConstraints = {
1283
+ width: { ideal: 1280 },
1284
+ height: { ideal: 720 }
1285
+ },
1286
+ showWebcam = true,
1287
+ videoObjectFit = "contain"
1288
+ }) {
1289
+ const [stream, setStream] = (0, import_react6.useState)(null);
1290
+ const [isPublishing, setIsPublishing] = (0, import_react6.useState)(false);
1291
+ const [permissionDenied, setPermissionDenied] = (0, import_react6.useState)(false);
1292
+ const { status, publishVideoStream, unpublishVideoStream, reactor } = useReactor((state) => ({
1293
+ status: state.status,
1294
+ publishVideoStream: state.publishVideoStream,
1295
+ unpublishVideoStream: state.unpublishVideoStream,
1296
+ reactor: state.internal.reactor
1297
+ }));
1298
+ const videoRef = (0, import_react6.useRef)(null);
1299
+ const startWebcam = () => __async(null, null, function* () {
1300
+ console.debug("[WebcamPublisher] Starting webcam");
1301
+ try {
1302
+ const mediaStream = yield navigator.mediaDevices.getUserMedia({
1303
+ video: videoConstraints,
1304
+ audio: false
1305
+ });
1306
+ console.debug("[WebcamPublisher] Webcam started successfully");
1307
+ setStream(mediaStream);
1308
+ setPermissionDenied(false);
1309
+ } catch (err) {
1310
+ console.error("[WebcamPublisher] Failed to start webcam:", err);
1311
+ if (err instanceof DOMException && (err.name === "NotAllowedError" || err.name === "PermissionDeniedError")) {
1312
+ console.debug("[WebcamPublisher] Camera permission denied");
1313
+ setPermissionDenied(true);
1314
+ }
1315
+ }
1316
+ });
1317
+ const stopWebcam = () => __async(null, null, function* () {
1318
+ console.debug("[WebcamPublisher] Stopping webcam");
1319
+ try {
1320
+ yield unpublishVideoStream();
1321
+ console.debug("[WebcamPublisher] Unpublished before stopping");
1322
+ } catch (err) {
1323
+ console.error("[WebcamPublisher] Error unpublishing before stop:", err);
1324
+ }
1325
+ setIsPublishing(false);
1326
+ stream == null ? void 0 : stream.getTracks().forEach((track) => {
1327
+ track.stop();
1328
+ console.debug("[WebcamPublisher] Stopped track:", track.kind);
1329
+ });
1330
+ setStream(null);
1331
+ console.debug("[WebcamPublisher] Webcam stopped");
1332
+ });
1333
+ (0, import_react6.useEffect)(() => {
1334
+ console.debug("[WebcamPublisher] Stream effect triggered", {
1335
+ hasVideoElement: !!videoRef.current,
1336
+ hasStream: !!stream
1337
+ });
1338
+ if (!videoRef.current) {
1339
+ return;
1340
+ }
1341
+ if (stream) {
1342
+ console.debug("[WebcamPublisher] Attaching stream to video element");
1343
+ videoRef.current.srcObject = stream;
1344
+ console.debug("[WebcamPublisher] Stream attached successfully");
1345
+ } else {
1346
+ console.debug("[WebcamPublisher] Clearing video element");
1347
+ videoRef.current.srcObject = null;
1348
+ }
1349
+ }, [stream]);
1350
+ (0, import_react6.useEffect)(() => {
1351
+ if (!stream) {
1352
+ return;
1353
+ }
1354
+ if (status === "ready" && !isPublishing) {
1355
+ console.debug(
1356
+ "[WebcamPublisher] Reactor ready, auto-publishing webcam stream"
1357
+ );
1358
+ publishVideoStream(stream).then(() => {
1359
+ console.debug("[WebcamPublisher] Auto-publish successful");
1360
+ setIsPublishing(true);
1361
+ }).catch((err) => {
1362
+ console.error("[WebcamPublisher] Auto-publish failed:", err);
1363
+ });
1364
+ } else if (status !== "ready" && isPublishing) {
1365
+ console.debug("[WebcamPublisher] Reactor not ready, auto-unpublishing");
1366
+ unpublishVideoStream().then(() => {
1367
+ console.debug("[WebcamPublisher] Auto-unpublish successful");
1368
+ setIsPublishing(false);
1369
+ }).catch((err) => {
1370
+ console.error("[WebcamPublisher] Auto-unpublish failed:", err);
1371
+ });
1372
+ }
1373
+ }, [status, stream, isPublishing, publishVideoStream, unpublishVideoStream]);
1374
+ (0, import_react6.useEffect)(() => {
1375
+ const handleError = (error) => {
1376
+ console.debug("[WebcamPublisher] Received error event:", error);
1377
+ if (error.code === "VIDEO_PUBLISH_FAILED") {
1378
+ console.debug(
1379
+ "[WebcamPublisher] Video publish failed, resetting isPublishing state"
1380
+ );
1381
+ setIsPublishing(false);
1382
+ }
1383
+ };
1384
+ reactor.on("error", handleError);
1385
+ return () => {
1386
+ reactor.off("error", handleError);
1387
+ };
1388
+ }, [reactor]);
1389
+ (0, import_react6.useEffect)(() => {
1390
+ if (status !== "ready") {
1391
+ console.debug(
1392
+ "[WebcamPublisher] Status changed to",
1393
+ status,
1394
+ "- resetting isPublishing state"
1395
+ );
1396
+ setIsPublishing(false);
1397
+ }
1398
+ }, [status, isPublishing]);
1399
+ (0, import_react6.useEffect)(() => {
1400
+ console.debug("[WebcamPublisher] Auto-starting webcam");
1401
+ startWebcam();
1402
+ return () => {
1403
+ console.debug("[WebcamPublisher] Cleanup on unmount");
1404
+ stopWebcam();
1405
+ };
1406
+ }, []);
1407
+ const showPlaceholder = !stream;
1408
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1409
+ "div",
1410
+ {
1411
+ style: __spreadValues({
1412
+ display: showWebcam ? "block" : "none",
1413
+ position: "relative",
1414
+ background: "#000"
1415
+ }, style),
1416
+ className,
1417
+ children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1419
+ "video",
1420
+ {
1421
+ ref: videoRef,
1422
+ style: {
1423
+ width: "100%",
1424
+ height: "100%",
1425
+ objectFit: videoObjectFit,
1426
+ display: showPlaceholder ? "none" : "block"
1427
+ },
1428
+ muted: true,
1429
+ playsInline: true,
1430
+ autoPlay: true
1431
+ }
1432
+ ),
1433
+ showPlaceholder && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1434
+ "div",
1435
+ {
1436
+ style: {
1437
+ position: "absolute",
1438
+ top: 0,
1439
+ left: 0,
1440
+ width: "100%",
1441
+ height: "100%",
1442
+ color: "#fff",
1443
+ display: "flex",
1444
+ alignItems: "center",
1445
+ justifyContent: "center",
1446
+ fontSize: "16px",
1447
+ fontFamily: "monospace",
1448
+ textAlign: "center",
1449
+ padding: "20px",
1450
+ boxSizing: "border-box",
1451
+ flexDirection: "column",
1452
+ gap: "12px"
1453
+ },
1454
+ children: permissionDenied ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", fontFamily: "monospace" }, children: [
1455
+ "Camera access denied.",
1456
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {}),
1457
+ "Please allow access in your browser settings."
1458
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: "12px", fontFamily: "monospace" }, children: "Starting camera..." })
1459
+ }
1460
+ )
1461
+ ]
1462
+ }
1463
+ );
1464
+ }
1245
1465
  // Annotate the CommonJS export names for ESM import in node:
1246
1466
  0 && (module.exports = {
1247
1467
  Reactor,
1248
1468
  ReactorProvider,
1249
1469
  ReactorView,
1470
+ WebcamStream,
1250
1471
  useReactor,
1251
1472
  useReactorMessage,
1252
1473
  useReactorStore