@fishjam-cloud/react-client 0.5.5 → 0.7.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.
Files changed (83) hide show
  1. package/dist/DeviceManager.d.ts +3 -4
  2. package/dist/DeviceManager.d.ts.map +1 -1
  3. package/dist/DeviceManager.js +25 -20
  4. package/dist/constraints.d.ts +2 -21
  5. package/dist/constraints.d.ts.map +1 -1
  6. package/dist/constraints.js +3 -7
  7. package/dist/fishjamProvider.d.ts +12 -0
  8. package/dist/fishjamProvider.d.ts.map +1 -0
  9. package/dist/fishjamProvider.js +39 -0
  10. package/dist/hooks/deviceManagers/useAudioDeviceManager.d.ts +2 -0
  11. package/dist/hooks/deviceManagers/useAudioDeviceManager.d.ts.map +1 -0
  12. package/dist/hooks/deviceManagers/useAudioDeviceManager.js +7 -0
  13. package/dist/hooks/deviceManagers/useDeviceManager.d.ts +4 -0
  14. package/dist/hooks/deviceManagers/useDeviceManager.d.ts.map +1 -0
  15. package/dist/hooks/deviceManagers/useDeviceManager.js +39 -0
  16. package/dist/hooks/deviceManagers/useVideoDeviceManager.d.ts +2 -0
  17. package/dist/hooks/deviceManagers/useVideoDeviceManager.d.ts.map +1 -0
  18. package/dist/hooks/deviceManagers/useVideoDeviceManager.js +7 -0
  19. package/dist/hooks/internal.d.ts +2 -0
  20. package/dist/hooks/internal.d.ts.map +1 -0
  21. package/dist/hooks/internal.js +1 -0
  22. package/dist/hooks/public.d.ts +9 -0
  23. package/dist/hooks/public.d.ts.map +1 -0
  24. package/dist/hooks/public.js +8 -0
  25. package/dist/hooks/useConnection.d.ts +4 -0
  26. package/dist/hooks/useConnection.d.ts.map +1 -0
  27. package/dist/hooks/useConnection.js +12 -0
  28. package/dist/hooks/useDevices.d.ts +7 -0
  29. package/dist/hooks/useDevices.d.ts.map +1 -0
  30. package/dist/hooks/useDevices.js +84 -0
  31. package/dist/hooks/useFishjamClientState.d.ts +11 -0
  32. package/dist/hooks/useFishjamClientState.d.ts.map +1 -0
  33. package/dist/hooks/useFishjamClientState.js +100 -0
  34. package/dist/hooks/useFishjamClient_DO_NOT_USE.d.ts +2 -0
  35. package/dist/hooks/useFishjamClient_DO_NOT_USE.d.ts.map +1 -0
  36. package/dist/hooks/useFishjamClient_DO_NOT_USE.js +6 -0
  37. package/dist/hooks/useFishjamContext.d.ts +20 -0
  38. package/dist/hooks/useFishjamContext.d.ts.map +1 -0
  39. package/dist/hooks/useFishjamContext.js +8 -0
  40. package/dist/hooks/useParticipantStatus.d.ts +8 -0
  41. package/dist/hooks/useParticipantStatus.d.ts.map +1 -0
  42. package/dist/hooks/useParticipantStatus.js +39 -0
  43. package/dist/hooks/useParticipants.d.ts +6 -0
  44. package/dist/hooks/useParticipants.d.ts.map +1 -0
  45. package/dist/hooks/useParticipants.js +16 -0
  46. package/dist/hooks/useProcessedPreviewStream.d.ts +3 -0
  47. package/dist/hooks/useProcessedPreviewStream.d.ts.map +1 -0
  48. package/dist/hooks/useProcessedPreviewStream.js +17 -0
  49. package/dist/hooks/useReconnection.d.ts +3 -0
  50. package/dist/hooks/useReconnection.d.ts.map +1 -0
  51. package/dist/hooks/useReconnection.js +27 -0
  52. package/dist/hooks/useScreenShare.d.ts +3 -0
  53. package/dist/hooks/useScreenShare.d.ts.map +1 -0
  54. package/dist/hooks/useScreenShare.js +119 -0
  55. package/dist/hooks/useStatus.d.ts +2 -0
  56. package/dist/hooks/useStatus.d.ts.map +1 -0
  57. package/dist/hooks/useStatus.js +4 -0
  58. package/dist/hooks/useTrackManager.d.ts +11 -0
  59. package/dist/hooks/useTrackManager.d.ts.map +1 -0
  60. package/dist/hooks/useTrackManager.js +176 -0
  61. package/dist/index.d.ts +4 -5
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +2 -2
  64. package/dist/state.types.d.ts +11 -24
  65. package/dist/state.types.d.ts.map +1 -1
  66. package/dist/types.d.ts +59 -41
  67. package/dist/types.d.ts.map +1 -1
  68. package/package.json +13 -17
  69. package/dist/Client.d.ts +0 -198
  70. package/dist/Client.d.ts.map +0 -1
  71. package/dist/Client.js +0 -461
  72. package/dist/create.d.ts +0 -10
  73. package/dist/create.d.ts.map +0 -1
  74. package/dist/create.js +0 -195
  75. package/dist/screenShareTrackManager.d.ts +0 -4
  76. package/dist/screenShareTrackManager.d.ts.map +0 -1
  77. package/dist/screenShareTrackManager.js +0 -92
  78. package/dist/trackManager.d.ts +0 -9
  79. package/dist/trackManager.d.ts.map +0 -1
  80. package/dist/trackManager.js +0 -116
  81. package/dist/useSetupMedia.d.ts +0 -3
  82. package/dist/useSetupMedia.d.ts.map +0 -1
  83. package/dist/useSetupMedia.js +0 -201
