@runwayml/avatars-react 0.9.0 → 0.10.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -16
- package/dist/api.cjs +10 -0
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +74 -1
- package/dist/api.d.ts +74 -1
- package/dist/api.js +10 -1
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +208 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +215 -9
- package/dist/index.d.ts +215 -9
- package/dist/index.js +204 -58
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -154,53 +154,22 @@ function useLatest(value) {
|
|
|
154
154
|
}, [value]);
|
|
155
155
|
return ref;
|
|
156
156
|
}
|
|
157
|
-
|
|
157
|
+
|
|
158
|
+
// src/utils/parseClientEvent.ts
|
|
159
|
+
function isAckMessage(args) {
|
|
160
|
+
return "status" in args && args.status === "event_sent";
|
|
161
|
+
}
|
|
162
|
+
function parseClientEvent(payload) {
|
|
158
163
|
try {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
164
|
+
const message = JSON.parse(new TextDecoder().decode(payload));
|
|
165
|
+
if (message?.type === "client_event" && typeof message.tool === "string" && message.args != null && typeof message.args === "object" && !isAckMessage(message.args)) {
|
|
166
|
+
return message;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
164
169
|
} catch {
|
|
165
|
-
return
|
|
170
|
+
return null;
|
|
166
171
|
}
|
|
167
172
|
}
|
|
168
|
-
function useDeviceAvailability(requestAudio, requestVideo) {
|
|
169
|
-
const [state, setState] = react.useState({
|
|
170
|
-
audio: requestAudio,
|
|
171
|
-
// Optimistically assume devices exist
|
|
172
|
-
video: requestVideo
|
|
173
|
-
});
|
|
174
|
-
react.useEffect(() => {
|
|
175
|
-
let cancelled = false;
|
|
176
|
-
async function checkDevices() {
|
|
177
|
-
const [hasAudio, hasVideo] = await Promise.all([
|
|
178
|
-
requestAudio ? hasMediaDevice("audioinput") : Promise.resolve(false),
|
|
179
|
-
requestVideo ? hasMediaDevice("videoinput") : Promise.resolve(false)
|
|
180
|
-
]);
|
|
181
|
-
if (!cancelled) {
|
|
182
|
-
setState({
|
|
183
|
-
audio: requestAudio && hasAudio,
|
|
184
|
-
video: requestVideo && hasVideo
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
checkDevices();
|
|
189
|
-
return () => {
|
|
190
|
-
cancelled = true;
|
|
191
|
-
};
|
|
192
|
-
}, [requestAudio, requestVideo]);
|
|
193
|
-
return state;
|
|
194
|
-
}
|
|
195
|
-
var MEDIA_DEVICE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
196
|
-
"NotAllowedError",
|
|
197
|
-
"NotFoundError",
|
|
198
|
-
"NotReadableError",
|
|
199
|
-
"OverconstrainedError"
|
|
200
|
-
]);
|
|
201
|
-
function isMediaDeviceError(error) {
|
|
202
|
-
return MEDIA_DEVICE_ERROR_NAMES.has(error.name);
|
|
203
|
-
}
|
|
204
173
|
var DEFAULT_ROOM_OPTIONS = {
|
|
205
174
|
adaptiveStream: false,
|
|
206
175
|
dynacast: false
|
|
@@ -222,6 +191,7 @@ function mapConnectionState(connectionState) {
|
|
|
222
191
|
var AvatarSessionContext = react.createContext(
|
|
223
192
|
null
|
|
224
193
|
);
|
|
194
|
+
var MediaDeviceErrorContext = react.createContext(null);
|
|
225
195
|
function AvatarSession({
|
|
226
196
|
credentials,
|
|
227
197
|
children,
|
|
@@ -229,16 +199,14 @@ function AvatarSession({
|
|
|
229
199
|
video: requestVideo = true,
|
|
230
200
|
onEnd,
|
|
231
201
|
onError,
|
|
202
|
+
onClientEvent,
|
|
232
203
|
initialScreenStream,
|
|
233
204
|
__unstable_roomOptions
|
|
234
205
|
}) {
|
|
235
206
|
const errorRef = react.useRef(null);
|
|
236
|
-
const deviceAvailability = useDeviceAvailability(requestAudio, requestVideo);
|
|
237
207
|
const handleError = (error) => {
|
|
238
208
|
onError?.(error);
|
|
239
|
-
|
|
240
|
-
errorRef.current = error;
|
|
241
|
-
}
|
|
209
|
+
errorRef.current = error;
|
|
242
210
|
};
|
|
243
211
|
const roomOptions = {
|
|
244
212
|
...DEFAULT_ROOM_OPTIONS,
|
|
@@ -250,8 +218,8 @@ function AvatarSession({
|
|
|
250
218
|
serverUrl: credentials.serverUrl,
|
|
251
219
|
token: credentials.token,
|
|
252
220
|
connect: true,
|
|
253
|
-
audio:
|
|
254
|
-
video:
|
|
221
|
+
audio: false,
|
|
222
|
+
video: false,
|
|
255
223
|
onDisconnected: () => onEnd?.(),
|
|
256
224
|
onError: handleError,
|
|
257
225
|
options: roomOptions,
|
|
@@ -263,7 +231,10 @@ function AvatarSession({
|
|
|
263
231
|
AvatarSessionContextInner,
|
|
264
232
|
{
|
|
265
233
|
sessionId: credentials.sessionId,
|
|
234
|
+
requestAudio,
|
|
235
|
+
requestVideo,
|
|
266
236
|
onEnd,
|
|
237
|
+
onClientEvent,
|
|
267
238
|
errorRef,
|
|
268
239
|
initialScreenStream,
|
|
269
240
|
children
|
|
@@ -276,7 +247,10 @@ function AvatarSession({
|
|
|
276
247
|
}
|
|
277
248
|
function AvatarSessionContextInner({
|
|
278
249
|
sessionId,
|
|
250
|
+
requestAudio,
|
|
251
|
+
requestVideo,
|
|
279
252
|
onEnd,
|
|
253
|
+
onClientEvent,
|
|
280
254
|
errorRef,
|
|
281
255
|
initialScreenStream,
|
|
282
256
|
children
|
|
@@ -285,6 +259,8 @@ function AvatarSessionContextInner({
|
|
|
285
259
|
const connectionState = componentsReact.useConnectionState();
|
|
286
260
|
const onEndRef = react.useRef(onEnd);
|
|
287
261
|
onEndRef.current = onEnd;
|
|
262
|
+
const onClientEventRef = react.useRef(onClientEvent);
|
|
263
|
+
onClientEventRef.current = onClientEvent;
|
|
288
264
|
const publishedRef = react.useRef(false);
|
|
289
265
|
react.useEffect(() => {
|
|
290
266
|
if (connectionState !== livekitClient.ConnectionState.Connected) return;
|
|
@@ -308,6 +284,76 @@ function AvatarSessionContextInner({
|
|
|
308
284
|
});
|
|
309
285
|
};
|
|
310
286
|
}, [connectionState, initialScreenStream, room]);
|
|
287
|
+
const [micError, setMicError] = react.useState(null);
|
|
288
|
+
const [cameraError, setCameraError] = react.useState(null);
|
|
289
|
+
const mediaEnabledRef = react.useRef(false);
|
|
290
|
+
react.useEffect(() => {
|
|
291
|
+
if (connectionState !== livekitClient.ConnectionState.Connected) return;
|
|
292
|
+
if (mediaEnabledRef.current) return;
|
|
293
|
+
mediaEnabledRef.current = true;
|
|
294
|
+
async function enableMedia() {
|
|
295
|
+
if (requestAudio) {
|
|
296
|
+
try {
|
|
297
|
+
await room.localParticipant.setMicrophoneEnabled(true);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
if (err instanceof Error) setMicError(err);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (requestVideo) {
|
|
303
|
+
try {
|
|
304
|
+
await room.localParticipant.setCameraEnabled(true);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (err instanceof Error) setCameraError(err);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
enableMedia();
|
|
311
|
+
}, [connectionState, room, requestAudio, requestVideo]);
|
|
312
|
+
react.useEffect(() => {
|
|
313
|
+
function handleMediaDevicesError(error, kind) {
|
|
314
|
+
if (kind === "audioinput") {
|
|
315
|
+
setMicError(error);
|
|
316
|
+
} else if (kind === "videoinput") {
|
|
317
|
+
setCameraError(error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
room.on(livekitClient.RoomEvent.MediaDevicesError, handleMediaDevicesError);
|
|
321
|
+
return () => {
|
|
322
|
+
room.off(livekitClient.RoomEvent.MediaDevicesError, handleMediaDevicesError);
|
|
323
|
+
};
|
|
324
|
+
}, [room]);
|
|
325
|
+
const retryMic = react.useCallback(async () => {
|
|
326
|
+
try {
|
|
327
|
+
await room.localParticipant.setMicrophoneEnabled(true);
|
|
328
|
+
setMicError(null);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
if (err instanceof Error) setMicError(err);
|
|
331
|
+
}
|
|
332
|
+
}, [room]);
|
|
333
|
+
const retryCamera = react.useCallback(async () => {
|
|
334
|
+
try {
|
|
335
|
+
await room.localParticipant.setCameraEnabled(true);
|
|
336
|
+
setCameraError(null);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
if (err instanceof Error) setCameraError(err);
|
|
339
|
+
}
|
|
340
|
+
}, [room]);
|
|
341
|
+
const mediaDeviceErrors = react.useMemo(
|
|
342
|
+
() => ({ micError, cameraError, retryMic, retryCamera }),
|
|
343
|
+
[micError, cameraError, retryMic, retryCamera]
|
|
344
|
+
);
|
|
345
|
+
react.useEffect(() => {
|
|
346
|
+
function handleDataReceived(payload) {
|
|
347
|
+
const event = parseClientEvent(payload);
|
|
348
|
+
if (event) {
|
|
349
|
+
onClientEventRef.current?.(event);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
room.on(livekitClient.RoomEvent.DataReceived, handleDataReceived);
|
|
353
|
+
return () => {
|
|
354
|
+
room.off(livekitClient.RoomEvent.DataReceived, handleDataReceived);
|
|
355
|
+
};
|
|
356
|
+
}, [room]);
|
|
311
357
|
const end = react.useCallback(async () => {
|
|
312
358
|
try {
|
|
313
359
|
const encoder = new TextEncoder();
|
|
@@ -324,7 +370,7 @@ function AvatarSessionContextInner({
|
|
|
324
370
|
error: errorRef.current,
|
|
325
371
|
end
|
|
326
372
|
};
|
|
327
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AvatarSessionContext.Provider, { value: contextValue, children });
|
|
373
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AvatarSessionContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(MediaDeviceErrorContext.Provider, { value: mediaDeviceErrors, children }) });
|
|
328
374
|
}
|
|
329
375
|
function useAvatarSessionContext() {
|
|
330
376
|
const context = react.useContext(AvatarSessionContext);
|
|
@@ -335,6 +381,9 @@ function useAvatarSessionContext() {
|
|
|
335
381
|
}
|
|
336
382
|
return context;
|
|
337
383
|
}
|
|
384
|
+
function useMediaDeviceErrorContext() {
|
|
385
|
+
return react.useContext(MediaDeviceErrorContext);
|
|
386
|
+
}
|
|
338
387
|
function useAvatar() {
|
|
339
388
|
const remoteParticipants = componentsReact.useRemoteParticipants();
|
|
340
389
|
const avatarParticipant = remoteParticipants[0] ?? null;
|
|
@@ -370,7 +419,10 @@ function useAvatarStatus() {
|
|
|
370
419
|
return { status: "connecting" };
|
|
371
420
|
case "active":
|
|
372
421
|
if (hasVideo && videoTrackRef) {
|
|
373
|
-
return {
|
|
422
|
+
return {
|
|
423
|
+
status: "ready",
|
|
424
|
+
videoTrackRef
|
|
425
|
+
};
|
|
374
426
|
}
|
|
375
427
|
return { status: "waiting" };
|
|
376
428
|
case "ending":
|
|
@@ -397,8 +449,16 @@ function AvatarVideo({ children, ...props }) {
|
|
|
397
449
|
}
|
|
398
450
|
);
|
|
399
451
|
}
|
|
452
|
+
var NOOP_ASYNC = async () => {
|
|
453
|
+
};
|
|
400
454
|
function useLocalMedia() {
|
|
401
455
|
const { localParticipant } = componentsReact.useLocalParticipant();
|
|
456
|
+
const {
|
|
457
|
+
micError = null,
|
|
458
|
+
cameraError = null,
|
|
459
|
+
retryMic = NOOP_ASYNC,
|
|
460
|
+
retryCamera = NOOP_ASYNC
|
|
461
|
+
} = useMediaDeviceErrorContext() ?? {};
|
|
402
462
|
const audioDevices = componentsReact.useMediaDevices({ kind: "audioinput" });
|
|
403
463
|
const videoDevices = componentsReact.useMediaDevices({ kind: "videoinput" });
|
|
404
464
|
const hasMic = audioDevices?.length > 0;
|
|
@@ -444,7 +504,11 @@ function useLocalMedia() {
|
|
|
444
504
|
toggleMic,
|
|
445
505
|
toggleCamera,
|
|
446
506
|
toggleScreenShare,
|
|
447
|
-
localVideoTrackRef
|
|
507
|
+
localVideoTrackRef,
|
|
508
|
+
micError,
|
|
509
|
+
cameraError,
|
|
510
|
+
retryMic,
|
|
511
|
+
retryCamera
|
|
448
512
|
};
|
|
449
513
|
}
|
|
450
514
|
function ControlBar({
|
|
@@ -462,7 +526,11 @@ function ControlBar({
|
|
|
462
526
|
isScreenShareEnabled,
|
|
463
527
|
toggleMic,
|
|
464
528
|
toggleCamera,
|
|
465
|
-
toggleScreenShare
|
|
529
|
+
toggleScreenShare,
|
|
530
|
+
micError,
|
|
531
|
+
cameraError,
|
|
532
|
+
retryMic,
|
|
533
|
+
retryCamera
|
|
466
534
|
} = useLocalMedia();
|
|
467
535
|
const isActive = session.state === "active";
|
|
468
536
|
const state = {
|
|
@@ -473,7 +541,11 @@ function ControlBar({
|
|
|
473
541
|
toggleCamera,
|
|
474
542
|
toggleScreenShare,
|
|
475
543
|
endCall: session.end,
|
|
476
|
-
isActive
|
|
544
|
+
isActive,
|
|
545
|
+
micError,
|
|
546
|
+
cameraError,
|
|
547
|
+
retryMic,
|
|
548
|
+
retryCamera
|
|
477
549
|
};
|
|
478
550
|
if (children) {
|
|
479
551
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(state) });
|
|
@@ -635,6 +707,7 @@ function AvatarCall({
|
|
|
635
707
|
avatarImageUrl,
|
|
636
708
|
onEnd,
|
|
637
709
|
onError,
|
|
710
|
+
onClientEvent,
|
|
638
711
|
children,
|
|
639
712
|
initialScreenStream,
|
|
640
713
|
__unstable_roomOptions,
|
|
@@ -688,6 +761,7 @@ function AvatarCall({
|
|
|
688
761
|
video,
|
|
689
762
|
onEnd,
|
|
690
763
|
onError: handleSessionError,
|
|
764
|
+
onClientEvent,
|
|
691
765
|
initialScreenStream,
|
|
692
766
|
__unstable_roomOptions,
|
|
693
767
|
children: children ?? defaultChildren
|
|
@@ -722,6 +796,78 @@ function ScreenShareVideo({
|
|
|
722
796
|
}
|
|
723
797
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, "data-avatar-screen-share": "", "data-avatar-sharing": isSharing, children: screenShareTrackRef && componentsReact.isTrackReference(screenShareTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: screenShareTrackRef }) });
|
|
724
798
|
}
|
|
799
|
+
function useClientEvent(toolName, onEvent) {
|
|
800
|
+
const room = componentsReact.useRoomContext();
|
|
801
|
+
const [state, setState] = react.useState(null);
|
|
802
|
+
const onEventRef = react.useRef(onEvent);
|
|
803
|
+
onEventRef.current = onEvent;
|
|
804
|
+
react.useEffect(() => {
|
|
805
|
+
function handleDataReceived(payload) {
|
|
806
|
+
const event = parseClientEvent(payload);
|
|
807
|
+
if (event && event.tool === toolName) {
|
|
808
|
+
const args = event.args;
|
|
809
|
+
setState(args);
|
|
810
|
+
onEventRef.current?.(args);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
room.on(livekitClient.RoomEvent.DataReceived, handleDataReceived);
|
|
814
|
+
return () => {
|
|
815
|
+
room.off(livekitClient.RoomEvent.DataReceived, handleDataReceived);
|
|
816
|
+
};
|
|
817
|
+
}, [room, toolName]);
|
|
818
|
+
return state;
|
|
819
|
+
}
|
|
820
|
+
function useClientEvents(handler) {
|
|
821
|
+
const room = componentsReact.useRoomContext();
|
|
822
|
+
const handlerRef = react.useRef(handler);
|
|
823
|
+
handlerRef.current = handler;
|
|
824
|
+
react.useEffect(() => {
|
|
825
|
+
function handleDataReceived(payload) {
|
|
826
|
+
const event = parseClientEvent(payload);
|
|
827
|
+
if (event) {
|
|
828
|
+
handlerRef.current(event);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
room.on(livekitClient.RoomEvent.DataReceived, handleDataReceived);
|
|
832
|
+
return () => {
|
|
833
|
+
room.off(livekitClient.RoomEvent.DataReceived, handleDataReceived);
|
|
834
|
+
};
|
|
835
|
+
}, [room]);
|
|
836
|
+
}
|
|
837
|
+
function useTranscription(handler, options) {
|
|
838
|
+
const room = componentsReact.useRoomContext();
|
|
839
|
+
const handlerRef = react.useRef(handler);
|
|
840
|
+
handlerRef.current = handler;
|
|
841
|
+
const interimRef = react.useRef(options?.interim ?? false);
|
|
842
|
+
interimRef.current = options?.interim ?? false;
|
|
843
|
+
react.useEffect(() => {
|
|
844
|
+
function handleTranscription(segments, participant) {
|
|
845
|
+
const identity = participant?.identity ?? "unknown";
|
|
846
|
+
for (const segment of segments) {
|
|
847
|
+
if (!interimRef.current && !segment.final) continue;
|
|
848
|
+
handlerRef.current({
|
|
849
|
+
id: segment.id,
|
|
850
|
+
text: segment.text,
|
|
851
|
+
final: segment.final,
|
|
852
|
+
participantIdentity: identity
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
room.on(livekitClient.RoomEvent.TranscriptionReceived, handleTranscription);
|
|
857
|
+
return () => {
|
|
858
|
+
room.off(livekitClient.RoomEvent.TranscriptionReceived, handleTranscription);
|
|
859
|
+
};
|
|
860
|
+
}, [room]);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/tools.ts
|
|
864
|
+
function clientTool(name, config) {
|
|
865
|
+
return {
|
|
866
|
+
type: "client_event",
|
|
867
|
+
name,
|
|
868
|
+
description: config.description
|
|
869
|
+
};
|
|
870
|
+
}
|
|
725
871
|
|
|
726
872
|
Object.defineProperty(exports, "AudioRenderer", {
|
|
727
873
|
enumerable: true,
|
|
@@ -731,15 +877,23 @@ Object.defineProperty(exports, "VideoTrack", {
|
|
|
731
877
|
enumerable: true,
|
|
732
878
|
get: function () { return componentsReact.VideoTrack; }
|
|
733
879
|
});
|
|
880
|
+
Object.defineProperty(exports, "isTrackReference", {
|
|
881
|
+
enumerable: true,
|
|
882
|
+
get: function () { return componentsReact.isTrackReference; }
|
|
883
|
+
});
|
|
734
884
|
exports.AvatarCall = AvatarCall;
|
|
735
885
|
exports.AvatarSession = AvatarSession;
|
|
736
886
|
exports.AvatarVideo = AvatarVideo;
|
|
737
887
|
exports.ControlBar = ControlBar;
|
|
738
888
|
exports.ScreenShareVideo = ScreenShareVideo;
|
|
739
889
|
exports.UserVideo = UserVideo;
|
|
890
|
+
exports.clientTool = clientTool;
|
|
740
891
|
exports.useAvatar = useAvatar;
|
|
741
892
|
exports.useAvatarSession = useAvatarSession;
|
|
742
893
|
exports.useAvatarStatus = useAvatarStatus;
|
|
894
|
+
exports.useClientEvent = useClientEvent;
|
|
895
|
+
exports.useClientEvents = useClientEvents;
|
|
743
896
|
exports.useLocalMedia = useLocalMedia;
|
|
897
|
+
exports.useTranscription = useTranscription;
|
|
744
898
|
//# sourceMappingURL=index.cjs.map
|
|
745
899
|
//# sourceMappingURL=index.cjs.map
|