@stream-io/video-react-sdk 1.17.0 → 1.18.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/CHANGELOG.md +23 -0
- package/dist/css/styles.css +16 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/index.cjs.js +255 -56
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +255 -57
- package/dist/index.es.js.map +1 -1
- package/dist/src/core/components/CallLayout/LivestreamLayout.d.ts +14 -0
- package/dist/src/hooks/useEffectEvent.d.ts +1 -0
- package/dist/src/translations/index.d.ts +4 -0
- package/dist/src/wrappers/LivestreamPlayer/LivestreamPlayer.d.ts +21 -1
- package/package.json +4 -4
- package/src/core/components/CallLayout/LivestreamLayout.tsx +48 -6
- package/src/hooks/useEffectEvent.ts +16 -0
- package/src/hooks/usePersistedDevicePreferences.ts +181 -72
- package/src/translations/en.json +4 -0
- package/src/wrappers/LivestreamPlayer/LivestreamPlayer.tsx +119 -8
|
@@ -45,3 +45,17 @@ export declare const LivestreamLayout: {
|
|
|
45
45
|
(props: LivestreamLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
46
46
|
displayName: string;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* The props for the {@link LivestreamLayout} component.
|
|
50
|
+
*/
|
|
51
|
+
export type BackstageLayoutProps = {
|
|
52
|
+
/**
|
|
53
|
+
* Whether to show the counter for participants that joined before
|
|
54
|
+
* the livestream went live. Defaults to `true`.
|
|
55
|
+
*/
|
|
56
|
+
showEarlyParticipantCount?: boolean;
|
|
57
|
+
};
|
|
58
|
+
export declare const BackstageLayout: {
|
|
59
|
+
(props: BackstageLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
displayName: string;
|
|
61
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useEffectEvent<P extends unknown[]>(cb: ((...args: P) => void) | undefined): (...args: P) => void;
|
|
@@ -13,6 +13,10 @@ export declare const translations: {
|
|
|
13
13
|
Video: string;
|
|
14
14
|
"You are muted. Unmute to speak.": string;
|
|
15
15
|
Live: string;
|
|
16
|
+
"Livestream starts soon": string;
|
|
17
|
+
"Livestream starts at {{ startsAt }}": string;
|
|
18
|
+
"{{ count }} participants joined early_one": string;
|
|
19
|
+
"{{ count }} participants joined early_other": string;
|
|
16
20
|
"You can now speak.": string;
|
|
17
21
|
"Awaiting for an approval to speak.": string;
|
|
18
22
|
"You can no longer speak.": string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LivestreamLayoutProps } from '../../core';
|
|
1
|
+
import { BackstageLayoutProps, LivestreamLayoutProps } from '../../core';
|
|
2
2
|
export type LivestreamPlayerProps = {
|
|
3
3
|
/**
|
|
4
4
|
* The call type. Usually `livestream`.
|
|
@@ -8,9 +8,29 @@ export type LivestreamPlayerProps = {
|
|
|
8
8
|
* The call ID.
|
|
9
9
|
*/
|
|
10
10
|
callId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Determines when the viewer joins the call.
|
|
13
|
+
*
|
|
14
|
+
* `"asap"` behavior means joining the call as soon as it is possible
|
|
15
|
+
* (either the `join_ahead_time_seconds` setting allows it, or the user
|
|
16
|
+
* has a the capability to join backstage).
|
|
17
|
+
*
|
|
18
|
+
* `"live"` behavior means joining the call when it goes live.
|
|
19
|
+
*
|
|
20
|
+
* @default "asap"
|
|
21
|
+
*/
|
|
22
|
+
joinBehavior?: 'asap' | 'live';
|
|
11
23
|
/**
|
|
12
24
|
* The props for the {@link LivestreamLayout} component.
|
|
13
25
|
*/
|
|
14
26
|
layoutProps?: LivestreamLayoutProps;
|
|
27
|
+
/**
|
|
28
|
+
* The props for the {@link BackstageLayout} component.
|
|
29
|
+
*/
|
|
30
|
+
backstageProps?: BackstageLayoutProps;
|
|
31
|
+
/**
|
|
32
|
+
* Callback to handle errors while fetching or joining livestream.
|
|
33
|
+
*/
|
|
34
|
+
onError?: (error: any) => void;
|
|
15
35
|
};
|
|
16
36
|
export declare const LivestreamPlayer: (props: LivestreamPlayerProps) => import("react/jsx-runtime").JSX.Element | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"main": "./dist/index.cjs.js",
|
|
5
5
|
"module": "./dist/index.es.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@floating-ui/react": "^0.27.6",
|
|
33
|
-
"@stream-io/video-client": "1.23.
|
|
33
|
+
"@stream-io/video-client": "1.23.2",
|
|
34
34
|
"@stream-io/video-filters-web": "0.2.1",
|
|
35
|
-
"@stream-io/video-react-bindings": "1.6.
|
|
35
|
+
"@stream-io/video-react-bindings": "1.6.5",
|
|
36
36
|
"chart.js": "^4.4.4",
|
|
37
37
|
"clsx": "^2.0.0",
|
|
38
38
|
"react-chartjs-2": "^5.3.0"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@rollup/plugin-replace": "^6.0.2",
|
|
47
47
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
48
48
|
"@stream-io/audio-filters-web": "^0.4.0",
|
|
49
|
-
"@stream-io/video-styling": "^1.
|
|
49
|
+
"@stream-io/video-styling": "^1.2.0",
|
|
50
50
|
"@types/react": "^19.1.3",
|
|
51
51
|
"@types/react-dom": "^19.1.3",
|
|
52
52
|
"react": "19.0.0",
|
|
@@ -75,7 +75,7 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
|
75
75
|
|
|
76
76
|
usePaginatedLayoutSortPreset(call);
|
|
77
77
|
|
|
78
|
-
const
|
|
78
|
+
const overlay = (
|
|
79
79
|
<ParticipantOverlay
|
|
80
80
|
showParticipantCount={props.showParticipantCount}
|
|
81
81
|
showDuration={props.showDuration}
|
|
@@ -85,7 +85,7 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
|
85
85
|
);
|
|
86
86
|
|
|
87
87
|
const { floatingParticipantProps, muted } = props;
|
|
88
|
-
const
|
|
88
|
+
const floatingParticipantOverlay = hasOngoingScreenShare && (
|
|
89
89
|
<ParticipantOverlay
|
|
90
90
|
// these elements aren't needed for the video feed
|
|
91
91
|
showParticipantCount={
|
|
@@ -104,7 +104,7 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
|
104
104
|
<ParticipantView
|
|
105
105
|
className="str-video__livestream-layout__screen-share"
|
|
106
106
|
participant={presenter}
|
|
107
|
-
ParticipantViewUI={
|
|
107
|
+
ParticipantViewUI={overlay}
|
|
108
108
|
trackType="screenShareTrack"
|
|
109
109
|
muteAudio // audio is rendered by ParticipantsAudio
|
|
110
110
|
/>
|
|
@@ -121,7 +121,7 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
|
121
121
|
),
|
|
122
122
|
)}
|
|
123
123
|
participant={currentSpeaker}
|
|
124
|
-
ParticipantViewUI={
|
|
124
|
+
ParticipantViewUI={floatingParticipantOverlay || overlay}
|
|
125
125
|
mirror={
|
|
126
126
|
props.mirrorLocalParticipantVideo !== false ? undefined : false
|
|
127
127
|
}
|
|
@@ -132,6 +132,50 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
|
132
132
|
);
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
+
LivestreamLayout.displayName = 'LivestreamLayout';
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The props for the {@link LivestreamLayout} component.
|
|
139
|
+
*/
|
|
140
|
+
export type BackstageLayoutProps = {
|
|
141
|
+
/**
|
|
142
|
+
* Whether to show the counter for participants that joined before
|
|
143
|
+
* the livestream went live. Defaults to `true`.
|
|
144
|
+
*/
|
|
145
|
+
showEarlyParticipantCount?: boolean;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const BackstageLayout = (props: BackstageLayoutProps) => {
|
|
149
|
+
const { showEarlyParticipantCount = true } = props;
|
|
150
|
+
const { useParticipantCount, useCallStartsAt } = useCallStateHooks();
|
|
151
|
+
const participantCount = useParticipantCount();
|
|
152
|
+
const startsAt = useCallStartsAt();
|
|
153
|
+
const { t } = useI18n();
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="str-video__livestream-layout__wrapper">
|
|
157
|
+
<div className="str-video__livestream-layout__backstage">
|
|
158
|
+
{startsAt && (
|
|
159
|
+
<span className="str-video__livestream-layout__starts-at">
|
|
160
|
+
{startsAt.getTime() < Date.now()
|
|
161
|
+
? t('Livestream starts soon')
|
|
162
|
+
: t('Livestream starts at {{ startsAt }}', { startsAt })}
|
|
163
|
+
</span>
|
|
164
|
+
)}
|
|
165
|
+
{showEarlyParticipantCount && (
|
|
166
|
+
<span className="str-video__livestream-layout__early-viewers-count">
|
|
167
|
+
{t('{{ count }} participants joined early', {
|
|
168
|
+
count: participantCount,
|
|
169
|
+
})}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
BackstageLayout.displayName = 'BackstageLayout';
|
|
178
|
+
|
|
135
179
|
const ParticipantOverlay = (props: {
|
|
136
180
|
enableFullScreen?: boolean;
|
|
137
181
|
showParticipantCount?: boolean;
|
|
@@ -189,8 +233,6 @@ const ParticipantOverlay = (props: {
|
|
|
189
233
|
);
|
|
190
234
|
};
|
|
191
235
|
|
|
192
|
-
LivestreamLayout.displayName = 'LivestreamLayout';
|
|
193
|
-
|
|
194
236
|
const useUpdateCallDuration = () => {
|
|
195
237
|
const { useIsCallLive, useCallSession } = useCallStateHooks();
|
|
196
238
|
const isCallLive = useIsCallLive();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useEffectEvent<P extends unknown[]>(
|
|
4
|
+
cb: ((...args: P) => void) | undefined,
|
|
5
|
+
): (...args: P) => void {
|
|
6
|
+
const cbRef = useRef<((...args: P) => void) | undefined>(undefined);
|
|
7
|
+
|
|
8
|
+
useLayoutEffect(() => {
|
|
9
|
+
cbRef.current = cb;
|
|
10
|
+
}, [cb]);
|
|
11
|
+
|
|
12
|
+
return useCallback((...args: P) => {
|
|
13
|
+
const callback = cbRef.current;
|
|
14
|
+
callback?.(...args);
|
|
15
|
+
}, []);
|
|
16
|
+
}
|
|
@@ -43,122 +43,231 @@ export const usePersistedDevicePreferences = (
|
|
|
43
43
|
): void => {
|
|
44
44
|
const {
|
|
45
45
|
useCallSettings,
|
|
46
|
-
|
|
46
|
+
useCallCallingState,
|
|
47
47
|
useMicrophoneState,
|
|
48
|
+
useCameraState,
|
|
48
49
|
useSpeakerState,
|
|
49
50
|
} = useCallStateHooks();
|
|
50
51
|
const settings = useCallSettings();
|
|
52
|
+
const callingState = useCallCallingState();
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
useCameraState(),
|
|
56
|
-
settings ? !settings.video.camera_default_on : undefined,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
usePersistedDevicePreference(
|
|
60
|
-
key,
|
|
61
|
-
'microphone',
|
|
62
|
-
useMicrophoneState(),
|
|
63
|
-
settings ? !settings.audio.mic_default_on : undefined,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
usePersistedDevicePreference(key, 'speaker', useSpeakerState(), false);
|
|
67
|
-
};
|
|
54
|
+
const microphoneState: DeviceState<'microphone'> = useMicrophoneState();
|
|
55
|
+
const cameraState: DeviceState<'camera'> = useCameraState();
|
|
56
|
+
const speakerState: DeviceState<'speaker'> = useSpeakerState();
|
|
68
57
|
|
|
69
|
-
const usePersistedDevicePreference = <K extends DeviceKey>(
|
|
70
|
-
key: string,
|
|
71
|
-
deviceKey: K,
|
|
72
|
-
state: DeviceState<K>,
|
|
73
|
-
defaultMuted?: boolean,
|
|
74
|
-
): void => {
|
|
75
|
-
const { useCallCallingState } = useCallStateHooks();
|
|
76
|
-
const callingState = useCallCallingState();
|
|
77
58
|
const [applyingState, setApplyingState] = useState<
|
|
78
59
|
'idle' | 'applying' | 'applied'
|
|
79
60
|
>('idle');
|
|
80
|
-
const manager = state[deviceKey];
|
|
81
61
|
|
|
82
62
|
useEffect(
|
|
83
63
|
function apply() {
|
|
84
64
|
if (
|
|
85
65
|
callingState === CallingState.LEFT ||
|
|
86
|
-
!
|
|
87
|
-
|
|
66
|
+
!microphoneState.devices.length ||
|
|
67
|
+
!cameraState.devices.length ||
|
|
68
|
+
!speakerState.devices ||
|
|
69
|
+
!settings ||
|
|
88
70
|
applyingState !== 'idle'
|
|
89
71
|
) {
|
|
90
72
|
return;
|
|
91
73
|
}
|
|
92
74
|
|
|
93
|
-
const preferences = parseLocalDevicePreferences(key);
|
|
94
|
-
const preference = preferences[deviceKey];
|
|
95
|
-
|
|
96
75
|
setApplyingState('applying');
|
|
97
76
|
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
77
|
+
(async () => {
|
|
78
|
+
for (const [deviceKey, state, defaultMuted] of [
|
|
79
|
+
['microphone', microphoneState, !settings.audio.mic_default_on],
|
|
80
|
+
['camera', cameraState, !settings.video.camera_default_on],
|
|
81
|
+
['speaker', speakerState, false],
|
|
82
|
+
] as const) {
|
|
83
|
+
const preferences = parseLocalDevicePreferences(key);
|
|
84
|
+
const preference = preferences[deviceKey];
|
|
85
|
+
const manager = (
|
|
86
|
+
state as DeviceState<'camera' | 'microphone' | 'speaker'>
|
|
87
|
+
)[deviceKey];
|
|
88
|
+
|
|
89
|
+
if (!manager.state.selectedDevice) {
|
|
90
|
+
const applyPromise = preference
|
|
91
|
+
? applyLocalDevicePreference(
|
|
92
|
+
manager,
|
|
93
|
+
[preference].flat(),
|
|
94
|
+
state.devices,
|
|
95
|
+
)
|
|
96
|
+
: applyMutedState(manager, defaultMuted);
|
|
97
|
+
|
|
98
|
+
await applyPromise.catch((err) => {
|
|
99
|
+
console.warn(
|
|
100
|
+
`Failed to apply ${deviceKey} device preferences`,
|
|
101
|
+
err,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
})().finally(() =>
|
|
107
|
+
setApplyingState((state) => (state === 'applying' ? 'applied' : state)),
|
|
108
|
+
);
|
|
118
109
|
},
|
|
119
110
|
[
|
|
120
111
|
applyingState,
|
|
121
112
|
callingState,
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
cameraState,
|
|
114
|
+
cameraState.devices,
|
|
124
115
|
key,
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
microphoneState,
|
|
117
|
+
microphoneState.devices,
|
|
118
|
+
settings,
|
|
119
|
+
speakerState,
|
|
120
|
+
speakerState.devices,
|
|
127
121
|
],
|
|
128
122
|
);
|
|
129
123
|
|
|
130
124
|
useEffect(
|
|
131
125
|
function persist() {
|
|
132
|
-
if (
|
|
133
|
-
callingState === CallingState.LEFT ||
|
|
134
|
-
!state.devices?.length ||
|
|
135
|
-
applyingState !== 'applied'
|
|
136
|
-
) {
|
|
126
|
+
if (callingState === CallingState.LEFT || applyingState !== 'applied') {
|
|
137
127
|
return;
|
|
138
128
|
}
|
|
139
129
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
130
|
+
for (const [deviceKey, devices, selectedDevice, isMute] of [
|
|
131
|
+
[
|
|
132
|
+
'camera',
|
|
133
|
+
cameraState.devices,
|
|
134
|
+
cameraState.selectedDevice,
|
|
135
|
+
cameraState.isMute,
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
'microphone',
|
|
139
|
+
microphoneState.devices,
|
|
140
|
+
microphoneState.selectedDevice,
|
|
141
|
+
microphoneState.isMute,
|
|
142
|
+
],
|
|
143
|
+
[
|
|
144
|
+
'speaker',
|
|
145
|
+
speakerState.devices,
|
|
146
|
+
speakerState.selectedDevice,
|
|
147
|
+
speakerState.isMute,
|
|
148
|
+
],
|
|
149
|
+
] as const) {
|
|
150
|
+
try {
|
|
151
|
+
patchLocalDevicePreference(key, deviceKey, {
|
|
152
|
+
devices,
|
|
153
|
+
selectedDevice,
|
|
154
|
+
isMute,
|
|
155
|
+
});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.warn(`Failed to save ${deviceKey} device preferences`, err);
|
|
158
|
+
}
|
|
148
159
|
}
|
|
149
160
|
},
|
|
150
161
|
[
|
|
151
162
|
applyingState,
|
|
152
163
|
callingState,
|
|
153
|
-
|
|
164
|
+
cameraState.devices,
|
|
165
|
+
cameraState.isMute,
|
|
166
|
+
cameraState.selectedDevice,
|
|
154
167
|
key,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
168
|
+
microphoneState.devices,
|
|
169
|
+
microphoneState.isMute,
|
|
170
|
+
microphoneState.selectedDevice,
|
|
171
|
+
speakerState.devices,
|
|
172
|
+
speakerState.isMute,
|
|
173
|
+
speakerState.selectedDevice,
|
|
158
174
|
],
|
|
159
175
|
);
|
|
160
176
|
};
|
|
161
177
|
|
|
178
|
+
// const usePersistedDevicePreference = <K extends DeviceKey>(
|
|
179
|
+
// key: string,
|
|
180
|
+
// deviceKey: K,
|
|
181
|
+
// state: DeviceState<K>,
|
|
182
|
+
// defaultMuted?: boolean,
|
|
183
|
+
// ): void => {
|
|
184
|
+
// const { useCallCallingState } = useCallStateHooks();
|
|
185
|
+
// const callingState = useCallCallingState();
|
|
186
|
+
// const [applyingState, setApplyingState] = useState<
|
|
187
|
+
// 'idle' | 'applying' | 'applied'
|
|
188
|
+
// >('idle');
|
|
189
|
+
// const manager = state[deviceKey];
|
|
190
|
+
|
|
191
|
+
// useEffect(
|
|
192
|
+
// function apply() {
|
|
193
|
+
// if (
|
|
194
|
+
// callingState === CallingState.LEFT ||
|
|
195
|
+
// !state.devices?.length ||
|
|
196
|
+
// typeof defaultMuted !== 'boolean' ||
|
|
197
|
+
// applyingState !== 'idle'
|
|
198
|
+
// ) {
|
|
199
|
+
// return;
|
|
200
|
+
// }
|
|
201
|
+
|
|
202
|
+
// const preferences = parseLocalDevicePreferences(key);
|
|
203
|
+
// const preference = preferences[deviceKey];
|
|
204
|
+
|
|
205
|
+
// setApplyingState('applying');
|
|
206
|
+
|
|
207
|
+
// if (!manager.state.selectedDevice) {
|
|
208
|
+
// const applyPromise = preference
|
|
209
|
+
// ? applyLocalDevicePreference(
|
|
210
|
+
// manager,
|
|
211
|
+
// [preference].flat(),
|
|
212
|
+
// state.devices,
|
|
213
|
+
// )
|
|
214
|
+
// : applyMutedState(manager, defaultMuted);
|
|
215
|
+
|
|
216
|
+
// applyPromise
|
|
217
|
+
// .catch((err) => {
|
|
218
|
+
// console.warn(
|
|
219
|
+
// `Failed to apply ${deviceKey} device preferences`,
|
|
220
|
+
// err,
|
|
221
|
+
// );
|
|
222
|
+
// })
|
|
223
|
+
// .finally(() => setApplyingState('applied'));
|
|
224
|
+
// } else {
|
|
225
|
+
// setApplyingState('applied');
|
|
226
|
+
// }
|
|
227
|
+
// },
|
|
228
|
+
// [
|
|
229
|
+
// applyingState,
|
|
230
|
+
// callingState,
|
|
231
|
+
// defaultMuted,
|
|
232
|
+
// deviceKey,
|
|
233
|
+
// key,
|
|
234
|
+
// manager,
|
|
235
|
+
// state.devices,
|
|
236
|
+
// ],
|
|
237
|
+
// );
|
|
238
|
+
|
|
239
|
+
// useEffect(
|
|
240
|
+
// function persist() {
|
|
241
|
+
// if (
|
|
242
|
+
// callingState === CallingState.LEFT ||
|
|
243
|
+
// !state.devices?.length ||
|
|
244
|
+
// applyingState !== 'applied'
|
|
245
|
+
// ) {
|
|
246
|
+
// return;
|
|
247
|
+
// }
|
|
248
|
+
|
|
249
|
+
// try {
|
|
250
|
+
// patchLocalDevicePreference(key, deviceKey, {
|
|
251
|
+
// devices: state.devices,
|
|
252
|
+
// selectedDevice: state.selectedDevice,
|
|
253
|
+
// isMute: state.isMute,
|
|
254
|
+
// });
|
|
255
|
+
// } catch (err) {
|
|
256
|
+
// console.warn(`Failed to save ${deviceKey} device preferences`, err);
|
|
257
|
+
// }
|
|
258
|
+
// },
|
|
259
|
+
// [
|
|
260
|
+
// applyingState,
|
|
261
|
+
// callingState,
|
|
262
|
+
// deviceKey,
|
|
263
|
+
// key,
|
|
264
|
+
// state.devices,
|
|
265
|
+
// state.isMute,
|
|
266
|
+
// state.selectedDevice,
|
|
267
|
+
// ],
|
|
268
|
+
// );
|
|
269
|
+
// };
|
|
270
|
+
|
|
162
271
|
const parseLocalDevicePreferences = (key: string): LocalDevicePreferences => {
|
|
163
272
|
const preferencesStr = window.localStorage.getItem(key);
|
|
164
273
|
let preferences: LocalDevicePreferences = {};
|
package/src/translations/en.json
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"You are muted. Unmute to speak.": "You are muted. Unmute to speak.",
|
|
14
14
|
|
|
15
15
|
"Live": "Live",
|
|
16
|
+
"Livestream starts soon": "Livestream starts soon",
|
|
17
|
+
"Livestream starts at {{ startsAt }}": "Livestream starts at {{ startsAt, datetime }}",
|
|
18
|
+
"{{ count }} participants joined early_one": "{{ count }} participant joined early",
|
|
19
|
+
"{{ count }} participants joined early_other": "{{ count }} participants joined early",
|
|
16
20
|
|
|
17
21
|
"You can now speak.": "You can now speak.",
|
|
18
22
|
"Awaiting for an approval to speak.": "Awaiting for an approval to speak.",
|