@robinandeer/rtc-session-components 0.1.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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/index.cjs +401 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +257 -0
- package/dist/index.d.ts +257 -0
- package/dist/index.js +386 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { createContext, useRef, useCallback, useContext, useState, useEffect } from 'react';
|
|
2
|
+
import { LiveKitRoom, RoomAudioRenderer, useRoomContext, useConnectionState, useRemoteParticipants, useTracks, isTrackReference, VideoTrack, useLocalParticipant, useMediaDevices, TrackToggle } from '@livekit/components-react';
|
|
3
|
+
export { RoomAudioRenderer as AudioRenderer } from '@livekit/components-react';
|
|
4
|
+
import { ParticipantEvent, Track, ConnectionState } from 'livekit-client';
|
|
5
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/components/AvatarSession.tsx
|
|
8
|
+
function mapConnectionState(connectionState) {
|
|
9
|
+
switch (connectionState) {
|
|
10
|
+
case ConnectionState.Connecting:
|
|
11
|
+
return "connecting";
|
|
12
|
+
case ConnectionState.Connected:
|
|
13
|
+
return "active";
|
|
14
|
+
case ConnectionState.Reconnecting:
|
|
15
|
+
return "connecting";
|
|
16
|
+
case ConnectionState.Disconnected:
|
|
17
|
+
return "ended";
|
|
18
|
+
default:
|
|
19
|
+
return "ended";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var AvatarSessionContext = createContext(null);
|
|
23
|
+
function AvatarSession({
|
|
24
|
+
credentials,
|
|
25
|
+
children,
|
|
26
|
+
audio = true,
|
|
27
|
+
video = true,
|
|
28
|
+
onEnd,
|
|
29
|
+
onError
|
|
30
|
+
}) {
|
|
31
|
+
const errorRef = useRef(null);
|
|
32
|
+
const handleError = (error) => {
|
|
33
|
+
errorRef.current = error;
|
|
34
|
+
onError?.(error);
|
|
35
|
+
};
|
|
36
|
+
return /* @__PURE__ */ jsxs(
|
|
37
|
+
LiveKitRoom,
|
|
38
|
+
{
|
|
39
|
+
serverUrl: credentials.livekitUrl,
|
|
40
|
+
token: credentials.token,
|
|
41
|
+
connect: true,
|
|
42
|
+
audio,
|
|
43
|
+
video,
|
|
44
|
+
onDisconnected: () => onEnd?.(),
|
|
45
|
+
onError: handleError,
|
|
46
|
+
options: {
|
|
47
|
+
adaptiveStream: true,
|
|
48
|
+
dynacast: true
|
|
49
|
+
},
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx(
|
|
52
|
+
AvatarSessionContextInner,
|
|
53
|
+
{
|
|
54
|
+
sessionId: credentials.sessionId,
|
|
55
|
+
onEnd,
|
|
56
|
+
errorRef,
|
|
57
|
+
children
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
/* @__PURE__ */ jsx(RoomAudioRenderer, {})
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
function AvatarSessionContextInner({
|
|
66
|
+
sessionId,
|
|
67
|
+
onEnd,
|
|
68
|
+
errorRef,
|
|
69
|
+
children
|
|
70
|
+
}) {
|
|
71
|
+
const room = useRoomContext();
|
|
72
|
+
const connectionState = useConnectionState();
|
|
73
|
+
const onEndRef = useRef(onEnd);
|
|
74
|
+
onEndRef.current = onEnd;
|
|
75
|
+
const end = useCallback(async () => {
|
|
76
|
+
try {
|
|
77
|
+
const encoder = new TextEncoder();
|
|
78
|
+
const data = encoder.encode(JSON.stringify({ type: "END_CALL" }));
|
|
79
|
+
await room.localParticipant.publishData(data, { reliable: true });
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
await room.disconnect();
|
|
83
|
+
onEndRef.current?.();
|
|
84
|
+
}, [room]);
|
|
85
|
+
const contextValue = {
|
|
86
|
+
state: mapConnectionState(connectionState),
|
|
87
|
+
sessionId,
|
|
88
|
+
error: errorRef.current,
|
|
89
|
+
end
|
|
90
|
+
};
|
|
91
|
+
return /* @__PURE__ */ jsx(AvatarSessionContext.Provider, { value: contextValue, children });
|
|
92
|
+
}
|
|
93
|
+
function useAvatarSessionContext() {
|
|
94
|
+
const context = useContext(AvatarSessionContext);
|
|
95
|
+
if (!context) {
|
|
96
|
+
throw new Error("useAvatarSessionContext must be used within an AvatarSession");
|
|
97
|
+
}
|
|
98
|
+
return context;
|
|
99
|
+
}
|
|
100
|
+
function useMaybeAvatarSessionContext() {
|
|
101
|
+
return useContext(AvatarSessionContext);
|
|
102
|
+
}
|
|
103
|
+
function useAvatar() {
|
|
104
|
+
const remoteParticipants = useRemoteParticipants();
|
|
105
|
+
const avatarParticipant = remoteParticipants[0] ?? null;
|
|
106
|
+
const avatarIdentity = avatarParticipant?.identity ?? null;
|
|
107
|
+
const [isSpeaking, setIsSpeaking] = useState(false);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!avatarParticipant) {
|
|
110
|
+
setIsSpeaking(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setIsSpeaking(avatarParticipant.isSpeaking);
|
|
114
|
+
const handleIsSpeakingChanged = (speaking) => {
|
|
115
|
+
setIsSpeaking(speaking);
|
|
116
|
+
};
|
|
117
|
+
avatarParticipant.on(ParticipantEvent.IsSpeakingChanged, handleIsSpeakingChanged);
|
|
118
|
+
return () => {
|
|
119
|
+
avatarParticipant.off(ParticipantEvent.IsSpeakingChanged, handleIsSpeakingChanged);
|
|
120
|
+
};
|
|
121
|
+
}, [avatarParticipant]);
|
|
122
|
+
const tracks = useTracks(
|
|
123
|
+
[
|
|
124
|
+
{ source: Track.Source.Camera, withPlaceholder: true },
|
|
125
|
+
{ source: Track.Source.Microphone, withPlaceholder: true }
|
|
126
|
+
],
|
|
127
|
+
{ onlySubscribed: true }
|
|
128
|
+
);
|
|
129
|
+
let videoTrackRef = null;
|
|
130
|
+
let audioTrackRef = null;
|
|
131
|
+
for (const trackRef of tracks) {
|
|
132
|
+
if (trackRef.participant.identity !== avatarIdentity) continue;
|
|
133
|
+
if (trackRef.source === Track.Source.Camera && !videoTrackRef) {
|
|
134
|
+
videoTrackRef = trackRef;
|
|
135
|
+
} else if (trackRef.source === Track.Source.Microphone && !audioTrackRef) {
|
|
136
|
+
audioTrackRef = trackRef;
|
|
137
|
+
}
|
|
138
|
+
if (videoTrackRef && audioTrackRef) break;
|
|
139
|
+
}
|
|
140
|
+
const hasVideo = videoTrackRef !== null && isTrackReference(videoTrackRef);
|
|
141
|
+
const hasAudio = audioTrackRef !== null && isTrackReference(audioTrackRef);
|
|
142
|
+
return {
|
|
143
|
+
participant: avatarParticipant,
|
|
144
|
+
videoTrackRef,
|
|
145
|
+
audioTrackRef,
|
|
146
|
+
isSpeaking,
|
|
147
|
+
hasVideo,
|
|
148
|
+
hasAudio
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/hooks/useAvatarSession.ts
|
|
153
|
+
function useAvatarSession() {
|
|
154
|
+
const context = useAvatarSessionContext();
|
|
155
|
+
return context;
|
|
156
|
+
}
|
|
157
|
+
function AvatarVideo({ children, ...props }) {
|
|
158
|
+
const session = useAvatarSession();
|
|
159
|
+
const { videoTrackRef, isSpeaking, hasVideo } = useAvatar();
|
|
160
|
+
const isConnecting = session.state === "connecting";
|
|
161
|
+
const state = {
|
|
162
|
+
hasVideo,
|
|
163
|
+
isConnecting,
|
|
164
|
+
isSpeaking,
|
|
165
|
+
trackRef: videoTrackRef
|
|
166
|
+
};
|
|
167
|
+
if (children) {
|
|
168
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(state) });
|
|
169
|
+
}
|
|
170
|
+
return /* @__PURE__ */ jsx(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
...props,
|
|
174
|
+
"data-has-video": hasVideo,
|
|
175
|
+
"data-connecting": isConnecting,
|
|
176
|
+
"data-speaking": isSpeaking,
|
|
177
|
+
children: hasVideo && videoTrackRef && isTrackReference(videoTrackRef) && /* @__PURE__ */ jsx(VideoTrack, { trackRef: videoTrackRef })
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
function useLocalMedia() {
|
|
182
|
+
const { localParticipant } = useLocalParticipant();
|
|
183
|
+
const audioDevices = useMediaDevices({ kind: "audioinput" });
|
|
184
|
+
const videoDevices = useMediaDevices({ kind: "videoinput" });
|
|
185
|
+
const hasMic = audioDevices.length > 0;
|
|
186
|
+
const hasCamera = videoDevices.length > 0;
|
|
187
|
+
const isMicEnabled = localParticipant?.isMicrophoneEnabled ?? false;
|
|
188
|
+
const isCameraEnabled = localParticipant?.isCameraEnabled ?? false;
|
|
189
|
+
const isScreenShareEnabled = localParticipant?.isScreenShareEnabled ?? false;
|
|
190
|
+
const isMicEnabledRef = useRef(isMicEnabled);
|
|
191
|
+
const isCameraEnabledRef = useRef(isCameraEnabled);
|
|
192
|
+
const isScreenShareEnabledRef = useRef(isScreenShareEnabled);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
isMicEnabledRef.current = isMicEnabled;
|
|
195
|
+
}, [isMicEnabled]);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
isCameraEnabledRef.current = isCameraEnabled;
|
|
198
|
+
}, [isCameraEnabled]);
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
isScreenShareEnabledRef.current = isScreenShareEnabled;
|
|
201
|
+
}, [isScreenShareEnabled]);
|
|
202
|
+
const toggleMic = useCallback(() => {
|
|
203
|
+
localParticipant?.setMicrophoneEnabled(!isMicEnabledRef.current);
|
|
204
|
+
}, [localParticipant]);
|
|
205
|
+
const toggleCamera = useCallback(() => {
|
|
206
|
+
localParticipant?.setCameraEnabled(!isCameraEnabledRef.current);
|
|
207
|
+
}, [localParticipant]);
|
|
208
|
+
const toggleScreenShare = useCallback(() => {
|
|
209
|
+
localParticipant?.setScreenShareEnabled(!isScreenShareEnabledRef.current);
|
|
210
|
+
}, [localParticipant]);
|
|
211
|
+
const tracks = useTracks([{ source: Track.Source.Camera, withPlaceholder: true }], {
|
|
212
|
+
onlySubscribed: false
|
|
213
|
+
});
|
|
214
|
+
const localIdentity = localParticipant?.identity;
|
|
215
|
+
const localVideoTrackRef = tracks.find(
|
|
216
|
+
(trackRef) => trackRef.participant.identity === localIdentity && trackRef.source === Track.Source.Camera
|
|
217
|
+
) ?? null;
|
|
218
|
+
return {
|
|
219
|
+
hasMic,
|
|
220
|
+
hasCamera,
|
|
221
|
+
isMicEnabled,
|
|
222
|
+
isCameraEnabled,
|
|
223
|
+
isScreenShareEnabled,
|
|
224
|
+
toggleMic,
|
|
225
|
+
toggleCamera,
|
|
226
|
+
toggleScreenShare,
|
|
227
|
+
localVideoTrackRef
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function UserVideo({ children, mirror = true, ...props }) {
|
|
231
|
+
const { localVideoTrackRef, isCameraEnabled } = useLocalMedia();
|
|
232
|
+
const hasVideo = localVideoTrackRef !== null && isTrackReference(localVideoTrackRef);
|
|
233
|
+
const state = {
|
|
234
|
+
hasVideo,
|
|
235
|
+
isCameraEnabled,
|
|
236
|
+
trackRef: localVideoTrackRef
|
|
237
|
+
};
|
|
238
|
+
if (children) {
|
|
239
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(state) });
|
|
240
|
+
}
|
|
241
|
+
return /* @__PURE__ */ jsx(
|
|
242
|
+
"div",
|
|
243
|
+
{
|
|
244
|
+
...props,
|
|
245
|
+
"data-has-video": hasVideo,
|
|
246
|
+
"data-camera-enabled": isCameraEnabled,
|
|
247
|
+
"data-mirror": mirror,
|
|
248
|
+
children: hasVideo && localVideoTrackRef && isTrackReference(localVideoTrackRef) && /* @__PURE__ */ jsx(VideoTrack, { trackRef: localVideoTrackRef })
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
function ControlBar({
|
|
253
|
+
children,
|
|
254
|
+
showMicrophone = true,
|
|
255
|
+
showCamera = true,
|
|
256
|
+
showScreenShare = false,
|
|
257
|
+
showEndCall = true,
|
|
258
|
+
...props
|
|
259
|
+
}) {
|
|
260
|
+
const session = useAvatarSession();
|
|
261
|
+
const { isMicEnabled, isCameraEnabled, toggleMic, toggleCamera } = useLocalMedia();
|
|
262
|
+
const isActive = session.state === "active";
|
|
263
|
+
const state = {
|
|
264
|
+
isMicEnabled,
|
|
265
|
+
isCameraEnabled,
|
|
266
|
+
toggleMic,
|
|
267
|
+
toggleCamera,
|
|
268
|
+
endCall: session.end,
|
|
269
|
+
isActive
|
|
270
|
+
};
|
|
271
|
+
if (children) {
|
|
272
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(state) });
|
|
273
|
+
}
|
|
274
|
+
if (!isActive) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return /* @__PURE__ */ jsxs("div", { ...props, "data-active": isActive, children: [
|
|
278
|
+
showMicrophone && /* @__PURE__ */ jsx(
|
|
279
|
+
"button",
|
|
280
|
+
{
|
|
281
|
+
type: "button",
|
|
282
|
+
onClick: toggleMic,
|
|
283
|
+
"data-control": "microphone",
|
|
284
|
+
"data-enabled": isMicEnabled,
|
|
285
|
+
"aria-label": isMicEnabled ? "Mute microphone" : "Unmute microphone",
|
|
286
|
+
children: /* @__PURE__ */ jsx(MicrophoneIcon, {})
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
showCamera && /* @__PURE__ */ jsx(
|
|
290
|
+
"button",
|
|
291
|
+
{
|
|
292
|
+
type: "button",
|
|
293
|
+
onClick: toggleCamera,
|
|
294
|
+
"data-control": "camera",
|
|
295
|
+
"data-enabled": isCameraEnabled,
|
|
296
|
+
"aria-label": isCameraEnabled ? "Turn off camera" : "Turn on camera",
|
|
297
|
+
children: /* @__PURE__ */ jsx(CameraIcon, {})
|
|
298
|
+
}
|
|
299
|
+
),
|
|
300
|
+
showScreenShare && /* @__PURE__ */ jsx(TrackToggle, { source: Track.Source.ScreenShare, showIcon: false, "data-control": "screen-share", children: /* @__PURE__ */ jsx(ScreenShareIcon, {}) }),
|
|
301
|
+
showEndCall && /* @__PURE__ */ jsx("button", { type: "button", onClick: session.end, "data-control": "end-call", "aria-label": "End call", children: /* @__PURE__ */ jsx("span", { children: "Leave" }) })
|
|
302
|
+
] });
|
|
303
|
+
}
|
|
304
|
+
function MicrophoneIcon() {
|
|
305
|
+
return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
306
|
+
"path",
|
|
307
|
+
{
|
|
308
|
+
strokeLinecap: "round",
|
|
309
|
+
strokeLinejoin: "round",
|
|
310
|
+
strokeWidth: 2,
|
|
311
|
+
d: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"
|
|
312
|
+
}
|
|
313
|
+
) });
|
|
314
|
+
}
|
|
315
|
+
function CameraIcon() {
|
|
316
|
+
return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
317
|
+
"path",
|
|
318
|
+
{
|
|
319
|
+
strokeLinecap: "round",
|
|
320
|
+
strokeLinejoin: "round",
|
|
321
|
+
strokeWidth: 2,
|
|
322
|
+
d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
|
323
|
+
}
|
|
324
|
+
) });
|
|
325
|
+
}
|
|
326
|
+
function ScreenShareIcon() {
|
|
327
|
+
return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
328
|
+
"path",
|
|
329
|
+
{
|
|
330
|
+
strokeLinecap: "round",
|
|
331
|
+
strokeLinejoin: "round",
|
|
332
|
+
strokeWidth: 2,
|
|
333
|
+
d: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
334
|
+
}
|
|
335
|
+
) });
|
|
336
|
+
}
|
|
337
|
+
function ScreenShareVideo({ children, ...props }) {
|
|
338
|
+
const { localParticipant } = useLocalParticipant();
|
|
339
|
+
const tracks = useTracks(
|
|
340
|
+
[{ source: Track.Source.ScreenShare, withPlaceholder: false }],
|
|
341
|
+
{ onlySubscribed: false }
|
|
342
|
+
);
|
|
343
|
+
const localIdentity = localParticipant?.identity;
|
|
344
|
+
const screenShareTrackRef = tracks.find(
|
|
345
|
+
(trackRef) => trackRef.participant.identity === localIdentity && trackRef.source === Track.Source.ScreenShare
|
|
346
|
+
) ?? null;
|
|
347
|
+
const isSharing = screenShareTrackRef !== null && isTrackReference(screenShareTrackRef);
|
|
348
|
+
const state = {
|
|
349
|
+
isSharing,
|
|
350
|
+
trackRef: screenShareTrackRef
|
|
351
|
+
};
|
|
352
|
+
if (children) {
|
|
353
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(state) });
|
|
354
|
+
}
|
|
355
|
+
if (!isSharing) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
return /* @__PURE__ */ jsx("div", { ...props, "data-sharing": isSharing, children: screenShareTrackRef && isTrackReference(screenShareTrackRef) && /* @__PURE__ */ jsx(VideoTrack, { trackRef: screenShareTrackRef }) });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/api/consume.ts
|
|
362
|
+
var DEFAULT_BASE_URL = "https://api.runwayml.com/v1";
|
|
363
|
+
async function consumeSession(options) {
|
|
364
|
+
const { sessionId, baseUrl = DEFAULT_BASE_URL } = options;
|
|
365
|
+
const response = await fetch(`${baseUrl}/realtime/sessions/${sessionId}/consume`, {
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: {
|
|
368
|
+
"Content-Type": "application/json"
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
const errorText = await response.text();
|
|
373
|
+
throw new Error(`Failed to consume session: ${response.status} ${errorText}`);
|
|
374
|
+
}
|
|
375
|
+
const data = await response.json();
|
|
376
|
+
return {
|
|
377
|
+
sessionId,
|
|
378
|
+
livekitUrl: data.url,
|
|
379
|
+
token: data.token,
|
|
380
|
+
roomName: data.roomName
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export { AvatarSession, AvatarVideo, ControlBar, ScreenShareVideo, UserVideo, consumeSession, useAvatar, useAvatarSession, useAvatarSessionContext, useLocalMedia, useMaybeAvatarSessionContext };
|
|
385
|
+
//# sourceMappingURL=index.js.map
|
|
386
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/AvatarSession.tsx","../src/hooks/useAvatar.ts","../src/hooks/useAvatarSession.ts","../src/components/AvatarVideo.tsx","../src/hooks/useLocalMedia.ts","../src/components/UserVideo.tsx","../src/components/ControlBar.tsx","../src/components/ScreenShareVideo.tsx","../src/api/consume.ts"],"names":["jsx","isTrackReference","useRef","useEffect","useCallback","useTracks","Track","Fragment","VideoTrack","jsxs","useLocalParticipant"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\n/**\n * AvatarSession Component\n *\n * Provides the session context for avatar interactions.\n * Wraps LiveKit's LiveKitRoom internally while exposing a clean API.\n *\n * @example\n * ```tsx\n * <AvatarSession credentials={credentials} onEnd={handleEnd}>\n * <AvatarVideo />\n * <ControlBar />\n * </AvatarSession>\n * ```\n */\n\nimport {\n createContext,\n useContext,\n useCallback,\n useRef,\n type ReactNode,\n} from 'react';\nimport {\n LiveKitRoom,\n useConnectionState,\n useRoomContext,\n RoomAudioRenderer,\n} from '@livekit/components-react';\nimport { ConnectionState } from 'livekit-client';\nimport type {\n SessionState,\n AvatarSessionContextValue,\n AvatarSessionProps,\n} from '../types';\n\n/**\n * Maps LiveKit connection state to our session state\n */\nfunction mapConnectionState(connectionState: ConnectionState): SessionState {\n switch (connectionState) {\n case ConnectionState.Connecting:\n return 'connecting';\n case ConnectionState.Connected:\n return 'active';\n case ConnectionState.Reconnecting:\n return 'connecting';\n case ConnectionState.Disconnected:\n return 'ended';\n default:\n return 'ended';\n }\n}\n\nconst AvatarSessionContext = createContext<AvatarSessionContextValue | null>(null);\n\n/**\n * AvatarSession component - the main entry point for avatar sessions\n *\n * Renders children within a LiveKit room context and provides session state.\n * This is a headless component that renders minimal DOM.\n */\nexport function AvatarSession({\n credentials,\n children,\n audio = true,\n video = true,\n onEnd,\n onError,\n}: AvatarSessionProps) {\n const errorRef = useRef<Error | null>(null);\n\n const handleError = (error: Error) => {\n errorRef.current = error;\n onError?.(error);\n };\n\n return (\n <LiveKitRoom\n serverUrl={credentials.livekitUrl}\n token={credentials.token}\n connect={true}\n audio={audio}\n video={video}\n onDisconnected={() => onEnd?.()}\n onError={handleError}\n options={{\n adaptiveStream: true,\n dynacast: true,\n }}\n >\n <AvatarSessionContextInner\n sessionId={credentials.sessionId}\n onEnd={onEnd}\n errorRef={errorRef}\n >\n {children}\n </AvatarSessionContextInner>\n <RoomAudioRenderer />\n </LiveKitRoom>\n );\n}\n\n/**\n * Inner context provider that has access to LiveKit room context\n */\nfunction AvatarSessionContextInner({\n sessionId,\n onEnd,\n errorRef,\n children,\n}: {\n sessionId: string;\n onEnd?: () => void;\n errorRef: React.RefObject<Error | null>;\n children: ReactNode;\n}) {\n const room = useRoomContext();\n const connectionState = useConnectionState();\n const onEndRef = useRef(onEnd);\n onEndRef.current = onEnd;\n\n const end = useCallback(async () => {\n try {\n // Send END_CALL message to the avatar\n const encoder = new TextEncoder();\n const data = encoder.encode(JSON.stringify({ type: 'END_CALL' }));\n await room.localParticipant.publishData(data, { reliable: true });\n } catch {\n // Ignore errors when sending end message\n }\n\n await room.disconnect();\n onEndRef.current?.();\n }, [room]);\n\n const contextValue: AvatarSessionContextValue = {\n state: mapConnectionState(connectionState),\n sessionId,\n error: errorRef.current,\n end,\n };\n\n return (\n <AvatarSessionContext.Provider value={contextValue}>\n {children}\n </AvatarSessionContext.Provider>\n );\n}\n\n/**\n * Hook to access the avatar session context\n * Must be used within an AvatarSession component\n */\nexport function useAvatarSessionContext(): AvatarSessionContextValue {\n const context = useContext(AvatarSessionContext);\n if (!context) {\n throw new Error('useAvatarSessionContext must be used within an AvatarSession');\n }\n return context;\n}\n\n/**\n * Hook to optionally access the avatar session context\n * Returns null if not within an AvatarSession\n */\nexport function useMaybeAvatarSessionContext(): AvatarSessionContextValue | null {\n return useContext(AvatarSessionContext);\n}\n","'use client';\n\n/**\n * useAvatar Hook\n *\n * Provides access to the remote avatar participant's video and audio tracks.\n * Uses LiveKit React hooks internally but exposes a clean API.\n *\n * @example\n * ```tsx\n * function AvatarDisplay() {\n * const { videoTrackRef, isSpeaking, hasVideo } = useAvatar();\n *\n * if (!hasVideo) {\n * return <Placeholder />;\n * }\n *\n * return (\n * <div data-speaking={isSpeaking}>\n * <VideoTrack trackRef={videoTrackRef} />\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useState, useEffect } from 'react';\nimport {\n useRemoteParticipants,\n useTracks,\n isTrackReference,\n type TrackReferenceOrPlaceholder,\n} from '@livekit/components-react';\nimport {\n Track,\n ParticipantEvent,\n} from 'livekit-client';\nimport type { UseAvatarReturn } from '../types';\n\n/**\n * Hook to access the remote avatar participant's tracks and state\n *\n * @returns Avatar participant info, track references, and speaking state\n */\nexport function useAvatar(): UseAvatarReturn {\n const remoteParticipants = useRemoteParticipants();\n const avatarParticipant = remoteParticipants[0] ?? null;\n const avatarIdentity = avatarParticipant?.identity ?? null;\n\n const [isSpeaking, setIsSpeaking] = useState(false);\n\n useEffect(() => {\n if (!avatarParticipant) {\n setIsSpeaking(false);\n return;\n }\n\n setIsSpeaking(avatarParticipant.isSpeaking);\n\n const handleIsSpeakingChanged = (speaking: boolean) => {\n setIsSpeaking(speaking);\n };\n\n avatarParticipant.on(ParticipantEvent.IsSpeakingChanged, handleIsSpeakingChanged);\n\n return () => {\n avatarParticipant.off(ParticipantEvent.IsSpeakingChanged, handleIsSpeakingChanged);\n };\n }, [avatarParticipant]);\n\n const tracks = useTracks(\n [\n { source: Track.Source.Camera, withPlaceholder: true },\n { source: Track.Source.Microphone, withPlaceholder: true },\n ],\n { onlySubscribed: true }\n );\n\n let videoTrackRef: TrackReferenceOrPlaceholder | null = null;\n let audioTrackRef: TrackReferenceOrPlaceholder | null = null;\n\n for (const trackRef of tracks) {\n if (trackRef.participant.identity !== avatarIdentity) continue;\n\n if (trackRef.source === Track.Source.Camera && !videoTrackRef) {\n videoTrackRef = trackRef;\n } else if (trackRef.source === Track.Source.Microphone && !audioTrackRef) {\n audioTrackRef = trackRef;\n }\n\n if (videoTrackRef && audioTrackRef) break;\n }\n\n const hasVideo = videoTrackRef !== null && isTrackReference(videoTrackRef);\n const hasAudio = audioTrackRef !== null && isTrackReference(audioTrackRef);\n\n return {\n participant: avatarParticipant,\n videoTrackRef,\n audioTrackRef,\n isSpeaking,\n hasVideo,\n hasAudio,\n };\n}\n","'use client';\n\n/**\n * useAvatarSession Hook\n *\n * Provides access to the current avatar session state.\n * Returns a discriminated union based on session state for type-safe UI rendering.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const session = useAvatarSession();\n *\n * if (session.state === 'connecting') {\n * return <Loading />;\n * }\n *\n * if (session.state === 'error') {\n * return <Error message={session.error.message} />;\n * }\n *\n * return <ActiveSession onEnd={session.end} />;\n * }\n * ```\n */\n\nimport { useAvatarSessionContext } from '../components/AvatarSession';\nimport type { AvatarSessionContextValue } from '../types';\n\n/**\n * Discriminated union types for type-safe session state handling\n */\nexport type UseAvatarSessionReturn =\n | { state: 'idle'; sessionId: string; error: null; end: () => Promise<void> }\n | { state: 'connecting'; sessionId: string; error: null; end: () => Promise<void> }\n | { state: 'active'; sessionId: string; error: null; end: () => Promise<void> }\n | { state: 'ending'; sessionId: string; error: null; end: () => Promise<void> }\n | { state: 'ended'; sessionId: string; error: null; end: () => Promise<void> }\n | { state: 'error'; sessionId: string; error: Error; end: () => Promise<void> };\n\n/**\n * Hook to access the current avatar session state\n *\n * @returns Session state as a discriminated union\n */\nexport function useAvatarSession(): UseAvatarSessionReturn {\n const context = useAvatarSessionContext();\n return context as UseAvatarSessionReturn;\n}\n\nexport type { AvatarSessionContextValue };\n","'use client';\n\nimport type { ReactNode, ComponentPropsWithoutRef } from 'react';\nimport { VideoTrack, isTrackReference } from '@livekit/components-react';\nimport { useAvatar } from '../hooks/useAvatar';\nimport { useAvatarSession } from '../hooks/useAvatarSession';\nimport type { UseAvatarReturn } from '../types';\n\nexport interface AvatarVideoState {\n hasVideo: boolean;\n isConnecting: boolean;\n isSpeaking: boolean;\n trackRef: UseAvatarReturn['videoTrackRef'];\n}\n\nexport interface AvatarVideoProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {\n children?: (state: AvatarVideoState) => ReactNode;\n}\n\nexport function AvatarVideo({ children, ...props }: AvatarVideoProps) {\n const session = useAvatarSession();\n const { videoTrackRef, isSpeaking, hasVideo } = useAvatar();\n\n const isConnecting = session.state === 'connecting';\n\n const state: AvatarVideoState = {\n hasVideo,\n isConnecting,\n isSpeaking,\n trackRef: videoTrackRef,\n };\n\n if (children) {\n return <>{children(state)}</>;\n }\n\n return (\n <div\n {...props}\n data-has-video={hasVideo}\n data-connecting={isConnecting}\n data-speaking={isSpeaking}\n >\n {hasVideo && videoTrackRef && isTrackReference(videoTrackRef) && (\n <VideoTrack trackRef={videoTrackRef} />\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useCallback, useRef, useEffect } from 'react';\nimport { useLocalParticipant, useMediaDevices, useTracks } from '@livekit/components-react';\nimport { Track } from 'livekit-client';\nimport type { UseLocalMediaReturn } from '../types';\n\nexport function useLocalMedia(): UseLocalMediaReturn {\n const { localParticipant } = useLocalParticipant();\n\n const audioDevices = useMediaDevices({ kind: 'audioinput' });\n const videoDevices = useMediaDevices({ kind: 'videoinput' });\n\n const hasMic = audioDevices.length > 0;\n const hasCamera = videoDevices.length > 0;\n\n const isMicEnabled = localParticipant?.isMicrophoneEnabled ?? false;\n const isCameraEnabled = localParticipant?.isCameraEnabled ?? false;\n const isScreenShareEnabled = localParticipant?.isScreenShareEnabled ?? false;\n\n const isMicEnabledRef = useRef(isMicEnabled);\n const isCameraEnabledRef = useRef(isCameraEnabled);\n const isScreenShareEnabledRef = useRef(isScreenShareEnabled);\n\n useEffect(() => {\n isMicEnabledRef.current = isMicEnabled;\n }, [isMicEnabled]);\n\n useEffect(() => {\n isCameraEnabledRef.current = isCameraEnabled;\n }, [isCameraEnabled]);\n\n useEffect(() => {\n isScreenShareEnabledRef.current = isScreenShareEnabled;\n }, [isScreenShareEnabled]);\n\n const toggleMic = useCallback(() => {\n localParticipant?.setMicrophoneEnabled(!isMicEnabledRef.current);\n }, [localParticipant]);\n\n const toggleCamera = useCallback(() => {\n localParticipant?.setCameraEnabled(!isCameraEnabledRef.current);\n }, [localParticipant]);\n\n const toggleScreenShare = useCallback(() => {\n localParticipant?.setScreenShareEnabled(!isScreenShareEnabledRef.current);\n }, [localParticipant]);\n\n const tracks = useTracks([{ source: Track.Source.Camera, withPlaceholder: true }], {\n onlySubscribed: false,\n });\n\n const localIdentity = localParticipant?.identity;\n\n const localVideoTrackRef =\n tracks.find(\n (trackRef) =>\n trackRef.participant.identity === localIdentity && trackRef.source === Track.Source.Camera\n ) ?? null;\n\n return {\n hasMic,\n hasCamera,\n isMicEnabled,\n isCameraEnabled,\n isScreenShareEnabled,\n toggleMic,\n toggleCamera,\n toggleScreenShare,\n localVideoTrackRef,\n };\n}\n","'use client';\n\nimport type { ReactNode, ComponentPropsWithoutRef } from 'react';\nimport { VideoTrack, isTrackReference } from '@livekit/components-react';\nimport { useLocalMedia } from '../hooks/useLocalMedia';\nimport type { UseLocalMediaReturn } from '../types';\n\nexport interface UserVideoState {\n hasVideo: boolean;\n isCameraEnabled: boolean;\n trackRef: UseLocalMediaReturn['localVideoTrackRef'];\n}\n\nexport interface UserVideoProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {\n mirror?: boolean;\n children?: (state: UserVideoState) => ReactNode;\n}\n\nexport function UserVideo({ children, mirror = true, ...props }: UserVideoProps) {\n const { localVideoTrackRef, isCameraEnabled } = useLocalMedia();\n\n const hasVideo = localVideoTrackRef !== null && isTrackReference(localVideoTrackRef);\n\n const state: UserVideoState = {\n hasVideo,\n isCameraEnabled,\n trackRef: localVideoTrackRef,\n };\n\n if (children) {\n return <>{children(state)}</>;\n }\n\n return (\n <div\n {...props}\n data-has-video={hasVideo}\n data-camera-enabled={isCameraEnabled}\n data-mirror={mirror}\n >\n {hasVideo && localVideoTrackRef && isTrackReference(localVideoTrackRef) && (\n <VideoTrack trackRef={localVideoTrackRef} />\n )}\n </div>\n );\n}\n","'use client';\n\nimport type { ReactNode, ComponentPropsWithoutRef } from 'react';\nimport { TrackToggle } from '@livekit/components-react';\nimport { Track } from 'livekit-client';\nimport { useLocalMedia } from '../hooks/useLocalMedia';\nimport { useAvatarSession } from '../hooks/useAvatarSession';\n\nexport interface ControlBarState {\n isMicEnabled: boolean;\n isCameraEnabled: boolean;\n toggleMic: () => void;\n toggleCamera: () => void;\n endCall: () => Promise<void>;\n isActive: boolean;\n}\n\nexport interface ControlBarProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {\n showMicrophone?: boolean;\n showCamera?: boolean;\n showScreenShare?: boolean;\n showEndCall?: boolean;\n children?: (state: ControlBarState) => ReactNode;\n}\n\nexport function ControlBar({\n children,\n showMicrophone = true,\n showCamera = true,\n showScreenShare = false,\n showEndCall = true,\n ...props\n}: ControlBarProps) {\n const session = useAvatarSession();\n const { isMicEnabled, isCameraEnabled, toggleMic, toggleCamera } = useLocalMedia();\n\n const isActive = session.state === 'active';\n\n const state: ControlBarState = {\n isMicEnabled,\n isCameraEnabled,\n toggleMic,\n toggleCamera,\n endCall: session.end,\n isActive,\n };\n\n if (children) {\n return <>{children(state)}</>;\n }\n\n if (!isActive) {\n return null;\n }\n\n return (\n <div {...props} data-active={isActive}>\n {showMicrophone && (\n <button\n type=\"button\"\n onClick={toggleMic}\n data-control=\"microphone\"\n data-enabled={isMicEnabled}\n aria-label={isMicEnabled ? 'Mute microphone' : 'Unmute microphone'}\n >\n <MicrophoneIcon />\n </button>\n )}\n {showCamera && (\n <button\n type=\"button\"\n onClick={toggleCamera}\n data-control=\"camera\"\n data-enabled={isCameraEnabled}\n aria-label={isCameraEnabled ? 'Turn off camera' : 'Turn on camera'}\n >\n <CameraIcon />\n </button>\n )}\n {showScreenShare && (\n <TrackToggle source={Track.Source.ScreenShare} showIcon={false} data-control=\"screen-share\">\n <ScreenShareIcon />\n </TrackToggle>\n )}\n {showEndCall && (\n <button type=\"button\" onClick={session.end} data-control=\"end-call\" aria-label=\"End call\">\n <span>Leave</span>\n </button>\n )}\n </div>\n );\n}\n\nfunction MicrophoneIcon() {\n return (\n <svg width=\"20\" height=\"20\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z\"\n />\n </svg>\n );\n}\n\nfunction CameraIcon() {\n return (\n <svg width=\"20\" height=\"20\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z\"\n />\n </svg>\n );\n}\n\nfunction ScreenShareIcon() {\n return (\n <svg width=\"20\" height=\"20\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n );\n}\n","'use client';\n\nimport type { ReactNode, ComponentPropsWithoutRef } from 'react';\nimport {\n useLocalParticipant,\n useTracks,\n VideoTrack,\n isTrackReference,\n type TrackReferenceOrPlaceholder,\n} from '@livekit/components-react';\nimport { Track } from 'livekit-client';\n\nexport interface ScreenShareVideoState {\n isSharing: boolean;\n trackRef: TrackReferenceOrPlaceholder | null;\n}\n\nexport interface ScreenShareVideoProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {\n children?: (state: ScreenShareVideoState) => ReactNode;\n}\n\nexport function ScreenShareVideo({ children, ...props }: ScreenShareVideoProps) {\n const { localParticipant } = useLocalParticipant();\n\n const tracks = useTracks(\n [{ source: Track.Source.ScreenShare, withPlaceholder: false }],\n { onlySubscribed: false }\n );\n\n const localIdentity = localParticipant?.identity;\n\n const screenShareTrackRef = tracks.find(\n (trackRef) =>\n trackRef.participant.identity === localIdentity &&\n trackRef.source === Track.Source.ScreenShare\n ) ?? null;\n\n const isSharing = screenShareTrackRef !== null && isTrackReference(screenShareTrackRef);\n\n const state: ScreenShareVideoState = {\n isSharing,\n trackRef: screenShareTrackRef,\n };\n\n if (children) {\n return <>{children(state)}</>;\n }\n\n if (!isSharing) {\n return null;\n }\n\n return (\n <div {...props} data-sharing={isSharing}>\n {screenShareTrackRef && isTrackReference(screenShareTrackRef) && (\n <VideoTrack trackRef={screenShareTrackRef} />\n )}\n </div>\n );\n}\n","/**\n * Runway API - Consume Session\n *\n * Client for the Runway /consume endpoint.\n * Retrieves LiveKit credentials for an existing session.\n *\n * @example\n * ```tsx\n * const credentials = await consumeSession({\n * sessionId: 'sess_abc123',\n * });\n *\n * // Use credentials with AvatarSession\n * <AvatarSession credentials={credentials}>\n * ...\n * </AvatarSession>\n * ```\n */\n\nimport type { SessionCredentials, ConsumeSessionOptions } from '../types';\n\nconst DEFAULT_BASE_URL = 'https://api.runwayml.com/v1';\n\n/**\n * Consume a session to get LiveKit credentials\n *\n * This calls the Runway API's /consume endpoint to retrieve\n * the LiveKit connection details for an existing session.\n *\n * @param options - Session ID and optional base URL\n * @returns SessionCredentials for connecting to the avatar\n */\nexport async function consumeSession(\n options: ConsumeSessionOptions\n): Promise<SessionCredentials> {\n const { sessionId, baseUrl = DEFAULT_BASE_URL } = options;\n\n const response = await fetch(`${baseUrl}/realtime/sessions/${sessionId}/consume`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to consume session: ${response.status} ${errorText}`);\n }\n\n const data = await response.json();\n\n return {\n sessionId,\n livekitUrl: data.url,\n token: data.token,\n roomName: data.roomName,\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@robinandeer/rtc-session-components",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless React components for WebRTC video sessions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"clean": "rm -rf dist",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"lint": "biome lint src/",
|
|
34
|
+
"lint:fix": "biome lint --write src/",
|
|
35
|
+
"format": "biome format --write src/",
|
|
36
|
+
"check": "biome check src/",
|
|
37
|
+
"typecheck": "tsc --noEmit"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"react",
|
|
41
|
+
"webrtc",
|
|
42
|
+
"livekit",
|
|
43
|
+
"video",
|
|
44
|
+
"streaming",
|
|
45
|
+
"hooks"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=18",
|
|
50
|
+
"react-dom": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"livekit-client": "^2.7.1",
|
|
54
|
+
"@livekit/components-react": "^2.8.2"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@biomejs/biome": "^2.3.13",
|
|
58
|
+
"@types/bun": "^1.3.7",
|
|
59
|
+
"@types/react": "^18.3.0",
|
|
60
|
+
"@types/react-dom": "^18.3.0",
|
|
61
|
+
"react": "^18.3.0",
|
|
62
|
+
"react-dom": "^18.3.0",
|
|
63
|
+
"tsup": "^8.0.0",
|
|
64
|
+
"typescript": "^5.4.0",
|
|
65
|
+
"vitest": "^2.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|