@stream-io/video-react-sdk 1.36.0 → 1.37.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 +24 -0
- package/dist/{embedded-BackgroundFilters-g6ozIvlN.es.js → background-filters-D2U5UBbH.es.js} +95 -32
- package/dist/background-filters-D2U5UBbH.es.js.map +1 -0
- package/dist/{background-filters-DdQqSx4T.cjs.js → background-filters-folwWrgs.cjs.js} +95 -32
- package/dist/background-filters-folwWrgs.cjs.js.map +1 -0
- package/dist/css/embedded.css +6 -0
- package/dist/css/embedded.css.map +1 -1
- package/dist/css/styles.css +6 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/{background-filters-g6ozIvlN.es.js → embedded-BackgroundFilters-D2U5UBbH.es.js} +95 -32
- package/dist/embedded-BackgroundFilters-D2U5UBbH.es.js.map +1 -0
- package/dist/{embedded-BackgroundFilters-DdQqSx4T.cjs.js → embedded-BackgroundFilters-folwWrgs.cjs.js} +95 -32
- package/dist/embedded-BackgroundFilters-folwWrgs.cjs.js.map +1 -0
- package/dist/embedded.cjs.js +41 -25
- package/dist/embedded.cjs.js.map +1 -1
- package/dist/embedded.es.js +42 -26
- package/dist/embedded.es.js.map +1 -1
- package/dist/index.cjs.js +42 -26
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +42 -26
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/BackgroundFilters/types.d.ts +5 -4
- package/dist/src/core/hooks/useTrackElementVisibility.d.ts +2 -3
- package/dist/src/translations/index.d.ts +2 -0
- package/package.json +5 -5
- package/src/components/BackgroundFilters/BackgroundFilters.tsx +145 -53
- package/src/components/BackgroundFilters/types.ts +5 -4
- package/src/components/CallControls/ToggleAudioButton.tsx +32 -5
- package/src/components/CallControls/ToggleVideoButton.tsx +36 -5
- package/src/core/components/CallLayout/PaginatedGridLayout.tsx +1 -1
- package/src/core/components/CallLayout/SpeakerLayout.tsx +1 -1
- package/src/core/components/ParticipantView/ParticipantView.tsx +0 -1
- package/src/core/hooks/useTrackElementVisibility.ts +4 -14
- package/src/translations/en.json +2 -0
- package/dist/background-filters-DdQqSx4T.cjs.js.map +0 -1
- package/dist/background-filters-g6ozIvlN.es.js.map +0 -1
- package/dist/embedded-BackgroundFilters-DdQqSx4T.cjs.js.map +0 -1
- package/dist/embedded-BackgroundFilters-g6ozIvlN.es.js.map +0 -1
|
@@ -74,8 +74,9 @@ export type BackgroundFiltersProps = PlatformSupportFlags & {
|
|
|
74
74
|
*/
|
|
75
75
|
onError?: (error: any) => void;
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
77
|
+
* `@deprecated` Performance tuning is now handled internally by the SDK, so
|
|
78
|
+
* this prop is ignored. Remove this prop; there is no replacement
|
|
79
|
+
* configuration. Degradation is derived from the processed/source FPS ratio.
|
|
79
80
|
*/
|
|
80
81
|
performanceThresholds?: BackgroundFiltersPerformanceThresholds;
|
|
81
82
|
/**
|
|
@@ -91,8 +92,8 @@ export type PerformanceDegradationReason = 'frame-drop' | 'cpu-throttling';
|
|
|
91
92
|
/**
|
|
92
93
|
* Performance degradation information for background filters.
|
|
93
94
|
*
|
|
94
|
-
* Performance is calculated
|
|
95
|
-
*
|
|
95
|
+
* Performance is calculated from a smoothed processed/source FPS ratio to
|
|
96
|
+
* provide stable performance warnings.
|
|
96
97
|
*/
|
|
97
98
|
export type BackgroundFiltersPerformance = {
|
|
98
99
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const useTrackElementVisibility: <T extends HTMLElement>({ trackedElement,
|
|
1
|
+
import { VideoTrackType } from '@stream-io/video-client';
|
|
2
|
+
export declare const useTrackElementVisibility: <T extends HTMLElement>({ trackedElement, sessionId, trackType, }: {
|
|
3
3
|
trackedElement: T | null;
|
|
4
4
|
sessionId: string;
|
|
5
|
-
dynascaleManager?: DynascaleManager;
|
|
6
5
|
trackType: VideoTrackType | "none";
|
|
7
6
|
}) => void;
|
|
@@ -56,8 +56,10 @@ export declare const translations: {
|
|
|
56
56
|
Dismiss: string;
|
|
57
57
|
"Microphone on": string;
|
|
58
58
|
"Microphone off": string;
|
|
59
|
+
"Microphone is paused by your system": string;
|
|
59
60
|
"Camera on": string;
|
|
60
61
|
"Camera off": string;
|
|
62
|
+
"Camera is paused by your system": string;
|
|
61
63
|
"No camera found": string;
|
|
62
64
|
"Video is disabled": string;
|
|
63
65
|
Pinned: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"main": "./dist/index.cjs.js",
|
|
5
5
|
"module": "./dist/index.es.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@floating-ui/react": "^0.27.6",
|
|
48
|
-
"@stream-io/video-client": "1.
|
|
48
|
+
"@stream-io/video-client": "1.50.0",
|
|
49
49
|
"@stream-io/video-filters-web": "0.7.4",
|
|
50
|
-
"@stream-io/video-react-bindings": "1.
|
|
50
|
+
"@stream-io/video-react-bindings": "1.16.0",
|
|
51
51
|
"chart.js": "^4.4.4",
|
|
52
52
|
"clsx": "^2.0.0",
|
|
53
53
|
"react-chartjs-2": "^5.3.0"
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@rollup/plugin-json": "^6.1.0",
|
|
61
61
|
"@rollup/plugin-replace": "^6.0.2",
|
|
62
62
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
63
|
-
"@stream-io/audio-filters-web": "^0.
|
|
64
|
-
"@stream-io/video-styling": "^1.
|
|
63
|
+
"@stream-io/audio-filters-web": "^0.8.0",
|
|
64
|
+
"@stream-io/video-styling": "^1.14.0",
|
|
65
65
|
"@types/react": "~19.1.17",
|
|
66
66
|
"@types/react-dom": "~19.1.11",
|
|
67
67
|
"react": "19.1.0",
|
|
@@ -30,15 +30,26 @@ import type {
|
|
|
30
30
|
PerformanceDegradationReason,
|
|
31
31
|
} from './types';
|
|
32
32
|
|
|
33
|
+
type MediaStreamTrackVideoStats = {
|
|
34
|
+
deliveredFrames: number;
|
|
35
|
+
discardedFrames: number;
|
|
36
|
+
totalFrames: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type MediaStreamTrackWithStats = MediaStreamTrack & {
|
|
40
|
+
stats?: MediaStreamTrackVideoStats;
|
|
41
|
+
};
|
|
42
|
+
|
|
33
43
|
/**
|
|
34
44
|
* Constants for FPS warning calculation.
|
|
35
|
-
* Smooths
|
|
36
|
-
*
|
|
45
|
+
* Smooths the processed-to-source FPS ratio and uses hysteresis
|
|
46
|
+
* so the degradation warning doesn't flicker near the limit.
|
|
37
47
|
*/
|
|
38
48
|
const ALPHA = 0.2;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
49
|
+
const FPS_RATIO_WARNING_THRESHOLD_LOWER = 0.75;
|
|
50
|
+
const FPS_RATIO_WARNING_THRESHOLD_UPPER = 0.85;
|
|
51
|
+
|
|
52
|
+
const EMPTY_BACKGROUND_IMAGES: string[] = [];
|
|
42
53
|
|
|
43
54
|
/**
|
|
44
55
|
* Represents the available background filter processing engines.
|
|
@@ -76,6 +87,76 @@ const determineEngine = async (
|
|
|
76
87
|
return isMediaPipeSupported ? FilterEngine.MEDIA_PIPE : FilterEngine.NONE;
|
|
77
88
|
};
|
|
78
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Samples the raw camera track's frame stats (W3C MediaCapture Extensions) at a
|
|
92
|
+
* fixed interval and returns a live source fps estimate. Returns `undefined`
|
|
93
|
+
* while warming up, when the camera is off, or when the browser doesn't expose
|
|
94
|
+
* `MediaStreamTrack.stats` (currently Chromium-only).
|
|
95
|
+
*/
|
|
96
|
+
const useTrackFramesPerSecond = (call: Call | undefined) => {
|
|
97
|
+
const { useCameraState } = useCallStateHooks();
|
|
98
|
+
const { rootMediaStream } = useCameraState();
|
|
99
|
+
const [fps, setFps] = useState<number | undefined>(undefined);
|
|
100
|
+
const trackId = rootMediaStream?.getVideoTracks()[0]?.id;
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!call) {
|
|
104
|
+
setFps(undefined);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let previousSnapshot:
|
|
109
|
+
| (MediaStreamTrackVideoStats & { capturedAt: number })
|
|
110
|
+
| undefined;
|
|
111
|
+
let previousTrackId: string | undefined;
|
|
112
|
+
|
|
113
|
+
const intervalId = setInterval(() => {
|
|
114
|
+
const track = rootMediaStream?.getVideoTracks()[0] as
|
|
115
|
+
| MediaStreamTrackWithStats
|
|
116
|
+
| undefined;
|
|
117
|
+
|
|
118
|
+
const stats = track?.stats;
|
|
119
|
+
if (!track || !stats) {
|
|
120
|
+
previousSnapshot = undefined;
|
|
121
|
+
setFps(undefined);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (track.id !== previousTrackId) {
|
|
126
|
+
previousTrackId = track.id;
|
|
127
|
+
previousSnapshot = undefined;
|
|
128
|
+
setFps(undefined);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const currentSnapshot = {
|
|
132
|
+
deliveredFrames: stats.deliveredFrames,
|
|
133
|
+
discardedFrames: stats.discardedFrames,
|
|
134
|
+
totalFrames: stats.totalFrames,
|
|
135
|
+
capturedAt: performance.now(),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (previousSnapshot) {
|
|
139
|
+
const elapsedSec =
|
|
140
|
+
(currentSnapshot.capturedAt - previousSnapshot.capturedAt) / 1000;
|
|
141
|
+
|
|
142
|
+
const delivered =
|
|
143
|
+
currentSnapshot.deliveredFrames - previousSnapshot.deliveredFrames;
|
|
144
|
+
|
|
145
|
+
const nextFps = Math.round(delivered / elapsedSec);
|
|
146
|
+
setFps(nextFps);
|
|
147
|
+
}
|
|
148
|
+
previousSnapshot = currentSnapshot;
|
|
149
|
+
}, 1000);
|
|
150
|
+
|
|
151
|
+
return () => {
|
|
152
|
+
clearInterval(intervalId);
|
|
153
|
+
setFps(undefined);
|
|
154
|
+
};
|
|
155
|
+
}, [call, rootMediaStream, trackId]);
|
|
156
|
+
|
|
157
|
+
return fps;
|
|
158
|
+
};
|
|
159
|
+
|
|
79
160
|
/**
|
|
80
161
|
* A provider component that enables the use of background filters in your app.
|
|
81
162
|
*
|
|
@@ -93,7 +174,7 @@ export const BackgroundFiltersProvider = (
|
|
|
93
174
|
const {
|
|
94
175
|
ContextProvider,
|
|
95
176
|
children,
|
|
96
|
-
backgroundImages =
|
|
177
|
+
backgroundImages = EMPTY_BACKGROUND_IMAGES,
|
|
97
178
|
backgroundFilter: bgFilterFromProps = undefined,
|
|
98
179
|
backgroundImage: bgImageFromProps = undefined,
|
|
99
180
|
backgroundBlurLevel: bgBlurLevelFromProps = undefined,
|
|
@@ -102,7 +183,6 @@ export const BackgroundFiltersProvider = (
|
|
|
102
183
|
useLegacyFilter,
|
|
103
184
|
basePath,
|
|
104
185
|
onError,
|
|
105
|
-
performanceThresholds,
|
|
106
186
|
forceSafariSupport,
|
|
107
187
|
forceMobileSupport,
|
|
108
188
|
segmentationOptions,
|
|
@@ -120,46 +200,37 @@ export const BackgroundFiltersProvider = (
|
|
|
120
200
|
const [showLowFpsWarning, setShowLowFpsWarning] = useState<boolean>(false);
|
|
121
201
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
122
202
|
|
|
203
|
+
const rootFps = useTrackFramesPerSecond(call);
|
|
123
204
|
const cameraFrameRate = callStatsReport?.publisherStats?.camera?.frameRate;
|
|
124
205
|
|
|
125
|
-
const sourceFps =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const fpsWarningThresholdLower =
|
|
129
|
-
performanceThresholds?.fpsWarningThresholdLower ?? sourceFps * 0.75;
|
|
130
|
-
const fpsWarningThresholdUpper =
|
|
131
|
-
performanceThresholds?.fpsWarningThresholdUpper ?? sourceFps * 0.85;
|
|
132
|
-
|
|
133
|
-
const emaRef = useRef<number>(sourceFps);
|
|
134
|
-
const outlierStreakRef = useRef<number>(0);
|
|
206
|
+
const sourceFps = rootFps ?? cameraFrameRate;
|
|
207
|
+
const fpsRatioEmaRef = useRef<number | undefined>(undefined);
|
|
135
208
|
|
|
136
209
|
const handleStats = useCallback(
|
|
137
210
|
(stats: PerformanceStats) => {
|
|
138
211
|
const fps = stats?.fps;
|
|
139
|
-
if (fps
|
|
140
|
-
|
|
141
|
-
outlierStreakRef.current = 0;
|
|
212
|
+
if (fps == null || !sourceFps) {
|
|
213
|
+
fpsRatioEmaRef.current = undefined;
|
|
142
214
|
setShowLowFpsWarning(false);
|
|
143
215
|
return;
|
|
144
216
|
}
|
|
145
217
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
const isOutlier = fps < prevEma && deviation > DEVIATION_LIMIT;
|
|
150
|
-
outlierStreakRef.current = isOutlier ? outlierStreakRef.current + 1 : 0;
|
|
151
|
-
if (isOutlier && outlierStreakRef.current < OUTLIER_PERSISTENCE) return;
|
|
218
|
+
const ratio = fps / sourceFps;
|
|
219
|
+
const prevRatioEma = fpsRatioEmaRef.current ?? ratio;
|
|
152
220
|
|
|
153
|
-
|
|
221
|
+
const nextFpsRatioEma = ALPHA * ratio + (1 - ALPHA) * prevRatioEma;
|
|
222
|
+
fpsRatioEmaRef.current = nextFpsRatioEma;
|
|
154
223
|
|
|
155
224
|
setShowLowFpsWarning((prev) => {
|
|
156
|
-
if (prev &&
|
|
157
|
-
|
|
225
|
+
if (prev && nextFpsRatioEma > FPS_RATIO_WARNING_THRESHOLD_UPPER)
|
|
226
|
+
return false;
|
|
227
|
+
if (!prev && nextFpsRatioEma < FPS_RATIO_WARNING_THRESHOLD_LOWER)
|
|
228
|
+
return true;
|
|
158
229
|
|
|
159
230
|
return prev;
|
|
160
231
|
});
|
|
161
232
|
},
|
|
162
|
-
[
|
|
233
|
+
[sourceFps],
|
|
163
234
|
);
|
|
164
235
|
|
|
165
236
|
const performance: BackgroundFiltersPerformance = useMemo(() => {
|
|
@@ -207,16 +278,17 @@ export const BackgroundFiltersProvider = (
|
|
|
207
278
|
call?.tracer.trace('backgroundFilters.performance', {
|
|
208
279
|
degraded: currentDegraded,
|
|
209
280
|
reason: performance?.reason,
|
|
210
|
-
|
|
281
|
+
ratio: fpsRatioEmaRef.current,
|
|
282
|
+
sourceFps,
|
|
211
283
|
});
|
|
212
284
|
}
|
|
213
285
|
prevDegradedRef.current = currentDegraded;
|
|
214
286
|
}, [
|
|
215
|
-
performanceThresholds,
|
|
216
287
|
performance.degraded,
|
|
217
288
|
performance.reason,
|
|
218
289
|
backgroundFilter,
|
|
219
290
|
call?.tracer,
|
|
291
|
+
sourceFps,
|
|
220
292
|
]);
|
|
221
293
|
|
|
222
294
|
const applyBackgroundImageFilter = useCallback((imageUrl: string) => {
|
|
@@ -237,10 +309,9 @@ export const BackgroundFiltersProvider = (
|
|
|
237
309
|
setBackgroundImage(undefined);
|
|
238
310
|
setBackgroundBlurLevel(undefined);
|
|
239
311
|
|
|
240
|
-
|
|
241
|
-
outlierStreakRef.current = 0;
|
|
312
|
+
fpsRatioEmaRef.current = undefined;
|
|
242
313
|
setShowLowFpsWarning(false);
|
|
243
|
-
}, [
|
|
314
|
+
}, []);
|
|
244
315
|
|
|
245
316
|
const [engine, setEngine] = useState<FilterEngine>(FilterEngine.NONE);
|
|
246
317
|
const [isSupported, setIsSupported] = useState(false);
|
|
@@ -288,24 +359,45 @@ export const BackgroundFiltersProvider = (
|
|
|
288
359
|
);
|
|
289
360
|
|
|
290
361
|
const isReady = useLegacyFilter ? !!tfLite : !!mediaPipe;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
362
|
+
|
|
363
|
+
const contextValue = useMemo<BackgroundFiltersContextValue>(
|
|
364
|
+
() => ({
|
|
365
|
+
isSupported,
|
|
366
|
+
performance,
|
|
367
|
+
isReady,
|
|
368
|
+
isLoading,
|
|
369
|
+
backgroundImage,
|
|
370
|
+
backgroundBlurLevel,
|
|
371
|
+
backgroundFilter,
|
|
372
|
+
disableBackgroundFilter,
|
|
373
|
+
applyBackgroundBlurFilter,
|
|
374
|
+
applyBackgroundImageFilter,
|
|
375
|
+
backgroundImages,
|
|
376
|
+
tfFilePath,
|
|
377
|
+
modelFilePath,
|
|
378
|
+
basePath,
|
|
379
|
+
onError: handleError,
|
|
380
|
+
segmentationOptions,
|
|
381
|
+
}),
|
|
382
|
+
[
|
|
383
|
+
isSupported,
|
|
384
|
+
performance,
|
|
385
|
+
isReady,
|
|
386
|
+
isLoading,
|
|
387
|
+
backgroundImage,
|
|
388
|
+
backgroundBlurLevel,
|
|
389
|
+
backgroundFilter,
|
|
390
|
+
disableBackgroundFilter,
|
|
391
|
+
applyBackgroundBlurFilter,
|
|
392
|
+
applyBackgroundImageFilter,
|
|
393
|
+
backgroundImages,
|
|
394
|
+
tfFilePath,
|
|
395
|
+
modelFilePath,
|
|
396
|
+
basePath,
|
|
397
|
+
handleError,
|
|
398
|
+
segmentationOptions,
|
|
399
|
+
],
|
|
400
|
+
);
|
|
309
401
|
return (
|
|
310
402
|
<ContextProvider.Provider value={contextValue}>
|
|
311
403
|
{children}
|
|
@@ -92,8 +92,9 @@ export type BackgroundFiltersProps = PlatformSupportFlags & {
|
|
|
92
92
|
onError?: (error: any) => void;
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
95
|
+
* `@deprecated` Performance tuning is now handled internally by the SDK, so
|
|
96
|
+
* this prop is ignored. Remove this prop; there is no replacement
|
|
97
|
+
* configuration. Degradation is derived from the processed/source FPS ratio.
|
|
97
98
|
*/
|
|
98
99
|
performanceThresholds?: BackgroundFiltersPerformanceThresholds;
|
|
99
100
|
|
|
@@ -112,8 +113,8 @@ export type PerformanceDegradationReason = 'frame-drop' | 'cpu-throttling';
|
|
|
112
113
|
/**
|
|
113
114
|
* Performance degradation information for background filters.
|
|
114
115
|
*
|
|
115
|
-
* Performance is calculated
|
|
116
|
-
*
|
|
116
|
+
* Performance is calculated from a smoothed processed/source FPS ratio to
|
|
117
|
+
* provide stable performance warnings.
|
|
117
118
|
*/
|
|
118
119
|
export type BackgroundFiltersPerformance = {
|
|
119
120
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OwnCapability } from '@stream-io/video-client';
|
|
1
|
+
import { OwnCapability, SfuModels } from '@stream-io/video-client';
|
|
2
2
|
import {
|
|
3
3
|
Restricted,
|
|
4
4
|
useCallStateHooks,
|
|
@@ -38,7 +38,7 @@ export const ToggleAudioPreviewButton = (
|
|
|
38
38
|
...restCompositeButtonProps
|
|
39
39
|
} = props;
|
|
40
40
|
const { t } = useI18n();
|
|
41
|
-
const { useMicrophoneState } = useCallStateHooks();
|
|
41
|
+
const { useMicrophoneState, useLocalParticipant } = useCallStateHooks();
|
|
42
42
|
const {
|
|
43
43
|
microphone,
|
|
44
44
|
hasBrowserPermission,
|
|
@@ -46,6 +46,10 @@ export const ToggleAudioPreviewButton = (
|
|
|
46
46
|
optionsAwareIsMute,
|
|
47
47
|
isTogglePending,
|
|
48
48
|
} = useMicrophoneState({ optimisticUpdates });
|
|
49
|
+
const localParticipant = useLocalParticipant();
|
|
50
|
+
const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
|
|
51
|
+
SfuModels.TrackType.AUDIO,
|
|
52
|
+
);
|
|
49
53
|
const [tooltipDisabled, setTooltipDisabled] = useState(false);
|
|
50
54
|
const handleClick = createCallControlHandler(props, () =>
|
|
51
55
|
microphone.toggle(),
|
|
@@ -56,7 +60,9 @@ export const ToggleAudioPreviewButton = (
|
|
|
56
60
|
title={
|
|
57
61
|
!hasBrowserPermission
|
|
58
62
|
? t('Check your browser audio permissions')
|
|
59
|
-
:
|
|
63
|
+
: isSystemMuted
|
|
64
|
+
? t('Microphone is paused by your system')
|
|
65
|
+
: (caption ?? t('Mic'))
|
|
60
66
|
}
|
|
61
67
|
tooltipDisabled={tooltipDisabled}
|
|
62
68
|
>
|
|
@@ -99,6 +105,13 @@ export const ToggleAudioPreviewButton = (
|
|
|
99
105
|
children="?"
|
|
100
106
|
/>
|
|
101
107
|
)}
|
|
108
|
+
{isSystemMuted && hasBrowserPermission && (
|
|
109
|
+
<span
|
|
110
|
+
className="str-video__system-muted"
|
|
111
|
+
title={t('Microphone is paused by your system')}
|
|
112
|
+
children="!"
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
102
115
|
</CompositeButton>
|
|
103
116
|
</WithTooltip>
|
|
104
117
|
);
|
|
@@ -128,7 +141,7 @@ export const ToggleAudioPublishingButton = (
|
|
|
128
141
|
const { hasPermission, requestPermission, isAwaitingPermission } =
|
|
129
142
|
useRequestPermission(OwnCapability.SEND_AUDIO);
|
|
130
143
|
|
|
131
|
-
const { useMicrophoneState } = useCallStateHooks();
|
|
144
|
+
const { useMicrophoneState, useLocalParticipant } = useCallStateHooks();
|
|
132
145
|
const {
|
|
133
146
|
microphone,
|
|
134
147
|
hasBrowserPermission,
|
|
@@ -136,6 +149,10 @@ export const ToggleAudioPublishingButton = (
|
|
|
136
149
|
isTogglePending,
|
|
137
150
|
optionsAwareIsMute,
|
|
138
151
|
} = useMicrophoneState({ optimisticUpdates });
|
|
152
|
+
const localParticipant = useLocalParticipant();
|
|
153
|
+
const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
|
|
154
|
+
SfuModels.TrackType.AUDIO,
|
|
155
|
+
);
|
|
139
156
|
|
|
140
157
|
const [tooltipDisabled, setTooltipDisabled] = useState(false);
|
|
141
158
|
const handleClick = createCallControlHandler(props, async () => {
|
|
@@ -161,7 +178,9 @@ export const ToggleAudioPublishingButton = (
|
|
|
161
178
|
? t('You have no permission to share your audio')
|
|
162
179
|
: !hasBrowserPermission
|
|
163
180
|
? t('Check your browser mic permissions')
|
|
164
|
-
:
|
|
181
|
+
: isSystemMuted
|
|
182
|
+
? t('Microphone is paused by your system')
|
|
183
|
+
: (caption ?? t('Mic'))
|
|
165
184
|
}
|
|
166
185
|
tooltipDisabled={tooltipDisabled}
|
|
167
186
|
>
|
|
@@ -200,6 +219,14 @@ export const ToggleAudioPublishingButton = (
|
|
|
200
219
|
?
|
|
201
220
|
</span>
|
|
202
221
|
)}
|
|
222
|
+
{isSystemMuted && hasBrowserPermission && hasPermission && (
|
|
223
|
+
<span
|
|
224
|
+
className="str-video__system-muted"
|
|
225
|
+
title={t('Microphone is paused by your system')}
|
|
226
|
+
>
|
|
227
|
+
!
|
|
228
|
+
</span>
|
|
229
|
+
)}
|
|
203
230
|
</CompositeButton>
|
|
204
231
|
</WithTooltip>
|
|
205
232
|
</PermissionNotification>
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
UseInputMediaDeviceOptions,
|
|
6
6
|
} from '@stream-io/video-react-bindings';
|
|
7
7
|
import clsx from 'clsx';
|
|
8
|
-
import { OwnCapability } from '@stream-io/video-client';
|
|
8
|
+
import { OwnCapability, SfuModels } from '@stream-io/video-client';
|
|
9
9
|
import { CompositeButton, IconButtonWithMenuProps } from '../Button/';
|
|
10
10
|
import { DeviceSelectorVideo } from '../DeviceSettings';
|
|
11
11
|
import { PermissionNotification } from '../Notification';
|
|
@@ -38,7 +38,7 @@ export const ToggleVideoPreviewButton = (
|
|
|
38
38
|
...restCompositeButtonProps
|
|
39
39
|
} = props;
|
|
40
40
|
const { t } = useI18n();
|
|
41
|
-
const { useCameraState } = useCallStateHooks();
|
|
41
|
+
const { useCameraState, useLocalParticipant } = useCallStateHooks();
|
|
42
42
|
const {
|
|
43
43
|
camera,
|
|
44
44
|
hasBrowserPermission,
|
|
@@ -46,6 +46,10 @@ export const ToggleVideoPreviewButton = (
|
|
|
46
46
|
isTogglePending,
|
|
47
47
|
optionsAwareIsMute,
|
|
48
48
|
} = useCameraState({ optimisticUpdates });
|
|
49
|
+
const localParticipant = useLocalParticipant();
|
|
50
|
+
const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
|
|
51
|
+
SfuModels.TrackType.VIDEO,
|
|
52
|
+
);
|
|
49
53
|
const [tooltipDisabled, setTooltipDisabled] = useState(false);
|
|
50
54
|
const handleClick = createCallControlHandler(props, () => camera.toggle());
|
|
51
55
|
|
|
@@ -54,7 +58,9 @@ export const ToggleVideoPreviewButton = (
|
|
|
54
58
|
title={
|
|
55
59
|
!hasBrowserPermission
|
|
56
60
|
? t('Check your browser video permissions')
|
|
57
|
-
:
|
|
61
|
+
: isSystemMuted
|
|
62
|
+
? t('Camera is paused by your system')
|
|
63
|
+
: (caption ?? t('Video'))
|
|
58
64
|
}
|
|
59
65
|
tooltipDisabled={tooltipDisabled}
|
|
60
66
|
>
|
|
@@ -97,6 +103,13 @@ export const ToggleVideoPreviewButton = (
|
|
|
97
103
|
children="?"
|
|
98
104
|
/>
|
|
99
105
|
)}
|
|
106
|
+
{isSystemMuted && hasBrowserPermission && (
|
|
107
|
+
<span
|
|
108
|
+
className="str-video__system-muted"
|
|
109
|
+
title={t('Camera is paused by your system')}
|
|
110
|
+
children="!"
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
100
113
|
</CompositeButton>
|
|
101
114
|
</WithTooltip>
|
|
102
115
|
);
|
|
@@ -126,7 +139,8 @@ export const ToggleVideoPublishingButton = (
|
|
|
126
139
|
const { hasPermission, requestPermission, isAwaitingPermission } =
|
|
127
140
|
useRequestPermission(OwnCapability.SEND_VIDEO);
|
|
128
141
|
|
|
129
|
-
const { useCameraState, useCallSettings } =
|
|
142
|
+
const { useCameraState, useCallSettings, useLocalParticipant } =
|
|
143
|
+
useCallStateHooks();
|
|
130
144
|
const {
|
|
131
145
|
camera,
|
|
132
146
|
optionsAwareIsMute,
|
|
@@ -134,6 +148,10 @@ export const ToggleVideoPublishingButton = (
|
|
|
134
148
|
isPromptingPermission,
|
|
135
149
|
isTogglePending,
|
|
136
150
|
} = useCameraState({ optimisticUpdates });
|
|
151
|
+
const localParticipant = useLocalParticipant();
|
|
152
|
+
const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
|
|
153
|
+
SfuModels.TrackType.VIDEO,
|
|
154
|
+
);
|
|
137
155
|
const callSettings = useCallSettings();
|
|
138
156
|
const isPublishingVideoAllowed = callSettings?.video.enabled;
|
|
139
157
|
const [tooltipDisabled, setTooltipDisabled] = useState(false);
|
|
@@ -164,7 +182,9 @@ export const ToggleVideoPublishingButton = (
|
|
|
164
182
|
? t('Check your browser video permissions')
|
|
165
183
|
: !isPublishingVideoAllowed
|
|
166
184
|
? t('Video publishing is disabled by the system')
|
|
167
|
-
:
|
|
185
|
+
: isSystemMuted
|
|
186
|
+
? t('Camera is paused by your system')
|
|
187
|
+
: caption || t('Video')
|
|
168
188
|
}
|
|
169
189
|
tooltipDisabled={tooltipDisabled}
|
|
170
190
|
>
|
|
@@ -205,6 +225,17 @@ export const ToggleVideoPublishingButton = (
|
|
|
205
225
|
?
|
|
206
226
|
</span>
|
|
207
227
|
)}
|
|
228
|
+
{isSystemMuted &&
|
|
229
|
+
hasBrowserPermission &&
|
|
230
|
+
hasPermission &&
|
|
231
|
+
isPublishingVideoAllowed && (
|
|
232
|
+
<span
|
|
233
|
+
className="str-video__system-muted"
|
|
234
|
+
title={t('Camera is paused by your system')}
|
|
235
|
+
>
|
|
236
|
+
!
|
|
237
|
+
</span>
|
|
238
|
+
)}
|
|
208
239
|
</CompositeButton>
|
|
209
240
|
</WithTooltip>
|
|
210
241
|
</PermissionNotification>
|
|
@@ -152,7 +152,7 @@ export const PaginatedGridLayout = (props: PaginatedGridLayoutProps) => {
|
|
|
152
152
|
|
|
153
153
|
const cleanup = call.setViewport(paginatedGridLayoutWrapperElement);
|
|
154
154
|
|
|
155
|
-
return () => cleanup();
|
|
155
|
+
return () => cleanup?.();
|
|
156
156
|
}, [paginatedGridLayoutWrapperElement, call]);
|
|
157
157
|
|
|
158
158
|
// only used to render video elements
|
|
@@ -149,7 +149,7 @@ export const SpeakerLayout = ({
|
|
|
149
149
|
|
|
150
150
|
const cleanup = call.setViewport(participantsBarWrapperElement);
|
|
151
151
|
|
|
152
|
-
return () => cleanup();
|
|
152
|
+
return () => cleanup?.();
|
|
153
153
|
}, [participantsBarWrapperElement, call]);
|
|
154
154
|
|
|
155
155
|
const isOneOnOneCall = allParticipants.length === 2;
|
|
@@ -102,7 +102,6 @@ export const ParticipantView = forwardRef<HTMLDivElement, ParticipantViewProps>(
|
|
|
102
102
|
const [contextVideoPlaceholderElement, setContextVideoPlaceholderElement] =
|
|
103
103
|
useState<HTMLDivElement | null>(null);
|
|
104
104
|
|
|
105
|
-
// TODO: allow to pass custom ViewportTracker instance from props
|
|
106
105
|
useTrackElementVisibility({
|
|
107
106
|
sessionId,
|
|
108
107
|
trackedElement,
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { VideoTrackType } from '@stream-io/video-client';
|
|
3
3
|
import { useCall } from '@stream-io/video-react-bindings';
|
|
4
4
|
|
|
5
5
|
export const useTrackElementVisibility = <T extends HTMLElement>({
|
|
6
6
|
trackedElement,
|
|
7
|
-
dynascaleManager: propsDynascaleManager,
|
|
8
7
|
sessionId,
|
|
9
8
|
trackType,
|
|
10
9
|
}: {
|
|
11
10
|
trackedElement: T | null;
|
|
12
11
|
sessionId: string;
|
|
13
|
-
dynascaleManager?: DynascaleManager;
|
|
14
12
|
trackType: VideoTrackType | 'none';
|
|
15
13
|
}) => {
|
|
16
14
|
const call = useCall();
|
|
17
|
-
const manager = propsDynascaleManager ?? call?.dynascaleManager;
|
|
18
15
|
useEffect(() => {
|
|
19
|
-
if (!trackedElement || !
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
sessionId,
|
|
23
|
-
trackType,
|
|
24
|
-
);
|
|
25
|
-
return () => {
|
|
26
|
-
unobserve();
|
|
27
|
-
};
|
|
28
|
-
}, [trackedElement, manager, call, sessionId, trackType]);
|
|
16
|
+
if (!trackedElement || !call || trackType === 'none') return;
|
|
17
|
+
return call.trackElementVisibility(trackedElement, sessionId, trackType);
|
|
18
|
+
}, [trackedElement, call, sessionId, trackType]);
|
|
29
19
|
};
|
package/src/translations/en.json
CHANGED
|
@@ -60,8 +60,10 @@
|
|
|
60
60
|
|
|
61
61
|
"Microphone on": "Microphone on",
|
|
62
62
|
"Microphone off": "Microphone off",
|
|
63
|
+
"Microphone is paused by your system": "Microphone is paused by your system",
|
|
63
64
|
"Camera on": "Camera on",
|
|
64
65
|
"Camera off": "Camera off",
|
|
66
|
+
"Camera is paused by your system": "Camera is paused by your system",
|
|
65
67
|
"No camera found": "No camera found",
|
|
66
68
|
"Video is disabled": "Video is disabled",
|
|
67
69
|
"Pinned": "Pinned",
|