@@ -0,0 +1,20 @@
1
+ import type { FishjamClient } from "@fishjam-cloud/ts-client";
2
+ import { type MutableRefObject } from "react";
3
+ import type { PeerMetadata, TrackMetadata, ScreenShareState, TrackManager } from "../types";
4
+ import type { DeviceManager } from "../DeviceManager";
5
+ import type { ParticipantStatus } from "../state.types";
6
+ import type { FishjamClientState } from "./useFishjamClientState";
7
+ export type FishjamContextType = {
8
+ fishjamClientRef: MutableRefObject<FishjamClient<PeerMetadata, TrackMetadata>>;
9
+ videoDeviceManagerRef: MutableRefObject<DeviceManager>;
10
+ audioDeviceManagerRef: MutableRefObject<DeviceManager>;
11
+ hasDevicesBeenInitializedRef: MutableRefObject<boolean>;
12
+ screenShareState: [ScreenShareState, React.Dispatch<React.SetStateAction<ScreenShareState>>];
13
+ peerStatus: ParticipantStatus;
14
+ videoTrackManager: TrackManager;
15
+ audioTrackManager: TrackManager;
16
+ clientState: FishjamClientState;
17
+ };
18
+ export declare const FishjamContext: import("react").Context<FishjamContextType | null>;
19
+ export declare function useFishjamContext(): FishjamContextType;
20
+ //# sourceMappingURL=useFishjamContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFishjamContext.d.ts","sourceRoot":"","sources":["../../src/hooks/useFishjamContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAiB,KAAK,gBAAgB,EAAc,MAAM,OAAO,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gBAAgB,EAAE,gBAAgB,CAAC,aAAa,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;IAC/E,qBAAqB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;IACvD,qBAAqB,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;IACvD,4BAA4B,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxD,gBAAgB,EAAE,CAAC,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC7F,UAAU,EAAE,iBAAiB,CAAC;IAC9B,iBAAiB,EAAE,YAAY,CAAC;IAChC,iBAAiB,EAAE,YAAY,CAAC;IAChC,WAAW,EAAE,kBAAkB,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,cAAc,oDAAiD,CAAC;AAE7E,wBAAgB,iBAAiB,IAGb,kBAAkB,CACrC"}
@@ -0,0 +1,8 @@
1
+ import { createContext, useContext } from "react";
2
+ export const FishjamContext = createContext(null);
3
+ export function useFishjamContext() {
4
+ const context = useContext(FishjamContext);
5
+ if (!context)
6
+ throw new Error("useFishjamContext must be used within a FishjamContextProvider");
7
+ return context;
8
+ }
@@ -0,0 +1,8 @@
1
+ import type { ParticipantStatus } from "../state.types";
2
+ import type { FishjamClient } from "@fishjam-cloud/ts-client";
3
+ import type { PeerMetadata, TrackMetadata } from "../types";
4
+ export declare const useParticipantStatus: (client: FishjamClient<PeerMetadata, TrackMetadata>) => {
5
+ readonly peerStatus: ParticipantStatus;
6
+ readonly getCurrentParticipantStatus: () => ParticipantStatus;
7
+ };
8
+ //# sourceMappingURL=useParticipantStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useParticipantStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useParticipantStatus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5D,eAAO,MAAM,oBAAoB,WAAY,aAAa,CAAC,YAAY,EAAE,aAAa,CAAC;;;CA8CtF,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ export const useParticipantStatus = (client) => {
3
+ const [peerStatus, setParticipantStatusState] = useState("idle");
4
+ const peerStatusRef = useRef("idle");
5
+ const setParticipantStatus = useCallback((status) => {
6
+ peerStatusRef.current = status;
7
+ setParticipantStatusState(status);
8
+ }, [setParticipantStatusState]);
9
+ const getCurrentParticipantStatus = useCallback(() => peerStatusRef.current, []);
10
+ useEffect(() => {
11
+ const setConnecting = () => {
12
+ setParticipantStatus("connecting");
13
+ };
14
+ const setError = () => {
15
+ setParticipantStatus("error");
16
+ };
17
+ const setJoined = () => {
18
+ setParticipantStatus("connected");
19
+ };
20
+ const setDisconnected = () => {
21
+ setParticipantStatus("idle");
22
+ };
23
+ client.on("connectionStarted", setConnecting);
24
+ client.on("joined", setJoined);
25
+ client.on("authError", setError);
26
+ client.on("joinError", setError);
27
+ client.on("connectionError", setError);
28
+ client.on("disconnected", setDisconnected);
29
+ return () => {
30
+ client.off("connectionStarted", setConnecting);
31
+ client.off("joined", setJoined);
32
+ client.off("authError", setError);
33
+ client.off("joinError", setError);
34
+ client.off("connectionError", setError);
35
+ client.off("disconnected", setDisconnected);
36
+ };
37
+ }, [client, setParticipantStatus]);
38
+ return { peerStatus, getCurrentParticipantStatus };
39
+ };
@@ -0,0 +1,6 @@
1
+ import type { PeerStateWithTracks } from "../types";
2
+ export declare function useParticipants(): {
3
+ localParticipant: PeerStateWithTracks | null;
4
+ participants: PeerStateWithTracks[];
5
+ };
6
+ //# sourceMappingURL=useParticipants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useParticipants.d.ts","sourceRoot":"","sources":["../../src/hooks/useParticipants.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAcpD,wBAAgB,eAAe;;;EAQ9B"}
@@ -0,0 +1,16 @@
1
+ import { useFishjamContext } from "./useFishjamContext";
2
+ function getPeerWithDistinguishedTracks(peerState) {
3
+ const peerTracks = Object.values(peerState.tracks ?? {});
4
+ const cameraTrack = peerTracks.find(({ metadata }) => metadata?.type === "camera");
5
+ const microphoneTrack = peerTracks.find(({ metadata }) => metadata?.type === "microphone");
6
+ const screenShareVideoTrack = peerTracks.find(({ metadata }) => metadata?.type === "screenShareVideo");
7
+ const screenShareAudioTrack = peerTracks.find(({ metadata }) => metadata?.type === "screenShareAudio");
8
+ return { ...peerState, cameraTrack, microphoneTrack, screenShareVideoTrack, screenShareAudioTrack };
9
+ }
10
+ export function useParticipants() {
11
+ const { clientState } = useFishjamContext();
12
+ const { localPeer, peers } = clientState;
13
+ const localParticipant = localPeer ? getPeerWithDistinguishedTracks(localPeer) : null;
14
+ const participants = Object.values(peers).map(getPeerWithDistinguishedTracks);
15
+ return { localParticipant, participants };
16
+ }
@@ -0,0 +1,3 @@
1
+ import type { TrackManager } from "../types";
2
+ export declare function useProcessedPreviewStream(trackManager: TrackManager, deviceTrack: MediaStreamTrack | null | undefined): MediaStream | null;
3
+ //# sourceMappingURL=useProcessedPreviewStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProcessedPreviewStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useProcessedPreviewStream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,YAAY,EAC1B,WAAW,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,sBAiBjD"}
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from "react";
2
+ export function useProcessedPreviewStream(trackManager, deviceTrack) {
3
+ const [previewStream, setPreviewStream] = useState(null);
4
+ const { currentTrack, currentTrackMiddleware } = trackManager;
5
+ useEffect(() => {
6
+ if (currentTrack || !deviceTrack)
7
+ return;
8
+ const result = currentTrackMiddleware?.(deviceTrack);
9
+ if (result?.track)
10
+ setPreviewStream(new MediaStream([result.track]));
11
+ return () => {
12
+ result?.onClear?.();
13
+ setPreviewStream(null);
14
+ };
15
+ }, [currentTrack, deviceTrack, currentTrackMiddleware]);
16
+ return previewStream;
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReconnectionStatus } from "@fishjam-cloud/ts-client";
2
+ export declare const useReconnection: () => ReconnectionStatus;
3
+ //# sourceMappingURL=useReconnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReconnection.d.ts","sourceRoot":"","sources":["../../src/hooks/useReconnection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,eAAO,MAAM,eAAe,QAAO,kBA6BlC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useFishjamContext } from "./useFishjamContext";
3
+ export const useReconnection = () => {
4
+ const { fishjamClientRef } = useFishjamContext();
5
+ const [reconnectionStatus, setReconnectionStatus] = useState("idle");
6
+ useEffect(() => {
7
+ const client = fishjamClientRef.current;
8
+ const setReconnecting = () => {
9
+ setReconnectionStatus("reconnecting");
10
+ };
11
+ const setIdle = () => {
12
+ setReconnectionStatus("idle");
13
+ };
14
+ const setError = () => {
15
+ setReconnectionStatus("error");
16
+ };
17
+ client.on("reconnectionStarted", setReconnecting);
18
+ client.on("reconnected", setIdle);
19
+ client.on("reconnectionRetriesLimitReached", setError);
20
+ return () => {
21
+ client.off("reconnectionStarted", setReconnecting);
22
+ client.off("reconnected", setIdle);
23
+ client.off("reconnectionRetriesLimitReached", setError);
24
+ };
25
+ }, [fishjamClientRef]);
26
+ return reconnectionStatus;
27
+ };
@@ -0,0 +1,3 @@
1
+ import type { ScreenshareApi } from "../types";
2
+ export declare const useScreenShare: () => ScreenshareApi;
3
+ //# sourceMappingURL=useScreenShare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useScreenShare.d.ts","sourceRoot":"","sources":["../../src/hooks/useScreenShare.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,UAAU,CAAC;AAUjE,eAAO,MAAM,cAAc,QAAO,cA0IjC,CAAC"}
@@ -0,0 +1,119 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import { getRemoteOrLocalTrack } from "../utils/track";
3
+ import { useFishjamContext } from "./useFishjamContext";
4
+ const getTracks = (stream) => {
5
+ const video = stream.getVideoTracks()[0];
6
+ const audio = stream.getAudioTracks()[0] ?? null;
7
+ return [video, audio];
8
+ };
9
+ export const useScreenShare = () => {
10
+ const ctx = useFishjamContext();
11
+ const [state, setState] = ctx.screenShareState;
12
+ const cleanMiddlewareFnRef = useRef(null);
13
+ const { fishjamClientRef } = useFishjamContext();
14
+ const tsClient = fishjamClientRef.current;
15
+ const stream = state.stream ?? null;
16
+ const [videoTrack, audioTrack] = stream ? getTracks(stream) : [null, null];
17
+ const getDisplayName = () => tsClient.getLocalPeer()?.metadata?.displayName;
18
+ const startStreaming = async (props) => {
19
+ const stream = await navigator.mediaDevices.getDisplayMedia({
20
+ video: props?.videoConstraints ?? true,
21
+ audio: props?.audioConstraints ?? true,
22
+ });
23
+ const displayName = getDisplayName();
24
+ let [video, audio] = getTracks(stream);
25
+ if (state.tracksMiddleware) {
26
+ const { videoTrack, audioTrack, onClear } = state.tracksMiddleware(video, audio);
27
+ video = videoTrack;
28
+ audio = audioTrack;
29
+ cleanMiddlewareFnRef.current = onClear;
30
+ }
31
+ const addTrackPromises = [tsClient.addTrack(video, { displayName, type: "screenShareVideo", paused: false })];
32
+ if (audio)
33
+ addTrackPromises.push(tsClient.addTrack(audio, { displayName, type: "screenShareAudio", paused: false }));
34
+ const [videoId, audioId] = await Promise.all(addTrackPromises);
35
+ setState({ stream, trackIds: { videoId, audioId } });
36
+ };
37
+ const replaceTracks = async (newVideoTrack, newAudioTrack) => {
38
+ if (!state?.stream)
39
+ return;
40
+ const addTrackPromises = [tsClient.replaceTrack(state.trackIds.videoId, newVideoTrack)];
41
+ if (newAudioTrack && state.trackIds.audioId) {
42
+ addTrackPromises.push(tsClient.replaceTrack(state.trackIds.audioId, newAudioTrack));
43
+ }
44
+ await Promise.all(addTrackPromises);
45
+ };
46
+ const cleanMiddleware = useCallback(() => {
47
+ cleanMiddlewareFnRef.current?.();
48
+ cleanMiddlewareFnRef.current = null;
49
+ }, []);
50
+ const setTracksMiddleware = async (middleware) => {
51
+ if (!state?.stream)
52
+ return;
53
+ const [video, audio] = getTracks(state.stream);
54
+ cleanMiddleware();
55
+ const { videoTrack, audioTrack, onClear } = middleware?.(video, audio) ?? {
56
+ videoTrack: video,
57
+ audioTrack: audio,
58
+ onClear: null,
59
+ };
60
+ cleanMiddlewareFnRef.current = onClear;
61
+ await replaceTracks(videoTrack, audioTrack);
62
+ };
63
+ const stopStreaming = useCallback(async () => {
64
+ if (!state.stream) {
65
+ console.warn("No stream to stop");
66
+ return;
67
+ }
68
+ const [video, audio] = getTracks(state.stream);
69
+ video.stop();
70
+ if (audio)
71
+ audio.stop();
72
+ const client = fishjamClientRef.current;
73
+ const removeTrackPromises = [client.removeTrack(state.trackIds.videoId)];
74
+ if (state.trackIds.audioId)
75
+ removeTrackPromises.push(client.removeTrack(state.trackIds.audioId));
76
+ await Promise.all(removeTrackPromises);
77
+ cleanMiddleware();
78
+ setState(({ tracksMiddleware }) => ({ stream: null, trackIds: null, tracksMiddleware }));
79
+ }, [state, fishjamClientRef, setState, cleanMiddleware]);
80
+ useEffect(() => {
81
+ if (!state.stream)
82
+ return;
83
+ const [video, audio] = getTracks(state.stream);
84
+ const trackEndedHandler = () => {
85
+ stopStreaming();
86
+ };
87
+ video.addEventListener("ended", trackEndedHandler);
88
+ audio?.addEventListener("ended", trackEndedHandler);
89
+ return () => {
90
+ video.removeEventListener("ended", trackEndedHandler);
91
+ audio?.removeEventListener("ended", trackEndedHandler);
92
+ };
93
+ }, [state, stopStreaming]);
94
+ useEffect(() => {
95
+ const client = fishjamClientRef.current;
96
+ const onDisconnected = () => {
97
+ if (stream) {
98
+ stopStreaming();
99
+ }
100
+ };
101
+ client.on("disconnected", onDisconnected);
102
+ return () => {
103
+ client.removeListener("disconnected", onDisconnected);
104
+ };
105
+ }, [stopStreaming, fishjamClientRef, stream]);
106
+ const videoBroadcast = state.stream ? getRemoteOrLocalTrack(tsClient, state.trackIds.videoId) : null;
107
+ const audioBroadcast = state.trackIds?.audioId ? getRemoteOrLocalTrack(tsClient, state.trackIds.audioId) : null;
108
+ return {
109
+ startStreaming,
110
+ stopStreaming,
111
+ stream,
112
+ videoTrack,
113
+ audioTrack,
114
+ videoBroadcast,
115
+ audioBroadcast,
116
+ setTracksMiddleware,
117
+ currentTracksMiddleware: state?.tracksMiddleware ?? null,
118
+ };
119
+ };
@@ -0,0 +1,2 @@
1
+ export declare function useStatus(): import("..").ParticipantStatus;
2
+ //# sourceMappingURL=useStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useStatus.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,mCAExB"}
@@ -0,0 +1,4 @@
1
+ import { useFishjamContext } from "./useFishjamContext";
2
+ export function useStatus() {
3
+ return useFishjamContext().peerStatus;
4
+ }
@@ -0,0 +1,11 @@
1
+ import type { FishjamClient } from "@fishjam-cloud/ts-client";
2
+ import type { MediaManager, PeerMetadata, TrackManager, TrackMetadata } from "../types";
3
+ import type { ParticipantStatus } from "../state.types";
4
+ interface TrackManagerConfig {
5
+ mediaManager: MediaManager;
6
+ tsClient: FishjamClient<PeerMetadata, TrackMetadata>;
7
+ getCurrentParticipantStatus: () => ParticipantStatus;
8
+ }
9
+ export declare const useTrackManager: ({ mediaManager, tsClient, getCurrentParticipantStatus, }: TrackManagerConfig) => TrackManager;
10
+ export {};
11
+ //# sourceMappingURL=useTrackManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTrackManager.d.ts","sourceRoot":"","sources":["../../src/hooks/useTrackManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAwC,MAAM,0BAA0B,CAAC;AACpG,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,YAAY,EAAE,aAAa,EAAmB,MAAM,UAAU,CAAC;AAGrH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,UAAU,kBAAkB;IAC1B,YAAY,EAAE,YAAY,CAAC;IAC3B,QAAQ,EAAE,aAAa,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;IACrD,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;CACtD;AAOD,eAAO,MAAM,eAAe,6DAIzB,kBAAkB,KAAG,YA2MvB,CAAC"}
@@ -0,0 +1,176 @@
1
+ import { getRemoteOrLocalTrack } from "../utils/track";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ const TRACK_TYPE_TO_DEVICE = {
4
+ video: "camera",
5
+ audio: "microphone",
6
+ };
7
+ export const useTrackManager = ({ mediaManager, tsClient, getCurrentParticipantStatus, }) => {
8
+ const [currentTrackId, setCurrentTrackId] = useState(null);
9
+ const [paused, setPaused] = useState(false);
10
+ const clearMiddlewareFnRef = useRef(null);
11
+ const [currentTrackMiddleware, setCurrentTrackMiddleware] = useState(null);
12
+ const type = TRACK_TYPE_TO_DEVICE[mediaManager.getDeviceType()];
13
+ const metadata = useMemo(() => ({ type, paused }), [type, paused]);
14
+ const currentTrack = useMemo(() => getRemoteOrLocalTrack(tsClient, currentTrackId), [tsClient, currentTrackId]);
15
+ function clearMiddleware() {
16
+ clearMiddlewareFnRef.current?.();
17
+ clearMiddlewareFnRef.current = null;
18
+ }
19
+ const clearAndGetProcessedTrack = useCallback((inputTrack) => {
20
+ clearMiddleware();
21
+ const { onClear, track } = currentTrackMiddleware?.(inputTrack) ?? {};
22
+ if (onClear)
23
+ clearMiddlewareFnRef.current = onClear;
24
+ return track ?? inputTrack;
25
+ }, [currentTrackMiddleware]);
26
+ async function setTrackMiddleware(middleware) {
27
+ const mediaTrack = mediaManager.getTracks()[0];
28
+ setCurrentTrackMiddleware(() => middleware);
29
+ if (!currentTrack || !mediaTrack)
30
+ return;
31
+ clearMiddleware();
32
+ const { onClear, track } = middleware?.(mediaTrack) ?? { track: mediaTrack };
33
+ if (onClear)
34
+ clearMiddlewareFnRef.current = onClear;
35
+ await tsClient.replaceTrack(currentTrack.trackId, track);
36
+ }
37
+ async function initialize(deviceId) {
38
+ await mediaManager?.start(deviceId);
39
+ if (!currentTrackId)
40
+ return;
41
+ const newTrack = mediaManager.getTracks()[0];
42
+ await tsClient.replaceTrack(currentTrackId, clearAndGetProcessedTrack(newTrack));
43
+ }
44
+ function stop() {
45
+ clearMiddleware();
46
+ return mediaManager?.stop();
47
+ }
48
+ const startStreaming = useCallback(async (simulcastConfig, maxBandwidth) => {
49
+ if (currentTrackId)
50
+ throw Error("Track already added");
51
+ const media = mediaManager.getMedia();
52
+ if (!media || !media.stream || !media.track)
53
+ throw Error("Device is unavailable");
54
+ const track = getRemoteOrLocalTrack(tsClient, currentTrackId);
55
+ if (track)
56
+ return track.trackId;
57
+ // see `getRemoteOrLocalTrackContext()` explanation
58
+ setCurrentTrackId(media.track.id);
59
+ const displayName = tsClient.getLocalPeer()?.metadata?.displayName;
60
+ const trackMetadata = { ...metadata, displayName, paused: false };
61
+ const processedTrack = clearAndGetProcessedTrack(media.track);
62
+ const remoteTrackId = await tsClient.addTrack(processedTrack, trackMetadata, simulcastConfig, maxBandwidth);
63
+ setCurrentTrackId(remoteTrackId);
64
+ setPaused(false);
65
+ return remoteTrackId;
66
+ }, [clearAndGetProcessedTrack, currentTrackId, mediaManager, metadata, tsClient]);
67
+ async function refreshStreamedTrack() {
68
+ if (!currentTrack)
69
+ return;
70
+ const newTrack = mediaManager.getTracks()[0];
71
+ if (!newTrack)
72
+ throw Error("New track is empty");
73
+ return tsClient.replaceTrack(currentTrack.trackId, clearAndGetProcessedTrack(newTrack));
74
+ }
75
+ async function stopStreaming() {
76
+ if (!currentTrack)
77
+ return;
78
+ clearMiddleware();
79
+ setCurrentTrackId(null);
80
+ setPaused(true);
81
+ return tsClient.removeTrack(currentTrack.trackId);
82
+ }
83
+ async function pauseStreaming() {
84
+ if (!currentTrack)
85
+ return;
86
+ setPaused(true);
87
+ await tsClient.replaceTrack(currentTrack.trackId, null);
88
+ const trackMetadata = { ...metadata, paused: true };
89
+ return tsClient.updateTrackMetadata(currentTrack.trackId, trackMetadata);
90
+ }
91
+ async function resumeStreaming() {
92
+ if (!currentTrack)
93
+ return;
94
+ const media = mediaManager.getMedia();
95
+ if (!media)
96
+ throw Error("Device is unavailable");
97
+ const processedTrack = media.track ? clearAndGetProcessedTrack(media.track) : null;
98
+ setPaused(false);
99
+ await tsClient.replaceTrack(currentTrack.trackId, processedTrack);
100
+ const trackMetadata = { ...metadata, paused: false };
101
+ return tsClient.updateTrackMetadata(currentTrack.trackId, trackMetadata);
102
+ }
103
+ function disableTrack() {
104
+ mediaManager.disable();
105
+ }
106
+ function enableTrack() {
107
+ mediaManager.enable();
108
+ }
109
+ const stream = async () => {
110
+ if (getCurrentParticipantStatus() !== "connected")
111
+ return;
112
+ if (currentTrack?.trackId) {
113
+ await resumeStreaming();
114
+ }
115
+ else {
116
+ await startStreaming();
117
+ }
118
+ };
119
+ /**
120
+ * @see {@link TrackManager#toggle} for more details.
121
+ */
122
+ async function toggle(mode = "hard") {
123
+ const mediaStream = mediaManager.getMedia()?.stream;
124
+ const track = mediaManager.getTracks()?.[0];
125
+ const enabled = Boolean(track?.enabled);
126
+ if (mediaStream && enabled) {
127
+ mediaManager.disable();
128
+ if (currentTrack?.trackId) {
129
+ await pauseStreaming();
130
+ }
131
+ if (mode === "hard") {
132
+ await mediaManager.stop();
133
+ }
134
+ }
135
+ else if (mediaStream && !enabled) {
136
+ mediaManager.enable();
137
+ await stream();
138
+ }
139
+ else {
140
+ await mediaManager.start();
141
+ await stream();
142
+ }
143
+ }
144
+ useEffect(() => {
145
+ const joinedHandler = () => {
146
+ if (mediaManager.getTracks().length > 0) {
147
+ startStreaming();
148
+ }
149
+ };
150
+ const disconnectedHandler = () => {
151
+ setCurrentTrackId(null);
152
+ };
153
+ tsClient.on("joined", joinedHandler);
154
+ tsClient.on("disconnected", disconnectedHandler);
155
+ return () => {
156
+ tsClient.off("joined", joinedHandler);
157
+ tsClient.off("disconnected", disconnectedHandler);
158
+ };
159
+ }, [mediaManager, startStreaming, tsClient]);
160
+ return {
161
+ currentTrack,
162
+ setTrackMiddleware,
163
+ initialize,
164
+ stop,
165
+ startStreaming,
166
+ stopStreaming,
167
+ pauseStreaming,
168
+ resumeStreaming,
169
+ disableTrack,
170
+ enableTrack,
171
+ currentTrackMiddleware,
172
+ refreshStreamedTrack,
173
+ paused,
174
+ toggle,
175
+ };
176
+ };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- export { create } from "./create";
2
- export { Client } from "./Client";
3
- export type { ClientEvents } from "./Client";
4
- export type { PeerState, Track, PeerId, TrackId, TrackWithOrigin, Origin, PeerStatus, Selector, State, SetStore, } from "./state.types";
5
- export type { DeviceManagerConfig, StorageConfig, Devices, UserMediaAPI, UseSetupMediaResult, UseSetupMediaConfig, CreateFishjamClient, ScreenshareApi, UseConnect, ConnectConfig, TrackManager, TrackMiddleware, TrackMetadata, // only for compatibility reasons, will be removed in: FCE-415
1
+ export * from "./hooks/public";
2
+ export * from "./fishjamProvider";
3
+ export type { PeerState, Track, PeerId, TrackId, TrackWithOrigin, Origin, ParticipantStatus } from "./state.types";
4
+ export type { DeviceManagerConfig, Participiants, StorageConfig, Devices, Device, AudioDevice, UseSetupMediaResult, UseSetupMediaConfig, ScreenshareApi, UseConnect, ConnectConfig, TrackMiddleware, PeerStateWithTracks, TrackMetadata, // only for compatibility reasons, will be removed in: FCE-415
6
5
  PeerMetadata, } from "./types";
7
6
  export { AUDIO_TRACK_CONSTRAINTS, VIDEO_TRACK_CONSTRAINTS, SCREEN_SHARING_MEDIA_CONSTRAINTS } from "./constraints";
8
7
  export type { Peer, MessageEvents, CreateConfig, TrackBandwidthLimit, SimulcastBandwidthLimit, BandwidthLimit, WebRTCEndpointEvents, TrackContextEvents, Endpoint, SimulcastConfig, TrackContext, VadStatus, EncodingReason, MetadataParser, AuthErrorReason, Encoding, } from "@fishjam-cloud/ts-client";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,YAAY,EACV,SAAS,EACT,KAAK,EACL,MAAM,EACN,OAAO,EACP,eAAe,EACf,MAAM,EACN,UAAU,EACV,QAAQ,EACR,KAAK,EACL,QAAQ,GACT,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,OAAO,EACP,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,aAAa,EAAE,8DAA8D;AAC7E,YAAY,GACb,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,gCAAgC,EAAE,MAAM,eAAe,CAAC;AAEnH,YAAY,EACV,IAAI,EACJ,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAS,EACT,cAAc,EACd,cAAc,EACd,eAAe,EACf,QAAQ,GACT,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAElC,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEnH,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,OAAO,EACP,MAAM,EACN,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EAAE,8DAA8D;AAC7E,YAAY,GACb,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,gCAAgC,EAAE,MAAM,eAAe,CAAC;AAEnH,YAAY,EACV,IAAI,EACJ,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAS,EACT,cAAc,EACd,cAAc,EACd,eAAe,EACf,QAAQ,GACT,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { create } from "./create";
2
- export { Client } from "./Client";
1
+ export * from "./hooks/public";
2
+ export * from "./fishjamProvider";
3
3
  export { AUDIO_TRACK_CONSTRAINTS, VIDEO_TRACK_CONSTRAINTS, SCREEN_SHARING_MEDIA_CONSTRAINTS } from "./constraints";
4
4
  export { FishjamClient } from "@fishjam-cloud/ts-client";
@@ -1,7 +1,5 @@
1
- import type { Encoding, VadStatus, SimulcastConfig, ReconnectionStatus } from "@fishjam-cloud/ts-client";
2
- import type { MediaState, PeerMetadata, TrackMetadata } from "./types";
3
- import type { Devices } from "./types";
4
- import type { Client } from "./Client";
1
+ import type { Encoding, VadStatus, SimulcastConfig } from "@fishjam-cloud/ts-client";
2
+ import type { PeerMetadata, TrackMetadata } from "./types";
5
3
  export type TrackId = string;
6
4
  export type PeerId = string;
7
5
  export type Track = {
@@ -32,24 +30,13 @@ export type PeerState = {
32
30
  metadataParsingError?: any;
33
31
  tracks: Record<TrackId, Track>;
34
32
  };
35
- export type PeerStatus = "connecting" | "connected" | "authenticated" | "joined" | "error" | "closed" | null;
36
- export type UseReconnection = {
37
- status: ReconnectionStatus;
38
- isReconnecting: boolean;
39
- isError: boolean;
40
- isIdle: boolean;
41
- };
42
- export type State = {
43
- local: PeerState | null;
44
- remote: Record<PeerId, PeerState>;
45
- tracks: Record<TrackId, TrackWithOrigin>;
46
- bandwidthEstimation: bigint;
47
- status: PeerStatus;
48
- media: MediaState | null;
49
- devices: Devices;
50
- client: Client;
51
- reconnectionStatus: ReconnectionStatus;
52
- };
53
- export type SetStore = (setter: (prevState: State) => State) => void;
54
- export type Selector<Result> = (snapshot: State) => Result;
33
+ /**
34
+ * Represents the possible statuses of a peer connection.
35
+ *
36
+ * idle - Participant is not connected, either never connected or successfully disconnected.
37
+ * connecting - Participant is in the process of connecting.
38
+ * connected - Participant has successfully connected.
39
+ * error - There was an error in the connection process.
40
+ */
41
+ export type ParticipantStatus = "connecting" | "connected" | "error" | "idle";
55
42
  //# sourceMappingURL=state.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.types.d.ts","sourceRoot":"","sources":["../src/state.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACzG,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,GAAG,CAAC;IAC3B,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,GAAG,CAAC;CAC5B;AAED,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,GAAG,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAE7G,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IACzC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,kBAAkB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC;AAErE,MAAM,MAAM,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,KAAK,MAAM,CAAC"}
1
+ {"version":3,"file":"state.types.d.ts","sourceRoot":"","sources":["../src/state.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,GAAG,CAAC;IAC3B,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,GAAG,CAAC;CAC5B;AAED,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,GAAG,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;CAChC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC"}