@runwayml/avatars-react 0.10.0-beta.0 → 0.11.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/README.md +28 -18
- package/dist/index.cjs +186 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +188 -115
- package/dist/index.js.map +1 -1
- package/dist/styles.css +121 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -46,17 +46,20 @@ import '@runwayml/avatars-react/styles.css';
|
|
|
46
46
|
The styles use CSS custom properties for easy customization:
|
|
47
47
|
|
|
48
48
|
```css
|
|
49
|
-
|
|
50
|
-
--avatar-bg: #
|
|
51
|
-
--avatar-radius: 16px;
|
|
52
|
-
--avatar-control-size:
|
|
53
|
-
--avatar-end-call-bg: #
|
|
49
|
+
[data-avatar-call] {
|
|
50
|
+
--avatar-bg-connecting: #8b5cf6; /* Video background color */
|
|
51
|
+
--avatar-radius: 16px; /* Container border radius */
|
|
52
|
+
--avatar-control-size: 40px; /* Control button size */
|
|
53
|
+
--avatar-end-call-bg: #ff552f; /* End call button color */
|
|
54
|
+
--avatar-screen-share-active-bg: #fff; /* Active share button background */
|
|
54
55
|
}
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
See [`examples/`](./examples) for complete working examples:
|
|
58
59
|
- [`nextjs`](./examples/nextjs) - Next.js App Router
|
|
59
60
|
- [`nextjs-client-events`](./examples/nextjs-client-events) - Client event tools (trivia game)
|
|
61
|
+
- [`nextjs-rpc`](./examples/nextjs-rpc) - Backend RPC + client events (trivia with server-side questions)
|
|
62
|
+
- [`nextjs-rpc-weather`](./examples/nextjs-rpc-weather) - Backend RPC only (weather assistant)
|
|
60
63
|
- [`nextjs-server-actions`](./examples/nextjs-server-actions) - Next.js with Server Actions
|
|
61
64
|
- [`react-router`](./examples/react-router) - React Router v7 framework mode
|
|
62
65
|
- [`express`](./examples/express) - Express + Vite
|
|
@@ -151,38 +154,41 @@ import { AvatarCall, AvatarVideo, ControlBar, UserVideo } from '@runwayml/avatar
|
|
|
151
154
|
|
|
152
155
|
### Render Props
|
|
153
156
|
|
|
154
|
-
All components support render props for complete control
|
|
157
|
+
All display components support render props for complete control. `AvatarVideo` receives a discriminated union with `status`:
|
|
155
158
|
|
|
156
159
|
```tsx
|
|
157
160
|
<AvatarVideo>
|
|
158
|
-
{(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
{(avatar) => {
|
|
162
|
+
switch (avatar.status) {
|
|
163
|
+
case 'connecting': return <Spinner />;
|
|
164
|
+
case 'waiting': return <Placeholder />;
|
|
165
|
+
case 'ready': return <VideoTrack trackRef={avatar.videoTrackRef} />;
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
164
168
|
</AvatarVideo>
|
|
165
169
|
```
|
|
166
170
|
|
|
167
171
|
### CSS Styling with Data Attributes
|
|
168
172
|
|
|
169
|
-
Style
|
|
173
|
+
Style components with the namespaced `data-avatar-*` attributes:
|
|
170
174
|
|
|
171
175
|
```tsx
|
|
172
176
|
<AvatarCall avatarId="music-superstar" connectUrl="/api/avatar/connect" className="my-avatar" />
|
|
173
177
|
```
|
|
174
178
|
|
|
175
179
|
```css
|
|
176
|
-
|
|
180
|
+
/* Style avatar video by connection status */
|
|
181
|
+
[data-avatar-video][data-avatar-status="connecting"] {
|
|
177
182
|
opacity: 0.5;
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
[data-avatar-video][data-avatar-status="ready"] {
|
|
186
|
+
opacity: 1;
|
|
182
187
|
}
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
/* Style control buttons */
|
|
190
|
+
[data-avatar-control][data-avatar-enabled="false"] {
|
|
191
|
+
opacity: 0.5;
|
|
186
192
|
}
|
|
187
193
|
```
|
|
188
194
|
|
|
@@ -233,6 +239,8 @@ Enable the screen share button by passing `showScreenShare` to `ControlBar`, and
|
|
|
233
239
|
</AvatarCall>
|
|
234
240
|
```
|
|
235
241
|
|
|
242
|
+
While sharing is active, the default `ControlBar` UI shows a sharing banner with a quick `Stop` action.
|
|
243
|
+
|
|
236
244
|
You can also start screen sharing automatically by passing a pre-captured stream via `initialScreenStream`. This is useful when you want to prompt the user for screen share permission before the session connects:
|
|
237
245
|
|
|
238
246
|
```tsx
|
|
@@ -346,6 +354,8 @@ function MediaControls() {
|
|
|
346
354
|
|
|
347
355
|
## Client Events
|
|
348
356
|
|
|
357
|
+
> **Compatibility:** Client events (tool calling) are supported on avatars that use a **preset voice**. Custom voice avatars do not currently support client events.
|
|
358
|
+
|
|
349
359
|
Avatars can trigger UI events via tool calls sent over the data channel. Define tools, pass them when creating a session, and subscribe on the client:
|
|
350
360
|
|
|
351
361
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -170,53 +170,6 @@ function parseClientEvent(payload) {
|
|
|
170
170
|
return null;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
async function hasMediaDevice(kind, timeoutMs = 1e3) {
|
|
174
|
-
try {
|
|
175
|
-
const timeoutPromise = new Promise(
|
|
176
|
-
(resolve) => setTimeout(() => resolve(false), timeoutMs)
|
|
177
|
-
);
|
|
178
|
-
const checkPromise = navigator.mediaDevices.enumerateDevices().then((devices) => devices.some((device) => device.kind === kind));
|
|
179
|
-
return await Promise.race([checkPromise, timeoutPromise]);
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function useDeviceAvailability(requestAudio, requestVideo) {
|
|
185
|
-
const [state, setState] = react.useState({
|
|
186
|
-
audio: requestAudio,
|
|
187
|
-
// Optimistically assume devices exist
|
|
188
|
-
video: requestVideo
|
|
189
|
-
});
|
|
190
|
-
react.useEffect(() => {
|
|
191
|
-
let cancelled = false;
|
|
192
|
-
async function checkDevices() {
|
|
193
|
-
const [hasAudio, hasVideo] = await Promise.all([
|
|
194
|
-
requestAudio ? hasMediaDevice("audioinput") : Promise.resolve(false),
|
|
195
|
-
requestVideo ? hasMediaDevice("videoinput") : Promise.resolve(false)
|
|
196
|
-
]);
|
|
197
|
-
if (!cancelled) {
|
|
198
|
-
setState({
|
|
199
|
-
audio: requestAudio && hasAudio,
|
|
200
|
-
video: requestVideo && hasVideo
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
checkDevices();
|
|
205
|
-
return () => {
|
|
206
|
-
cancelled = true;
|
|
207
|
-
};
|
|
208
|
-
}, [requestAudio, requestVideo]);
|
|
209
|
-
return state;
|
|
210
|
-
}
|
|
211
|
-
var MEDIA_DEVICE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
212
|
-
"NotAllowedError",
|
|
213
|
-
"NotFoundError",
|
|
214
|
-
"NotReadableError",
|
|
215
|
-
"OverconstrainedError"
|
|
216
|
-
]);
|
|
217
|
-
function isMediaDeviceError(error) {
|
|
218
|
-
return MEDIA_DEVICE_ERROR_NAMES.has(error.name);
|
|
219
|
-
}
|
|
220
173
|
var DEFAULT_ROOM_OPTIONS = {
|
|
221
174
|
adaptiveStream: false,
|
|
222
175
|
dynacast: false
|
|
@@ -238,6 +191,7 @@ function mapConnectionState(connectionState) {
|
|
|
238
191
|
var AvatarSessionContext = react.createContext(
|
|
239
192
|
null
|
|
240
193
|
);
|
|
194
|
+
var MediaDeviceErrorContext = react.createContext(null);
|
|
241
195
|
function AvatarSession({
|
|
242
196
|
credentials,
|
|
243
197
|
children,
|
|
@@ -250,12 +204,9 @@ function AvatarSession({
|
|
|
250
204
|
__unstable_roomOptions
|
|
251
205
|
}) {
|
|
252
206
|
const errorRef = react.useRef(null);
|
|
253
|
-
const deviceAvailability = useDeviceAvailability(requestAudio, requestVideo);
|
|
254
207
|
const handleError = (error) => {
|
|
255
208
|
onError?.(error);
|
|
256
|
-
|
|
257
|
-
errorRef.current = error;
|
|
258
|
-
}
|
|
209
|
+
errorRef.current = error;
|
|
259
210
|
};
|
|
260
211
|
const roomOptions = {
|
|
261
212
|
...DEFAULT_ROOM_OPTIONS,
|
|
@@ -267,8 +218,8 @@ function AvatarSession({
|
|
|
267
218
|
serverUrl: credentials.serverUrl,
|
|
268
219
|
token: credentials.token,
|
|
269
220
|
connect: true,
|
|
270
|
-
audio:
|
|
271
|
-
video:
|
|
221
|
+
audio: false,
|
|
222
|
+
video: false,
|
|
272
223
|
onDisconnected: () => onEnd?.(),
|
|
273
224
|
onError: handleError,
|
|
274
225
|
options: roomOptions,
|
|
@@ -280,6 +231,8 @@ function AvatarSession({
|
|
|
280
231
|
AvatarSessionContextInner,
|
|
281
232
|
{
|
|
282
233
|
sessionId: credentials.sessionId,
|
|
234
|
+
requestAudio,
|
|
235
|
+
requestVideo,
|
|
283
236
|
onEnd,
|
|
284
237
|
onClientEvent,
|
|
285
238
|
errorRef,
|
|
@@ -294,6 +247,8 @@ function AvatarSession({
|
|
|
294
247
|
}
|
|
295
248
|
function AvatarSessionContextInner({
|
|
296
249
|
sessionId,
|
|
250
|
+
requestAudio,
|
|
251
|
+
requestVideo,
|
|
297
252
|
onEnd,
|
|
298
253
|
onClientEvent,
|
|
299
254
|
errorRef,
|
|
@@ -329,6 +284,64 @@ function AvatarSessionContextInner({
|
|
|
329
284
|
});
|
|
330
285
|
};
|
|
331
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
|
+
);
|
|
332
345
|
react.useEffect(() => {
|
|
333
346
|
function handleDataReceived(payload) {
|
|
334
347
|
const event = parseClientEvent(payload);
|
|
@@ -357,7 +370,7 @@ function AvatarSessionContextInner({
|
|
|
357
370
|
error: errorRef.current,
|
|
358
371
|
end
|
|
359
372
|
};
|
|
360
|
-
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 }) });
|
|
361
374
|
}
|
|
362
375
|
function useAvatarSessionContext() {
|
|
363
376
|
const context = react.useContext(AvatarSessionContext);
|
|
@@ -368,6 +381,9 @@ function useAvatarSessionContext() {
|
|
|
368
381
|
}
|
|
369
382
|
return context;
|
|
370
383
|
}
|
|
384
|
+
function useMediaDeviceErrorContext() {
|
|
385
|
+
return react.useContext(MediaDeviceErrorContext);
|
|
386
|
+
}
|
|
371
387
|
function useAvatar() {
|
|
372
388
|
const remoteParticipants = componentsReact.useRemoteParticipants();
|
|
373
389
|
const avatarParticipant = remoteParticipants[0] ?? null;
|
|
@@ -433,8 +449,24 @@ function AvatarVideo({ children, ...props }) {
|
|
|
433
449
|
}
|
|
434
450
|
);
|
|
435
451
|
}
|
|
452
|
+
var NOOP_ASYNC = async () => {
|
|
453
|
+
};
|
|
454
|
+
function createCaptureController() {
|
|
455
|
+
if (typeof window === "undefined" || !("CaptureController" in window)) {
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
const controller = new window.CaptureController();
|
|
459
|
+
controller.setFocusBehavior("no-focus-change");
|
|
460
|
+
return controller;
|
|
461
|
+
}
|
|
436
462
|
function useLocalMedia() {
|
|
437
463
|
const { localParticipant } = componentsReact.useLocalParticipant();
|
|
464
|
+
const {
|
|
465
|
+
micError = null,
|
|
466
|
+
cameraError = null,
|
|
467
|
+
retryMic = NOOP_ASYNC,
|
|
468
|
+
retryCamera = NOOP_ASYNC
|
|
469
|
+
} = useMediaDeviceErrorContext() ?? {};
|
|
438
470
|
const audioDevices = componentsReact.useMediaDevices({ kind: "audioinput" });
|
|
439
471
|
const videoDevices = componentsReact.useMediaDevices({ kind: "videoinput" });
|
|
440
472
|
const hasMic = audioDevices?.length > 0;
|
|
@@ -458,7 +490,16 @@ function useLocalMedia() {
|
|
|
458
490
|
}
|
|
459
491
|
}, [localParticipant]);
|
|
460
492
|
const toggleScreenShare = react.useCallback(() => {
|
|
461
|
-
|
|
493
|
+
const next = !isScreenShareEnabledRef.current;
|
|
494
|
+
if (next) {
|
|
495
|
+
const controller = createCaptureController();
|
|
496
|
+
localParticipant?.setScreenShareEnabled(true, {
|
|
497
|
+
controller,
|
|
498
|
+
surfaceSwitching: "include"
|
|
499
|
+
});
|
|
500
|
+
} else {
|
|
501
|
+
localParticipant?.setScreenShareEnabled(false);
|
|
502
|
+
}
|
|
462
503
|
}, [localParticipant]);
|
|
463
504
|
const tracks = componentsReact.useTracks(
|
|
464
505
|
[{ source: livekitClient.Track.Source.Camera, withPlaceholder: true }],
|
|
@@ -480,7 +521,11 @@ function useLocalMedia() {
|
|
|
480
521
|
toggleMic,
|
|
481
522
|
toggleCamera,
|
|
482
523
|
toggleScreenShare,
|
|
483
|
-
localVideoTrackRef
|
|
524
|
+
localVideoTrackRef,
|
|
525
|
+
micError,
|
|
526
|
+
cameraError,
|
|
527
|
+
retryMic,
|
|
528
|
+
retryCamera
|
|
484
529
|
};
|
|
485
530
|
}
|
|
486
531
|
function ControlBar({
|
|
@@ -498,9 +543,17 @@ function ControlBar({
|
|
|
498
543
|
isScreenShareEnabled,
|
|
499
544
|
toggleMic,
|
|
500
545
|
toggleCamera,
|
|
501
|
-
toggleScreenShare
|
|
546
|
+
toggleScreenShare,
|
|
547
|
+
micError,
|
|
548
|
+
cameraError,
|
|
549
|
+
retryMic,
|
|
550
|
+
retryCamera
|
|
502
551
|
} = useLocalMedia();
|
|
503
552
|
const isActive = session.state === "active";
|
|
553
|
+
const handleStopScreenShare = react.useCallback(() => {
|
|
554
|
+
if (!isScreenShareEnabled) return;
|
|
555
|
+
toggleScreenShare();
|
|
556
|
+
}, [isScreenShareEnabled, toggleScreenShare]);
|
|
504
557
|
const state = {
|
|
505
558
|
isMicEnabled,
|
|
506
559
|
isCameraEnabled,
|
|
@@ -509,7 +562,11 @@ function ControlBar({
|
|
|
509
562
|
toggleCamera,
|
|
510
563
|
toggleScreenShare,
|
|
511
564
|
endCall: session.end,
|
|
512
|
-
isActive
|
|
565
|
+
isActive,
|
|
566
|
+
micError,
|
|
567
|
+
cameraError,
|
|
568
|
+
retryMic,
|
|
569
|
+
retryCamera
|
|
513
570
|
};
|
|
514
571
|
if (children) {
|
|
515
572
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(state) });
|
|
@@ -518,57 +575,71 @@ function ControlBar({
|
|
|
518
575
|
return null;
|
|
519
576
|
}
|
|
520
577
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...props, "data-avatar-control-bar": "", "data-avatar-active": isActive, children: [
|
|
521
|
-
|
|
522
|
-
"
|
|
523
|
-
{
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
578
|
+
showScreenShare && isScreenShareEnabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-avatar-share-indicator": "", "aria-live": "polite", children: [
|
|
579
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-avatar-share-label": "", children: "You're sharing your screen" }),
|
|
580
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-avatar-share-actions": "", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
581
|
+
"button",
|
|
582
|
+
{
|
|
583
|
+
type: "button",
|
|
584
|
+
onClick: handleStopScreenShare,
|
|
585
|
+
"data-avatar-share-action": "stop",
|
|
586
|
+
children: "Stop"
|
|
587
|
+
}
|
|
588
|
+
) })
|
|
589
|
+
] }),
|
|
590
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-avatar-controls": "", children: [
|
|
591
|
+
showMicrophone && /* @__PURE__ */ jsxRuntime.jsx(
|
|
592
|
+
"button",
|
|
593
|
+
{
|
|
594
|
+
type: "button",
|
|
595
|
+
onClick: toggleMic,
|
|
596
|
+
"data-avatar-control": "microphone",
|
|
597
|
+
"data-avatar-enabled": isMicEnabled,
|
|
598
|
+
"aria-label": isMicEnabled ? "Mute microphone" : "Unmute microphone",
|
|
599
|
+
children: microphoneIcon
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
showCamera && /* @__PURE__ */ jsxRuntime.jsx(
|
|
603
|
+
"button",
|
|
604
|
+
{
|
|
605
|
+
type: "button",
|
|
606
|
+
onClick: toggleCamera,
|
|
607
|
+
"data-avatar-control": "camera",
|
|
608
|
+
"data-avatar-enabled": isCameraEnabled,
|
|
609
|
+
"aria-label": isCameraEnabled ? "Turn off camera" : "Turn on camera",
|
|
610
|
+
children: cameraIcon
|
|
611
|
+
}
|
|
612
|
+
),
|
|
613
|
+
showScreenShare && /* @__PURE__ */ jsxRuntime.jsx(
|
|
614
|
+
"button",
|
|
615
|
+
{
|
|
616
|
+
type: "button",
|
|
617
|
+
onClick: toggleScreenShare,
|
|
618
|
+
"data-avatar-control": "screen-share",
|
|
619
|
+
"data-avatar-enabled": isScreenShareEnabled,
|
|
620
|
+
"aria-label": isScreenShareEnabled ? "Stop sharing screen" : "Share screen",
|
|
621
|
+
children: screenShareIcon
|
|
622
|
+
}
|
|
623
|
+
),
|
|
624
|
+
showEndCall && /* @__PURE__ */ jsxRuntime.jsx(
|
|
625
|
+
"button",
|
|
626
|
+
{
|
|
627
|
+
type: "button",
|
|
628
|
+
onClick: session.end,
|
|
629
|
+
"data-avatar-control": "end-call",
|
|
630
|
+
"data-avatar-enabled": true,
|
|
631
|
+
"aria-label": "End call",
|
|
632
|
+
children: phoneIcon
|
|
633
|
+
}
|
|
634
|
+
)
|
|
635
|
+
] })
|
|
565
636
|
] });
|
|
566
637
|
}
|
|
567
638
|
var microphoneIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
568
639
|
"svg",
|
|
569
640
|
{
|
|
570
|
-
width: "
|
|
571
|
-
height: "
|
|
641
|
+
width: "16",
|
|
642
|
+
height: "16",
|
|
572
643
|
viewBox: "0 0 24 24",
|
|
573
644
|
fill: "none",
|
|
574
645
|
stroke: "currentColor",
|
|
@@ -586,8 +657,8 @@ var microphoneIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
586
657
|
var cameraIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
587
658
|
"svg",
|
|
588
659
|
{
|
|
589
|
-
width: "
|
|
590
|
-
height: "
|
|
660
|
+
width: "16",
|
|
661
|
+
height: "16",
|
|
591
662
|
viewBox: "0 0 24 24",
|
|
592
663
|
fill: "none",
|
|
593
664
|
stroke: "currentColor",
|
|
@@ -604,8 +675,8 @@ var cameraIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
604
675
|
var screenShareIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
605
676
|
"svg",
|
|
606
677
|
{
|
|
607
|
-
width: "
|
|
608
|
-
height: "
|
|
678
|
+
width: "16",
|
|
679
|
+
height: "16",
|
|
609
680
|
viewBox: "0 0 24 24",
|
|
610
681
|
fill: "none",
|
|
611
682
|
stroke: "currentColor",
|
|
@@ -614,17 +685,19 @@ var screenShareIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
614
685
|
strokeLinejoin: "round",
|
|
615
686
|
"aria-hidden": "true",
|
|
616
687
|
children: [
|
|
617
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
618
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
619
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-3" }),
|
|
689
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 21h8" }),
|
|
690
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 17v4" }),
|
|
691
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m17 8 5-5" }),
|
|
692
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 3h5v5" })
|
|
620
693
|
]
|
|
621
694
|
}
|
|
622
695
|
);
|
|
623
696
|
var phoneIcon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
624
697
|
"svg",
|
|
625
698
|
{
|
|
626
|
-
width: "
|
|
627
|
-
height: "
|
|
699
|
+
width: "16",
|
|
700
|
+
height: "16",
|
|
628
701
|
viewBox: "8 14 24 12",
|
|
629
702
|
fill: "currentColor",
|
|
630
703
|
"aria-hidden": "true",
|