@fishjam-cloud/react-client 0.6.0 → 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 (93) 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 +2 -2
  8. package/dist/fishjamProvider.d.ts.map +1 -1
  9. package/dist/fishjamProvider.js +32 -23
  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 +8 -6
  23. package/dist/hooks/public.d.ts.map +1 -1
  24. package/dist/hooks/public.js +8 -6
  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/{clientState.js → useFishjamClientState.js} +53 -34
  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/useParticipantStatus.d.ts +8 -0
  40. package/dist/hooks/useParticipantStatus.d.ts.map +1 -0
  41. package/dist/hooks/useParticipantStatus.js +39 -0
  42. package/dist/hooks/{participants.d.ts → useParticipants.d.ts} +1 -1
  43. package/dist/hooks/useParticipants.d.ts.map +1 -0
  44. package/dist/hooks/useParticipants.js +16 -0
  45. package/dist/hooks/useProcessedPreviewStream.d.ts +3 -0
  46. package/dist/hooks/useProcessedPreviewStream.d.ts.map +1 -0
  47. package/dist/hooks/useProcessedPreviewStream.js +17 -0
  48. package/dist/hooks/useReconnection.d.ts +3 -0
  49. package/dist/hooks/useReconnection.d.ts.map +1 -0
  50. package/dist/hooks/useReconnection.js +27 -0
  51. package/dist/hooks/{screenShare.d.ts → useScreenShare.d.ts} +1 -1
  52. package/dist/hooks/useScreenShare.d.ts.map +1 -0
  53. package/dist/hooks/{screenShare.js → useScreenShare.js} +49 -24
  54. package/dist/hooks/useStatus.d.ts +2 -0
  55. package/dist/hooks/useStatus.d.ts.map +1 -0
  56. package/dist/hooks/useStatus.js +4 -0
  57. package/dist/hooks/useTrackManager.d.ts +11 -0
  58. package/dist/hooks/useTrackManager.d.ts.map +1 -0
  59. package/dist/hooks/useTrackManager.js +176 -0
  60. package/dist/index.d.ts +2 -4
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +0 -1
  63. package/dist/state.types.d.ts +11 -24
  64. package/dist/state.types.d.ts.map +1 -1
  65. package/dist/types.d.ts +56 -36
  66. package/dist/types.d.ts.map +1 -1
  67. package/package.json +12 -15
  68. package/dist/Client.d.ts +0 -198
  69. package/dist/Client.d.ts.map +0 -1
  70. package/dist/Client.js +0 -461
  71. package/dist/hooks/clientState.d.ts +0 -4
  72. package/dist/hooks/clientState.d.ts.map +0 -1
  73. package/dist/hooks/connection.d.ts +0 -6
  74. package/dist/hooks/connection.d.ts.map +0 -1
  75. package/dist/hooks/connection.js +0 -28
  76. package/dist/hooks/devices.d.ts +0 -4
  77. package/dist/hooks/devices.d.ts.map +0 -1
  78. package/dist/hooks/devices.js +0 -9
  79. package/dist/hooks/fishjamContext.d.ts +0 -4
  80. package/dist/hooks/fishjamContext.d.ts.map +0 -1
  81. package/dist/hooks/participants.d.ts.map +0 -1
  82. package/dist/hooks/participants.js +0 -13
  83. package/dist/hooks/screenShare.d.ts.map +0 -1
  84. package/dist/hooks/setupMedia.d.ts +0 -3
  85. package/dist/hooks/setupMedia.d.ts.map +0 -1
  86. package/dist/hooks/setupMedia.js +0 -200
  87. package/dist/hooks/state.d.ts +0 -6
  88. package/dist/hooks/state.d.ts.map +0 -1
  89. package/dist/hooks/state.js +0 -15
  90. package/dist/trackManager.d.ts +0 -9
  91. package/dist/trackManager.d.ts.map +0 -1
  92. package/dist/trackManager.js +0 -116
  93. /package/dist/hooks/{fishjamContext.js → useFishjamContext.js} +0 -0
