@100mslive/react-sdk 0.0.16 → 0.0.18-alpha.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/dist/hooks/useAVToggle.js +1 -0
- package/dist/hooks/useAVToggle.js.map +1 -0
- package/dist/hooks/useAudioLevelStyles.js +1 -0
- package/dist/hooks/useAudioLevelStyles.js.map +1 -0
- package/dist/hooks/useAutoplayError.js +1 -0
- package/dist/hooks/useAutoplayError.js.map +1 -0
- package/dist/hooks/useCustomEvent.js +1 -0
- package/dist/hooks/useCustomEvent.js.map +1 -0
- package/dist/hooks/useDevices.js +1 -0
- package/dist/hooks/useDevices.js.map +1 -0
- package/dist/hooks/useParticipantList.js +1 -0
- package/dist/hooks/useParticipantList.js.map +1 -0
- package/dist/hooks/usePreviewJoin.js +1 -0
- package/dist/hooks/usePreviewJoin.js.map +1 -0
- package/dist/hooks/useRecordingStreaming.js +1 -0
- package/dist/hooks/useRecordingStreaming.js.map +1 -0
- package/dist/hooks/useRemoteAVToggle.js +1 -0
- package/dist/hooks/useRemoteAVToggle.js.map +1 -0
- package/dist/hooks/useScreenShare.js +1 -0
- package/dist/hooks/useScreenShare.js.map +1 -0
- package/dist/hooks/useVideo.js +1 -0
- package/dist/hooks/useVideo.js.map +1 -0
- package/dist/hooks/useVideoList.js +1 -0
- package/dist/hooks/useVideoList.js.map +1 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/node_modules/tslib/tslib.es6.js +1 -0
- package/dist/node_modules/tslib/tslib.es6.js.map +1 -0
- package/dist/node_modules/zustand/esm/shallow.js +1 -0
- package/dist/node_modules/zustand/esm/shallow.js.map +1 -0
- package/dist/primitives/HmsRoomProvider.js +1 -0
- package/dist/primitives/HmsRoomProvider.js.map +1 -0
- package/dist/primitives/store.js +1 -0
- package/dist/primitives/store.js.map +1 -0
- package/dist/utils/commons.js +1 -0
- package/dist/utils/commons.js.map +1 -0
- package/dist/utils/groupBy.js +1 -0
- package/dist/utils/groupBy.js.map +1 -0
- package/dist/utils/isBrowser.js +1 -0
- package/dist/utils/isBrowser.js.map +1 -0
- package/dist/utils/layout.js +1 -0
- package/dist/utils/layout.js.map +1 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +6 -5
- package/src/hooks/types.ts +6 -0
- package/src/hooks/useAVToggle.ts +62 -0
- package/src/hooks/useAudioLevelStyles.ts +37 -0
- package/src/hooks/useAutoplayError.ts +41 -0
- package/src/hooks/useCustomEvent.ts +85 -0
- package/src/hooks/useDevices.ts +93 -0
- package/src/hooks/useParticipantList.ts +27 -0
- package/src/hooks/usePreviewJoin.ts +132 -0
- package/src/hooks/useRecordingStreaming.ts +34 -0
- package/src/hooks/useRemoteAVToggle.ts +101 -0
- package/src/hooks/useScreenShare.ts +76 -0
- package/src/hooks/useVideo.ts +77 -0
- package/src/hooks/useVideoList.ts +162 -0
- package/src/index.ts +40 -0
- package/src/primitives/HmsRoomProvider.ts +203 -0
- package/src/primitives/store.ts +59 -0
- package/src/primitives/types.ts +6 -0
- package/src/utils/commons.ts +14 -0
- package/src/utils/groupBy.ts +29 -0
- package/src/utils/isBrowser.ts +1 -0
- package/src/utils/layout.ts +466 -0
- package/src/utils/logger.ts +62 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { HMSPeer, HMSTrack, HMSTrackID, selectTracksMap } from '@100mslive/hms-video-store';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
calculateLayoutSizes,
|
|
5
|
+
chunkElements,
|
|
6
|
+
getModeAspectRatio,
|
|
7
|
+
getVideoTracksFromPeers,
|
|
8
|
+
TrackWithPeer,
|
|
9
|
+
} from '../utils/layout';
|
|
10
|
+
import { useHMSVanillaStore } from '../primitives/HmsRoomProvider';
|
|
11
|
+
import { useResizeDetector } from 'react-resize-detector';
|
|
12
|
+
|
|
13
|
+
export interface useVideoListInput {
|
|
14
|
+
/**
|
|
15
|
+
* peers is the list of all peers you need to display
|
|
16
|
+
*/
|
|
17
|
+
peers: HMSPeer[];
|
|
18
|
+
/**
|
|
19
|
+
* Max tiles in a page. Overrides maxRowCount and maxColCount
|
|
20
|
+
*/
|
|
21
|
+
maxTileCount?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Max rows in a page. Only applied if maxTileCount is not present
|
|
24
|
+
*/
|
|
25
|
+
maxRowCount?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Max columns in a page. Only applied if maxTileCount and maxRowCount are not present
|
|
28
|
+
*/
|
|
29
|
+
maxColCount?: number;
|
|
30
|
+
/**
|
|
31
|
+
* A function which tells whether to show the screenShare for a peer who is screensharing. A peer is passed
|
|
32
|
+
* and a boolean value is expected.
|
|
33
|
+
* This can be useful if there are multiple screenShares in the room where you may want to show the main one in the
|
|
34
|
+
* center view and others in video list along side other tiles. No screenShare is included by default.
|
|
35
|
+
* e.g. includeScreenShare = (peer) => return peer.id !== mainScreenSharingPeer.id
|
|
36
|
+
*/
|
|
37
|
+
includeScreenShareForPeer?: (peer: HMSPeer) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Aspect ratio of VideoTiles, ideally this should be the same as the aspect ratio selected for
|
|
40
|
+
* capture in the dashboard template.
|
|
41
|
+
*/
|
|
42
|
+
aspectRatio?: { width: number; height: number };
|
|
43
|
+
/**
|
|
44
|
+
* By default this will be true. Only publishing(audio/video/screen) peers in the passed in peer list
|
|
45
|
+
* will be filtered. If you wish to show all peers, pass false for this.
|
|
46
|
+
*/
|
|
47
|
+
filterNonPublishingPeers?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Height that would be subtracted from the parent's height to give the available height, use case: if your pagination is inside the parent component then offsetY would be the height of pagination
|
|
50
|
+
*/
|
|
51
|
+
offsetY?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface useVideoListTile extends TrackWithPeer {
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface useVideoResult {
|
|
60
|
+
/**
|
|
61
|
+
* This returns a list of all pages with every page containing the list of all tiles on it.
|
|
62
|
+
*/
|
|
63
|
+
pagesWithTiles: useVideoListTile[][];
|
|
64
|
+
/**
|
|
65
|
+
* add the ref to the element going to render the video list, this is used to measure the available
|
|
66
|
+
* space/dimensions in order to calculate the best fit
|
|
67
|
+
*/
|
|
68
|
+
ref: React.MutableRefObject<any>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const DEFAULTS = {
|
|
72
|
+
aspectRatio: {
|
|
73
|
+
width: 1,
|
|
74
|
+
height: 1,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* This hook can be used to build a paginated gallery view of video tiles. You can give the hook
|
|
80
|
+
* a list of all the peers which need to be shown and it tells you how to structure the UI by giving
|
|
81
|
+
* a list of pages with every page having a list of video tiles.
|
|
82
|
+
* Please check the documentation of input and output types for more details.
|
|
83
|
+
*/
|
|
84
|
+
export const useVideoList = ({
|
|
85
|
+
peers,
|
|
86
|
+
maxTileCount,
|
|
87
|
+
maxColCount,
|
|
88
|
+
maxRowCount,
|
|
89
|
+
includeScreenShareForPeer = () => false,
|
|
90
|
+
aspectRatio = DEFAULTS.aspectRatio,
|
|
91
|
+
filterNonPublishingPeers = true,
|
|
92
|
+
offsetY = 0,
|
|
93
|
+
}: useVideoListInput): useVideoResult => {
|
|
94
|
+
const { width = 0, height = 0, ref } = useResizeDetector();
|
|
95
|
+
const store = useHMSVanillaStore();
|
|
96
|
+
// using vanilla store as we don't need re-rendering everytime something in a track changes
|
|
97
|
+
const tracksMap: Record<HMSTrackID, HMSTrack> = store.getState(selectTracksMap);
|
|
98
|
+
const tracksWithPeer: TrackWithPeer[] = getVideoTracksFromPeers(
|
|
99
|
+
peers,
|
|
100
|
+
tracksMap,
|
|
101
|
+
includeScreenShareForPeer,
|
|
102
|
+
filterNonPublishingPeers,
|
|
103
|
+
);
|
|
104
|
+
const finalAspectRatio = useMemo(() => {
|
|
105
|
+
if (aspectRatio) {
|
|
106
|
+
return aspectRatio;
|
|
107
|
+
}
|
|
108
|
+
const modeAspectRatio = getModeAspectRatio(tracksWithPeer);
|
|
109
|
+
// Default to 1 if there are no video tracks
|
|
110
|
+
return {
|
|
111
|
+
width: modeAspectRatio || 1,
|
|
112
|
+
height: 1,
|
|
113
|
+
};
|
|
114
|
+
}, [aspectRatio, tracksWithPeer]);
|
|
115
|
+
const count = tracksWithPeer.length;
|
|
116
|
+
const {
|
|
117
|
+
tilesInFirstPage,
|
|
118
|
+
defaultWidth,
|
|
119
|
+
defaultHeight,
|
|
120
|
+
lastPageWidth,
|
|
121
|
+
lastPageHeight,
|
|
122
|
+
isLastPageDifferentFromFirstPage,
|
|
123
|
+
} = useMemo(
|
|
124
|
+
() =>
|
|
125
|
+
calculateLayoutSizes({
|
|
126
|
+
count,
|
|
127
|
+
parentWidth: Math.floor(width),
|
|
128
|
+
parentHeight: Math.floor(height) - Math.min(height, offsetY),
|
|
129
|
+
maxTileCount,
|
|
130
|
+
maxRowCount,
|
|
131
|
+
maxColCount,
|
|
132
|
+
aspectRatio: finalAspectRatio,
|
|
133
|
+
}),
|
|
134
|
+
[count, width, height, maxTileCount, maxRowCount, maxColCount, finalAspectRatio, offsetY],
|
|
135
|
+
);
|
|
136
|
+
const chunkedTracksWithPeer = useMemo(
|
|
137
|
+
() =>
|
|
138
|
+
chunkElements<TrackWithPeer>({
|
|
139
|
+
elements: tracksWithPeer,
|
|
140
|
+
tilesInFirstPage,
|
|
141
|
+
onlyOnePage: false,
|
|
142
|
+
isLastPageDifferentFromFirstPage,
|
|
143
|
+
defaultWidth,
|
|
144
|
+
defaultHeight,
|
|
145
|
+
lastPageWidth,
|
|
146
|
+
lastPageHeight,
|
|
147
|
+
}),
|
|
148
|
+
[
|
|
149
|
+
tracksWithPeer,
|
|
150
|
+
tilesInFirstPage,
|
|
151
|
+
isLastPageDifferentFromFirstPage,
|
|
152
|
+
defaultWidth,
|
|
153
|
+
defaultHeight,
|
|
154
|
+
lastPageWidth,
|
|
155
|
+
lastPageHeight,
|
|
156
|
+
],
|
|
157
|
+
);
|
|
158
|
+
return {
|
|
159
|
+
pagesWithTiles: chunkedTracksWithPeer,
|
|
160
|
+
ref,
|
|
161
|
+
};
|
|
162
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
HMSRoomProvider,
|
|
3
|
+
useHMSStore,
|
|
4
|
+
useHMSActions,
|
|
5
|
+
useHMSNotifications,
|
|
6
|
+
useHMSVanillaStore,
|
|
7
|
+
useHMSVanillaNotifications,
|
|
8
|
+
useHMSStatsStore,
|
|
9
|
+
} from './primitives/HmsRoomProvider';
|
|
10
|
+
export { usePreviewJoin } from './hooks/usePreviewJoin';
|
|
11
|
+
export { useAVToggle } from './hooks/useAVToggle';
|
|
12
|
+
export { useVideo } from './hooks/useVideo';
|
|
13
|
+
export { useScreenShare } from './hooks/useScreenShare';
|
|
14
|
+
export { useRemoteAVToggle } from './hooks/useRemoteAVToggle';
|
|
15
|
+
export { useVideoList } from './hooks/useVideoList';
|
|
16
|
+
export { useAudioLevelStyles } from './hooks/useAudioLevelStyles';
|
|
17
|
+
export { useDevices, DeviceType } from './hooks/useDevices';
|
|
18
|
+
export { useParticipantList } from './hooks/useParticipantList';
|
|
19
|
+
export { useRecordingStreaming } from './hooks/useRecordingStreaming';
|
|
20
|
+
export { useAutoplayError } from './hooks/useAutoplayError';
|
|
21
|
+
export { useCustomEvent } from './hooks/useCustomEvent';
|
|
22
|
+
// types
|
|
23
|
+
export type { hooksErrHandler } from './hooks/types';
|
|
24
|
+
export type { usePreviewInput, usePreviewResult } from './hooks/usePreviewJoin';
|
|
25
|
+
export type { useVideoListInput, useVideoResult, useVideoListTile } from './hooks/useVideoList';
|
|
26
|
+
export type { useAVToggleResult } from './hooks/useAVToggle';
|
|
27
|
+
export type { useDevicesResult } from './hooks/useDevices';
|
|
28
|
+
export type { useScreenShareResult } from './hooks/useScreenShare';
|
|
29
|
+
export type { useRemoteAVToggleResult } from './hooks/useRemoteAVToggle';
|
|
30
|
+
export type { useRecordingStreamingResult } from './hooks/useRecordingStreaming';
|
|
31
|
+
export type { useParticipantListResult } from './hooks/useParticipantList';
|
|
32
|
+
export type { useVideoInput, useVideoOutput } from './hooks/useVideo';
|
|
33
|
+
export type { useAutoplayErrorResult } from './hooks/useAutoplayError';
|
|
34
|
+
export type { useCustomEventInput, useCustomEventResult } from './hooks/useCustomEvent';
|
|
35
|
+
|
|
36
|
+
// helpers
|
|
37
|
+
export { throwErrorHandler } from './utils/commons';
|
|
38
|
+
|
|
39
|
+
// reexport everything from store so app can import everything directly from this
|
|
40
|
+
export * from '@100mslive/hms-video-store';
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import React, { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
HMSReactiveStore,
|
|
4
|
+
HMSStore,
|
|
5
|
+
HMSActions,
|
|
6
|
+
HMSNotification,
|
|
7
|
+
HMSNotifications,
|
|
8
|
+
HMSStatsStore,
|
|
9
|
+
HMSStats,
|
|
10
|
+
HMSStoreWrapper,
|
|
11
|
+
HMSNotificationTypes,
|
|
12
|
+
} from '@100mslive/hms-video-store';
|
|
13
|
+
import create from 'zustand';
|
|
14
|
+
import { HMSContextProviderProps, makeHMSStoreHook, hooksErrorMessage, makeHMSStatsStoreHook } from './store';
|
|
15
|
+
import { isBrowser } from '../utils/isBrowser';
|
|
16
|
+
|
|
17
|
+
export interface HMSRoomProviderProps {
|
|
18
|
+
actions?: HMSActions;
|
|
19
|
+
store?: HMSStoreWrapper;
|
|
20
|
+
notifications?: HMSNotifications;
|
|
21
|
+
stats?: HMSStats;
|
|
22
|
+
/**
|
|
23
|
+
* if true this will enable webrtc stats collection
|
|
24
|
+
*/
|
|
25
|
+
isHMSStatsOn?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* only one context is being created currently. This would need to be changed if multiple
|
|
30
|
+
* rooms have to be supported, where every room will have its own context, provider, store and actions.
|
|
31
|
+
*/
|
|
32
|
+
const HMSContext = createContext<HMSContextProviderProps | null>(null);
|
|
33
|
+
|
|
34
|
+
let providerProps: HMSContextProviderProps;
|
|
35
|
+
/**
|
|
36
|
+
* top level wrapper for using react sdk hooks. This doesn't have any mandatory arguments, if you are already
|
|
37
|
+
* initialising the sdk on your side, you can pass in the primitives from there as well to use hooks for
|
|
38
|
+
* react part of your code.
|
|
39
|
+
* @constructor
|
|
40
|
+
*/
|
|
41
|
+
// eslint-disable-next-line complexity
|
|
42
|
+
export const HMSRoomProvider: React.FC<PropsWithChildren<HMSRoomProviderProps>> = ({
|
|
43
|
+
children,
|
|
44
|
+
actions,
|
|
45
|
+
store,
|
|
46
|
+
notifications,
|
|
47
|
+
stats,
|
|
48
|
+
isHMSStatsOn = false,
|
|
49
|
+
}) => {
|
|
50
|
+
if (!providerProps) {
|
|
51
|
+
// adding a dummy function for setstate and destroy because zustan'd create expects them
|
|
52
|
+
// to be present but we don't expose them from the store.
|
|
53
|
+
const errFn = () => {
|
|
54
|
+
throw new Error('modifying store is not allowed');
|
|
55
|
+
};
|
|
56
|
+
if (actions && store) {
|
|
57
|
+
providerProps = {
|
|
58
|
+
actions: actions,
|
|
59
|
+
store: create<HMSStore>({
|
|
60
|
+
...store,
|
|
61
|
+
setState: errFn,
|
|
62
|
+
destroy: errFn,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
if (notifications) {
|
|
66
|
+
providerProps.notifications = notifications;
|
|
67
|
+
}
|
|
68
|
+
if (stats) {
|
|
69
|
+
providerProps.statsStore = create<HMSStatsStore>({
|
|
70
|
+
getState: stats.getState,
|
|
71
|
+
subscribe: stats.subscribe,
|
|
72
|
+
setState: errFn,
|
|
73
|
+
destroy: errFn,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const hmsReactiveStore = new HMSReactiveStore();
|
|
78
|
+
providerProps = {
|
|
79
|
+
actions: hmsReactiveStore.getActions(),
|
|
80
|
+
store: create<HMSStore>({
|
|
81
|
+
...hmsReactiveStore.getStore(),
|
|
82
|
+
setState: errFn,
|
|
83
|
+
destroy: errFn,
|
|
84
|
+
}), // convert vanilla store in react hook
|
|
85
|
+
notifications: hmsReactiveStore.getNotifications(),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (isHMSStatsOn) {
|
|
89
|
+
const stats = hmsReactiveStore.getStats();
|
|
90
|
+
providerProps.statsStore = create<HMSStatsStore>({
|
|
91
|
+
getState: stats.getState,
|
|
92
|
+
subscribe: stats.subscribe,
|
|
93
|
+
setState: errFn,
|
|
94
|
+
destroy: errFn,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (isBrowser) {
|
|
102
|
+
window.addEventListener('beforeunload', () => providerProps.actions.leave());
|
|
103
|
+
window.addEventListener('onunload', () => providerProps.actions.leave());
|
|
104
|
+
}
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
return React.createElement(HMSContext.Provider, { value: providerProps }, children);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `useHMSStore` is a read only hook which can be passed a selector to read data.
|
|
112
|
+
* The hook can only be used in a component if HMSRoomProvider is present in its ancestors.
|
|
113
|
+
*/
|
|
114
|
+
export const useHMSStore = makeHMSStoreHook(HMSContext);
|
|
115
|
+
|
|
116
|
+
export const useHMSStatsStore = makeHMSStatsStoreHook(HMSContext);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* `useHMSVanillaStore` is a read only hook which returns the vanilla HMSStore.
|
|
120
|
+
* Usage:
|
|
121
|
+
* ```
|
|
122
|
+
* const hmsStore = useHMSVanillaStore();
|
|
123
|
+
* const dominantSpeaker = hmsStore.getState(selectDominantSpeaker);
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Note: There's no need to use the vanilla hmsStore in React components.
|
|
127
|
+
* This is used in rare cases where the store needs to be accessed outside a React component.
|
|
128
|
+
* For almost every case, `useHMSStore` would get the job done.
|
|
129
|
+
*/
|
|
130
|
+
export const useHMSVanillaStore = () => {
|
|
131
|
+
const HMSContextConsumer = useContext(HMSContext);
|
|
132
|
+
if (!HMSContextConsumer) {
|
|
133
|
+
throw new Error(hooksErrorMessage);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return HMSContextConsumer.store;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
* `useHMSVanillaNotifications` returns the vanilla HMSNotifications object. This makes it a bit easier to ensure
|
|
141
|
+
* a notification is processed only once in your components. The other way is to use the hook version and put
|
|
142
|
+
* the component high enough in the chain.
|
|
143
|
+
* Usage:
|
|
144
|
+
* ```
|
|
145
|
+
* useEffect(() => {
|
|
146
|
+
* const unsub = notifications.onNotification((notification) => {
|
|
147
|
+
* console.log(notification);
|
|
148
|
+
* }, notificationType);
|
|
149
|
+
* return unsub;
|
|
150
|
+
* }, [])
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export const useHMSVanillaNotifications = () => {
|
|
154
|
+
const HMSContextConsumer = useContext(HMSContext);
|
|
155
|
+
if (!HMSContextConsumer) {
|
|
156
|
+
throw new Error(hooksErrorMessage);
|
|
157
|
+
}
|
|
158
|
+
return HMSContextConsumer.notifications;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
* `useHMSActions` is a write only hook which can be used to dispatch actions.
|
|
163
|
+
*/
|
|
164
|
+
export const useHMSActions = () => {
|
|
165
|
+
const HMSContextConsumer = useContext(HMSContext);
|
|
166
|
+
if (!HMSContextConsumer) {
|
|
167
|
+
throw new Error(hooksErrorMessage);
|
|
168
|
+
}
|
|
169
|
+
return HMSContextConsumer.actions;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* `useHMSNotifications` is a read only hook which gives the latest notification(HMSNotification) received.
|
|
174
|
+
* @param type can be a string or an array of string for the types of notifications to listen to. If an array is passed
|
|
175
|
+
* either declare it outside the functional component or use a useMemo to make sure its reference stays same across
|
|
176
|
+
* rerenders for performance reasons.
|
|
177
|
+
*/
|
|
178
|
+
export const useHMSNotifications = (type?: HMSNotificationTypes | HMSNotificationTypes[]) => {
|
|
179
|
+
const HMSContextConsumer = useContext(HMSContext);
|
|
180
|
+
const [notification, setNotification] = useState<HMSNotification | null>(null);
|
|
181
|
+
|
|
182
|
+
if (!HMSContextConsumer) {
|
|
183
|
+
throw new Error(hooksErrorMessage);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (!HMSContextConsumer.notifications) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const unsubscribe = HMSContextConsumer.notifications.onNotification(
|
|
191
|
+
(notification: HMSNotification) => setNotification(notification),
|
|
192
|
+
type,
|
|
193
|
+
);
|
|
194
|
+
return unsubscribe;
|
|
195
|
+
}, [HMSContextConsumer.notifications, type]);
|
|
196
|
+
|
|
197
|
+
return notification;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const useIsHMSStatsOn = () => {
|
|
201
|
+
const HMSContextConsumer = useContext(HMSContext);
|
|
202
|
+
return Boolean(HMSContextConsumer?.statsStore);
|
|
203
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { EqualityChecker, StateSelector } from 'zustand';
|
|
2
|
+
import React, { useContext } from 'react';
|
|
3
|
+
import shallow from 'zustand/shallow';
|
|
4
|
+
import { HMSActions, HMSStore, HMSNotifications, HMSStatsStore, IStoreReadOnly } from '@100mslive/hms-video-store';
|
|
5
|
+
import HMSLogger from '../utils/logger';
|
|
6
|
+
|
|
7
|
+
export interface IHMSReactStore<S extends HMSStore | HMSStatsStore> extends IStoreReadOnly<S> {
|
|
8
|
+
<U>(selector: StateSelector<S, U>, equalityFn?: EqualityChecker<U>): U;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const hooksErrorMessage =
|
|
12
|
+
'It seems like you forgot to add your component within a top level HMSRoomProvider, please refer to 100ms react docs(https://docs.100ms.live/javascript/v2/features/integration#react-hooks) to check on the required steps for using this hook.';
|
|
13
|
+
|
|
14
|
+
export interface HMSContextProviderProps {
|
|
15
|
+
actions: HMSActions; // for actions which may also mutate store
|
|
16
|
+
store: IHMSReactStore<HMSStore>; // readonly store, don't mutate this
|
|
17
|
+
notifications?: HMSNotifications;
|
|
18
|
+
statsStore?: IHMSReactStore<HMSStatsStore>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function makeHMSStoreHook(hmsContext: React.Context<HMSContextProviderProps | null>) {
|
|
22
|
+
const useHMSStore = <StateSlice>(
|
|
23
|
+
selector: StateSelector<HMSStore, StateSlice>,
|
|
24
|
+
equalityFn: EqualityChecker<StateSlice> = shallow,
|
|
25
|
+
) => {
|
|
26
|
+
if (!selector) {
|
|
27
|
+
HMSLogger.w(
|
|
28
|
+
'fetching full store without passing any selector may have a heavy performance impact on your website.',
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const HMSContextConsumer = useContext(hmsContext);
|
|
32
|
+
if (!HMSContextConsumer) {
|
|
33
|
+
throw new Error(hooksErrorMessage);
|
|
34
|
+
}
|
|
35
|
+
const useStore = HMSContextConsumer.store;
|
|
36
|
+
return useStore(selector, equalityFn);
|
|
37
|
+
};
|
|
38
|
+
return useHMSStore;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function makeHMSStatsStoreHook(hmsContext: React.Context<HMSContextProviderProps | null>) {
|
|
42
|
+
const useHMSStatsStore = <StateSlice>(
|
|
43
|
+
selector: StateSelector<HMSStatsStore, StateSlice>,
|
|
44
|
+
equalityFn: EqualityChecker<StateSlice> = shallow,
|
|
45
|
+
) => {
|
|
46
|
+
if (!selector) {
|
|
47
|
+
HMSLogger.w(
|
|
48
|
+
'fetching full store without passing any selector may have a heavy performance impact on your website.',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const HMSContextConsumer = useContext(hmsContext);
|
|
52
|
+
if (!HMSContextConsumer) {
|
|
53
|
+
throw new Error(hooksErrorMessage);
|
|
54
|
+
}
|
|
55
|
+
const useStore = HMSContextConsumer.statsStore;
|
|
56
|
+
return useStore?.(selector, equalityFn);
|
|
57
|
+
};
|
|
58
|
+
return useHMSStatsStore;
|
|
59
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { EqualityChecker, StateSelector } from 'zustand';
|
|
2
|
+
import { HMSStore, IStoreReadOnly, HMSStatsStore } from '@100mslive/hms-video-store';
|
|
3
|
+
|
|
4
|
+
export interface IHMSReactStore<S extends HMSStore | HMSStatsStore> extends IStoreReadOnly<S> {
|
|
5
|
+
<U>(selector: StateSelector<S, U>, equalityFn?: EqualityChecker<U>): U;
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { hooksErrHandler } from '../hooks/types';
|
|
2
|
+
import HMSLogger from './logger';
|
|
3
|
+
|
|
4
|
+
const TAG = 'react-sdk';
|
|
5
|
+
|
|
6
|
+
export const logErrorHandler: hooksErrHandler = (err: Error, method?: string) => HMSLogger.e(TAG, method, err);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* pass in this error handler to get the error thrown back to the UI for further handling, showing toast etc.
|
|
10
|
+
* @param err
|
|
11
|
+
*/
|
|
12
|
+
export const throwErrorHandler: hooksErrHandler = (err: Error) => {
|
|
13
|
+
throw err;
|
|
14
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { HMSPeer } from '@100mslive/hms-video-store';
|
|
2
|
+
/**
|
|
3
|
+
* Give array like [
|
|
4
|
+
* { name: 'peer1', id: 1, roleName: 'role1' },
|
|
5
|
+
* { name: 'peer2', id: 2, roleName: 'role2' }
|
|
6
|
+
*]
|
|
7
|
+
* the output will be
|
|
8
|
+
* {
|
|
9
|
+
* 'role1': [{'name': 'peer1', id: 1, roleName: 'role1'}],
|
|
10
|
+
* 'role2': [{ name: 'peer2', id: 2, roleName: 'role2' }]
|
|
11
|
+
* }
|
|
12
|
+
* @param {HMSPeer[]} peers
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export const groupByRoles = (peers: HMSPeer[]) => {
|
|
16
|
+
if (!peers || !Array.isArray(peers) || peers.length === 0) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
return peers.reduce((res: Record<string, HMSPeer[]>, peer) => {
|
|
20
|
+
if (!peer.roleName) {
|
|
21
|
+
return res;
|
|
22
|
+
}
|
|
23
|
+
if (!res[peer.roleName]) {
|
|
24
|
+
res[peer.roleName] = [];
|
|
25
|
+
}
|
|
26
|
+
res[peer.roleName].push(peer);
|
|
27
|
+
return res;
|
|
28
|
+
}, {});
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isBrowser = typeof window !== 'undefined';
|