@@ -1,38 +1,29 @@
1
- import { useCallback, useRef, useSyncExternalStore } from "react";
1
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
2
2
  const eventNames = [
3
- "socketOpen",
4
- "socketError",
5
3
  "socketClose",
4
+ "socketError",
5
+ "socketOpen",
6
6
  "authSuccess",
7
7
  "authError",
8
8
  "disconnected",
9
- "joined",
10
- "joinError",
11
- "peerJoined",
12
- "peerUpdated",
13
- "peerLeft",
9
+ "reconnectionStarted",
14
10
  "reconnected",
15
11
  "reconnectionRetriesLimitReached",
16
- "reconnectionStarted",
17
- "componentAdded",
18
- "componentUpdated",
19
- "componentRemoved",
12
+ "joined",
13
+ "joinError",
20
14
  "trackReady",
21
15
  "trackAdded",
22
16
  "trackRemoved",
23
17
  "trackUpdated",
18
+ "peerJoined",
19
+ "peerLeft",
20
+ "peerUpdated",
21
+ "componentAdded",
22
+ "componentRemoved",
23
+ "componentUpdated",
24
+ "connectionError",
25
+ "tracksPriorityChanged",
24
26
  "bandwidthEstimationChanged",
25
- "encodingChanged",
26
- "voiceActivityChanged",
27
- "deviceDisabled",
28
- "deviceEnabled",
29
- "managerInitialized",
30
- "managerStarted",
31
- "deviceStopped",
32
- "deviceReady",
33
- "devicesStarted",
34
- "devicesReady",
35
- "error",
36
27
  "targetTrackEncodingRequested",
37
28
  "localTrackAdded",
38
29
  "localTrackRemoved",
@@ -47,7 +38,35 @@ const eventNames = [
47
38
  "localTrackMetadataChanged",
48
39
  "disconnectRequested",
49
40
  ];
50
- export const useClientState = (client) => {
41
+ function trackContextToTrack(track) {
42
+ return {
43
+ rawMetadata: track.rawMetadata,
44
+ metadata: track.metadata,
45
+ trackId: track.trackId,
46
+ stream: track.stream,
47
+ simulcastConfig: track.simulcastConfig ?? null,
48
+ encoding: track.encoding ?? null,
49
+ vadStatus: track.vadStatus,
50
+ track: track.track,
51
+ metadataParsingError: track.metadataParsingError,
52
+ };
53
+ }
54
+ function endpointToPeerState(peer) {
55
+ const tracks = [...peer.tracks].reduce((acc, [, track]) => ({ ...acc, [track.trackId]: trackContextToTrack(track) }), {});
56
+ return {
57
+ rawMetadata: peer.rawMetadata,
58
+ metadata: peer.metadata,
59
+ metadataParsingError: peer.metadataParsingError,
60
+ id: peer.id,
61
+ tracks,
62
+ };
63
+ }
64
+ /*
65
+ This is an internally used hook.
66
+ It is not meant to be used by the end user.
67
+ */
68
+ export function useFishjamClientState(fishjamClient) {
69
+ const client = useMemo(() => fishjamClient, [fishjamClient]);
51
70
  const mutationRef = useRef(false);
52
71
  const subscribe = useCallback((subscribeCallback) => {
53
72
  const callback = () => {
@@ -62,20 +81,20 @@ export const useClientState = (client) => {
62
81
  const lastSnapshotRef = useRef(null);
63
82
  const getSnapshot = useCallback(() => {
64
83
  if (mutationRef.current || lastSnapshotRef.current === null) {
84
+ const peers = Object.values(client.getRemotePeers()).map(endpointToPeerState);
85
+ const components = Object.values(client.getRemoteComponents()).map(endpointToPeerState);
86
+ const localEndpoint = client.getLocalPeer();
87
+ const localPeer = localEndpoint ? endpointToPeerState(localEndpoint) : null;
88
+ const isReconnecting = client.isReconnecting();
65
89
  lastSnapshotRef.current = {
66
- remote: client.peers,
67
- media: client.media,
68
- bandwidthEstimation: client.bandwidthEstimation,
69
- tracks: client.peersTracks,
70
- local: client.local,
71
- status: client.status,
72
- devices: client.devices,
73
- client: client,
74
- reconnectionStatus: client.reconnectionStatus,
90
+ peers,
91
+ components,
92
+ localPeer,
93
+ isReconnecting,
75
94
  };
76
95
  mutationRef.current = false;
77
96
  }
78
97
  return lastSnapshotRef.current;
79
98
  }, [client]);
80
99
  return useSyncExternalStore(subscribe, getSnapshot);
81
- };
100
+ }
@@ -0,0 +1,2 @@
1
+ export declare function useFishjamClient_DO_NOT_USE(): import("@fishjam-cloud/ts-client").FishjamClient<import("..").PeerMetadata, import("..").TrackMetadata>;
2
+ //# sourceMappingURL=useFishjamClient_DO_NOT_USE.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFishjamClient_DO_NOT_USE.d.ts","sourceRoot":"","sources":["../../src/hooks/useFishjamClient_DO_NOT_USE.ts"],"names":[],"mappings":"AAGA,wBAAgB,2BAA2B,4GAI1C"}
@@ -0,0 +1,6 @@
1
+ import { useFishjamContext } from "./useFishjamContext";
2
+ import { useMemo } from "react";
3
+ export function useFishjamClient_DO_NOT_USE() {
4
+ const client = useFishjamContext().fishjamClientRef.current;
5
+ return useMemo(() => client, [client]);
6
+ }
@@ -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 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
+ };
@@ -3,4 +3,4 @@ export declare function useParticipants(): {
3
3
  localParticipant: PeerStateWithTracks | null;
4
4
  participants: PeerStateWithTracks[];
5
5
  };
6
- //# sourceMappingURL=participants.d.ts.map
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
+ };
@@ -1,3 +1,3 @@
1
1
  import type { ScreenshareApi } from "../types";
2
2
  export declare const useScreenShare: () => ScreenshareApi;
3
- //# sourceMappingURL=screenShare.d.ts.map
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"}
@@ -1,6 +1,6 @@
1
- import { useCallback, useEffect } from "react";
1
+ import { useCallback, useEffect, useRef } from "react";
2
2
  import { getRemoteOrLocalTrack } from "../utils/track";
3
- import { useFishjamContext } from "./fishjamContext";
3
+ import { useFishjamContext } from "./useFishjamContext";
4
4
  const getTracks = (stream) => {
5
5
  const video = stream.getVideoTracks()[0];
6
6
  const audio = stream.getAudioTracks()[0] ?? null;
@@ -8,17 +8,29 @@ const getTracks = (stream) => {
8
8
  };
9
9
  export const useScreenShare = () => {
10
10
  const ctx = useFishjamContext();
11
- const [state, setState] = ctx.screenshareState;
12
- const tsClient = ctx.state.client.getTsClient();
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;
13
18
  const startStreaming = async (props) => {
14
19
  const stream = await navigator.mediaDevices.getDisplayMedia({
15
20
  video: props?.videoConstraints ?? true,
16
21
  audio: props?.audioConstraints ?? true,
17
22
  });
18
- const [video, audio] = getTracks(stream);
19
- const addTrackPromises = [tsClient.addTrack(video)];
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 })];
20
32
  if (audio)
21
- addTrackPromises.push(tsClient.addTrack(audio));
33
+ addTrackPromises.push(tsClient.addTrack(audio, { displayName, type: "screenShareAudio", paused: false }));
22
34
  const [videoId, audioId] = await Promise.all(addTrackPromises);
23
35
  setState({ stream, trackIds: { videoId, audioId } });
24
36
  };
@@ -31,15 +43,25 @@ export const useScreenShare = () => {
31
43
  }
32
44
  await Promise.all(addTrackPromises);
33
45
  };
46
+ const cleanMiddleware = useCallback(() => {
47
+ cleanMiddlewareFnRef.current?.();
48
+ cleanMiddlewareFnRef.current = null;
49
+ }, []);
34
50
  const setTracksMiddleware = async (middleware) => {
35
51
  if (!state?.stream)
36
52
  return;
37
- const [videoTrack, audioTrack] = getTracks(state.stream);
38
- const [newVideoTrack, newAudioTrack] = middleware?.(videoTrack, audioTrack) ?? [videoTrack, audioTrack];
39
- await replaceTracks(newVideoTrack, newAudioTrack);
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);
40
62
  };
41
63
  const stopStreaming = useCallback(async () => {
42
- if (!state) {
64
+ if (!state.stream) {
43
65
  console.warn("No stream to stop");
44
66
  return;
45
67
  }
@@ -47,14 +69,16 @@ export const useScreenShare = () => {
47
69
  video.stop();
48
70
  if (audio)
49
71
  audio.stop();
50
- const removeTrackPromises = [tsClient.removeTrack(state.trackIds.videoId)];
72
+ const client = fishjamClientRef.current;
73
+ const removeTrackPromises = [client.removeTrack(state.trackIds.videoId)];
51
74
  if (state.trackIds.audioId)
52
- removeTrackPromises.push(tsClient.removeTrack(state.trackIds.audioId));
75
+ removeTrackPromises.push(client.removeTrack(state.trackIds.audioId));
53
76
  await Promise.all(removeTrackPromises);
54
- setState(null);
55
- }, [state, tsClient, setState]);
77
+ cleanMiddleware();
78
+ setState(({ tracksMiddleware }) => ({ stream: null, trackIds: null, tracksMiddleware }));
79
+ }, [state, fishjamClientRef, setState, cleanMiddleware]);
56
80
  useEffect(() => {
57
- if (!state)
81
+ if (!state.stream)
58
82
  return;
59
83
  const [video, audio] = getTracks(state.stream);
60
84
  const trackEndedHandler = () => {
@@ -68,18 +92,19 @@ export const useScreenShare = () => {
68
92
  };
69
93
  }, [state, stopStreaming]);
70
94
  useEffect(() => {
95
+ const client = fishjamClientRef.current;
71
96
  const onDisconnected = () => {
72
- stopStreaming();
97
+ if (stream) {
98
+ stopStreaming();
99
+ }
73
100
  };
74
- tsClient.on("disconnected", onDisconnected);
101
+ client.on("disconnected", onDisconnected);
75
102
  return () => {
76
- tsClient.removeListener("disconnected", onDisconnected);
103
+ client.removeListener("disconnected", onDisconnected);
77
104
  };
78
- }, [stopStreaming, tsClient]);
79
- const stream = state?.stream ?? null;
80
- const [videoTrack, audioTrack] = stream ? getTracks(stream) : [null, null];
81
- const videoBroadcast = state ? getRemoteOrLocalTrack(tsClient, state.trackIds.videoId) : null;
82
- const audioBroadcast = state?.trackIds.audioId ? getRemoteOrLocalTrack(tsClient, state.trackIds.audioId) : null;
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;
83
108
  return {
84
109
  startStreaming,
85
110
  stopStreaming,
@@ -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
+ };