@stream-io/video-react-sdk 1.28.1 → 1.29.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/background-filters-89nRJ8Uk.cjs.js +352 -0
- package/dist/background-filters-89nRJ8Uk.cjs.js.map +1 -0
- package/dist/background-filters-B5aRj_vl.es.js +350 -0
- package/dist/background-filters-B5aRj_vl.es.js.map +1 -0
- package/dist/index.cjs.js +123 -342
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +125 -344
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/BackgroundFilters/BackgroundFilters.d.ts +5 -144
- package/dist/src/components/BackgroundFilters/BackgroundFiltersProvider.d.ts +15 -0
- package/dist/src/components/BackgroundFilters/index.d.ts +2 -1
- package/dist/src/components/BackgroundFilters/types.d.ts +135 -0
- package/dist/src/core/components/CallLayout/SpeakerLayout.d.ts +6 -1
- package/dist/src/hooks/index.d.ts +1 -0
- package/dist/src/hooks/useDragToScroll.d.ts +18 -0
- package/package.json +4 -4
- package/src/components/BackgroundFilters/BackgroundFilters.tsx +50 -219
- package/src/components/BackgroundFilters/BackgroundFiltersProvider.tsx +60 -0
- package/src/components/BackgroundFilters/index.ts +4 -1
- package/src/components/BackgroundFilters/types.ts +162 -0
- package/src/core/components/CallLayout/SpeakerLayout.tsx +11 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDragToScroll.ts +162 -0
package/dist/index.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasAudio, hasScreenShareAudio, CallingState, hasVideo, hasScreenShare, OwnCapability, Browsers, VisibilityState, hasPausedTrack,
|
|
1
|
+
import { hasAudio, hasScreenShareAudio, CallingState, hasVideo, hasScreenShare, OwnCapability, Browsers, VisibilityState, hasPausedTrack, createSoundDetector, SfuModels, isPinned, name, NoiseCancellationSettingsModeEnum, paginatedLayoutSortPreset, combineComparators, screenSharing, speakerLayoutSortPreset, CallTypes, defaultSortPreset, humanize, setSdkInfo } from '@stream-io/video-client';
|
|
2
2
|
export * from '@stream-io/video-client';
|
|
3
3
|
import { useCall, useCallStateHooks, useI18n, Restricted, useToggleCallRecording, useConnectedUser, StreamCallProvider, StreamVideoProvider, useStreamVideoClient, useEffectEvent } from '@stream-io/video-react-bindings';
|
|
4
4
|
export * from '@stream-io/video-react-bindings';
|
|
@@ -6,8 +6,6 @@ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
|
6
6
|
import { useState, useEffect, Fragment as Fragment$1, createContext, useContext, useCallback, useMemo, useRef, isValidElement, forwardRef, useLayoutEffect, lazy, Suspense } from 'react';
|
|
7
7
|
import { useFloating, offset, shift, flip, size, autoUpdate, FloatingOverlay, FloatingPortal, arrow, FloatingArrow, useListItem, useListNavigation, useTypeahead, useClick, useDismiss, useRole, useInteractions, FloatingFocusManager, FloatingList, useHover } from '@floating-ui/react';
|
|
8
8
|
import clsx from 'clsx';
|
|
9
|
-
import { flushSync } from 'react-dom';
|
|
10
|
-
import { loadTFLite, loadMediaPipe, isPlatformSupported, isMediaPipePlatformSupported, VirtualBackground, createRenderer } from '@stream-io/video-filters-web';
|
|
11
9
|
|
|
12
10
|
const Audio = ({ participant, trackType = 'audioTrack', ...rest }) => {
|
|
13
11
|
const call = useCall();
|
|
@@ -596,6 +594,118 @@ const useModeration = (options) => {
|
|
|
596
594
|
useEffect(() => disableBlur, [disableBlur]);
|
|
597
595
|
};
|
|
598
596
|
|
|
597
|
+
/**
|
|
598
|
+
* Enables drag-to-scroll functionality with momentum scrolling on a scrollable element.
|
|
599
|
+
*
|
|
600
|
+
* This hook allows users to click and drag to scroll an element, with momentum scrolling
|
|
601
|
+
* that continues after the drag ends. The drag only activates after moving beyond a threshold
|
|
602
|
+
* distance, which prevents accidental drags from clicks.
|
|
603
|
+
*
|
|
604
|
+
* @param element - The HTML element to enable drag to scroll on.
|
|
605
|
+
* @param options - Options for customizing the drag-to-scroll behavior.
|
|
606
|
+
*/
|
|
607
|
+
function useDragToScroll(element, options = {}) {
|
|
608
|
+
const stateRef = useRef({
|
|
609
|
+
isDragging: false,
|
|
610
|
+
isPointerActive: false,
|
|
611
|
+
prevX: 0,
|
|
612
|
+
prevY: 0,
|
|
613
|
+
velocityX: 0,
|
|
614
|
+
velocityY: 0,
|
|
615
|
+
rafId: 0,
|
|
616
|
+
startX: 0,
|
|
617
|
+
startY: 0,
|
|
618
|
+
});
|
|
619
|
+
useEffect(() => {
|
|
620
|
+
if (!element || !options.enabled)
|
|
621
|
+
return;
|
|
622
|
+
const { decay = 0.95, minVelocity = 0.5, dragThreshold = 5 } = options;
|
|
623
|
+
const state = stateRef.current;
|
|
624
|
+
const stopMomentum = () => {
|
|
625
|
+
if (state.rafId) {
|
|
626
|
+
cancelAnimationFrame(state.rafId);
|
|
627
|
+
state.rafId = 0;
|
|
628
|
+
}
|
|
629
|
+
state.velocityX = 0;
|
|
630
|
+
state.velocityY = 0;
|
|
631
|
+
};
|
|
632
|
+
const momentumStep = () => {
|
|
633
|
+
state.velocityX *= decay;
|
|
634
|
+
state.velocityY *= decay;
|
|
635
|
+
element.scrollLeft -= state.velocityX;
|
|
636
|
+
element.scrollTop -= state.velocityY;
|
|
637
|
+
if (Math.abs(state.velocityX) < minVelocity &&
|
|
638
|
+
Math.abs(state.velocityY) < minVelocity) {
|
|
639
|
+
state.rafId = 0;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
state.rafId = requestAnimationFrame(momentumStep);
|
|
643
|
+
};
|
|
644
|
+
const onPointerDown = (e) => {
|
|
645
|
+
if (e.pointerType !== 'mouse')
|
|
646
|
+
return;
|
|
647
|
+
stopMomentum();
|
|
648
|
+
state.isDragging = false;
|
|
649
|
+
state.isPointerActive = true;
|
|
650
|
+
state.prevX = e.clientX;
|
|
651
|
+
state.prevY = e.clientY;
|
|
652
|
+
state.startX = e.clientX;
|
|
653
|
+
state.startY = e.clientY;
|
|
654
|
+
};
|
|
655
|
+
const onPointerMove = (e) => {
|
|
656
|
+
if (e.pointerType !== 'mouse')
|
|
657
|
+
return;
|
|
658
|
+
if (!state.isPointerActive)
|
|
659
|
+
return;
|
|
660
|
+
const dx = e.clientX - state.startX;
|
|
661
|
+
const dy = e.clientY - state.startY;
|
|
662
|
+
if (!state.isDragging && Math.hypot(dx, dy) > dragThreshold) {
|
|
663
|
+
state.isDragging = true;
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
}
|
|
666
|
+
if (!state.isDragging)
|
|
667
|
+
return;
|
|
668
|
+
const moveDx = e.clientX - state.prevX;
|
|
669
|
+
const moveDy = e.clientY - state.prevY;
|
|
670
|
+
element.scrollLeft -= moveDx;
|
|
671
|
+
element.scrollTop -= moveDy;
|
|
672
|
+
state.velocityX = moveDx;
|
|
673
|
+
state.velocityY = moveDy;
|
|
674
|
+
state.prevX = e.clientX;
|
|
675
|
+
state.prevY = e.clientY;
|
|
676
|
+
};
|
|
677
|
+
const onPointerUpOrCancel = () => {
|
|
678
|
+
const wasDragging = state.isDragging;
|
|
679
|
+
state.isDragging = false;
|
|
680
|
+
state.isPointerActive = false;
|
|
681
|
+
state.prevX = 0;
|
|
682
|
+
state.prevY = 0;
|
|
683
|
+
state.startX = 0;
|
|
684
|
+
state.startY = 0;
|
|
685
|
+
if (!wasDragging) {
|
|
686
|
+
stopMomentum();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (Math.hypot(state.velocityX, state.velocityY) < minVelocity) {
|
|
690
|
+
stopMomentum();
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
state.rafId = requestAnimationFrame(momentumStep);
|
|
694
|
+
};
|
|
695
|
+
element.addEventListener('pointerdown', onPointerDown);
|
|
696
|
+
element.addEventListener('pointermove', onPointerMove);
|
|
697
|
+
window.addEventListener('pointerup', onPointerUpOrCancel);
|
|
698
|
+
window.addEventListener('pointercancel', onPointerUpOrCancel);
|
|
699
|
+
return () => {
|
|
700
|
+
element.removeEventListener('pointerdown', onPointerDown);
|
|
701
|
+
element.removeEventListener('pointermove', onPointerMove);
|
|
702
|
+
window.removeEventListener('pointerup', onPointerUpOrCancel);
|
|
703
|
+
window.removeEventListener('pointercancel', onPointerUpOrCancel);
|
|
704
|
+
stopMomentum();
|
|
705
|
+
};
|
|
706
|
+
}, [element, options]);
|
|
707
|
+
}
|
|
708
|
+
|
|
599
709
|
var MenuVisualType;
|
|
600
710
|
(function (MenuVisualType) {
|
|
601
711
|
MenuVisualType["PORTAL"] = "portal";
|
|
@@ -957,34 +1067,9 @@ const AvatarFallback = ({ className, names, style, }) => {
|
|
|
957
1067
|
return (jsx("div", { className: clsx('str-video__avatar--initials-fallback', className), style: style, children: jsxs("div", { children: [names[0][0], names[1]?.[0]] }) }));
|
|
958
1068
|
};
|
|
959
1069
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
* and uses two thresholds to avoid flickering near the limit.
|
|
964
|
-
*/
|
|
965
|
-
const ALPHA = 0.2;
|
|
966
|
-
const FPS_WARNING_THRESHOLD_LOWER = 23;
|
|
967
|
-
const FPS_WARNING_THRESHOLD_UPPER = 25;
|
|
968
|
-
const DEFAULT_FPS = 30;
|
|
969
|
-
const DEVIATION_LIMIT = 0.5;
|
|
970
|
-
const OUTLIER_PERSISTENCE = 5;
|
|
971
|
-
/**
|
|
972
|
-
* Represents the available background filter processing engines.
|
|
973
|
-
*/
|
|
974
|
-
var FilterEngine;
|
|
975
|
-
(function (FilterEngine) {
|
|
976
|
-
FilterEngine[FilterEngine["TF"] = 0] = "TF";
|
|
977
|
-
FilterEngine[FilterEngine["MEDIA_PIPE"] = 1] = "MEDIA_PIPE";
|
|
978
|
-
FilterEngine[FilterEngine["NONE"] = 2] = "NONE";
|
|
979
|
-
})(FilterEngine || (FilterEngine = {}));
|
|
980
|
-
/**
|
|
981
|
-
* Represents the possible reasons for background filter performance degradation.
|
|
982
|
-
*/
|
|
983
|
-
var PerformanceDegradationReason;
|
|
984
|
-
(function (PerformanceDegradationReason) {
|
|
985
|
-
PerformanceDegradationReason["FRAME_DROP"] = "frame-drop";
|
|
986
|
-
PerformanceDegradationReason["CPU_THROTTLING"] = "cpu-throttling";
|
|
987
|
-
})(PerformanceDegradationReason || (PerformanceDegradationReason = {}));
|
|
1070
|
+
const BackgroundFiltersProviderImpl = lazy(() => import('./background-filters-B5aRj_vl.es.js').then((m) => ({
|
|
1071
|
+
default: m.BackgroundFiltersProvider,
|
|
1072
|
+
})));
|
|
988
1073
|
/**
|
|
989
1074
|
* The context for the background filters.
|
|
990
1075
|
*/
|
|
@@ -999,26 +1084,6 @@ const useBackgroundFilters = () => {
|
|
|
999
1084
|
}
|
|
1000
1085
|
return context;
|
|
1001
1086
|
};
|
|
1002
|
-
/**
|
|
1003
|
-
* Determines which filter engine is available.
|
|
1004
|
-
* MEDIA_PIPE is the default unless legacy filters are requested or MediaPipe is unsupported.
|
|
1005
|
-
*
|
|
1006
|
-
* Returns NONE if neither is supported.
|
|
1007
|
-
*/
|
|
1008
|
-
const determineEngine = async (useLegacyFilter, forceSafariSupport, forceMobileSupport) => {
|
|
1009
|
-
const isTfPlatformSupported = await isPlatformSupported({
|
|
1010
|
-
forceSafariSupport,
|
|
1011
|
-
forceMobileSupport,
|
|
1012
|
-
});
|
|
1013
|
-
if (useLegacyFilter) {
|
|
1014
|
-
return isTfPlatformSupported ? FilterEngine.TF : FilterEngine.NONE;
|
|
1015
|
-
}
|
|
1016
|
-
const isMediaPipeSupported = await isMediaPipePlatformSupported({
|
|
1017
|
-
forceSafariSupport,
|
|
1018
|
-
forceMobileSupport,
|
|
1019
|
-
});
|
|
1020
|
-
return isMediaPipeSupported ? FilterEngine.MEDIA_PIPE : FilterEngine.NONE;
|
|
1021
|
-
};
|
|
1022
1087
|
/**
|
|
1023
1088
|
* A provider component that enables the use of background filters in your app.
|
|
1024
1089
|
*
|
|
@@ -1026,295 +1091,8 @@ const determineEngine = async (useLegacyFilter, forceSafariSupport, forceMobileS
|
|
|
1026
1091
|
* in your project before using this component.
|
|
1027
1092
|
*/
|
|
1028
1093
|
const BackgroundFiltersProvider = (props) => {
|
|
1029
|
-
const {
|
|
1030
|
-
|
|
1031
|
-
const { useCallStatsReport } = useCallStateHooks();
|
|
1032
|
-
const callStatsReport = useCallStatsReport();
|
|
1033
|
-
const [backgroundFilter, setBackgroundFilter] = useState(bgFilterFromProps);
|
|
1034
|
-
const [backgroundImage, setBackgroundImage] = useState(bgImageFromProps);
|
|
1035
|
-
const [backgroundBlurLevel, setBackgroundBlurLevel] = useState(bgBlurLevelFromProps);
|
|
1036
|
-
const [showLowFpsWarning, setShowLowFpsWarning] = useState(false);
|
|
1037
|
-
const fpsWarningThresholdLower = performanceThresholds?.fpsWarningThresholdLower ??
|
|
1038
|
-
FPS_WARNING_THRESHOLD_LOWER;
|
|
1039
|
-
const fpsWarningThresholdUpper = performanceThresholds?.fpsWarningThresholdUpper ??
|
|
1040
|
-
FPS_WARNING_THRESHOLD_UPPER;
|
|
1041
|
-
const defaultFps = performanceThresholds?.defaultFps ?? DEFAULT_FPS;
|
|
1042
|
-
const emaRef = useRef(defaultFps);
|
|
1043
|
-
const outlierStreakRef = useRef(0);
|
|
1044
|
-
const handleStats = useCallback((stats) => {
|
|
1045
|
-
const fps = stats?.fps;
|
|
1046
|
-
if (fps === undefined || fps === null) {
|
|
1047
|
-
emaRef.current = defaultFps;
|
|
1048
|
-
outlierStreakRef.current = 0;
|
|
1049
|
-
setShowLowFpsWarning(false);
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
const prevEma = emaRef.current;
|
|
1053
|
-
const deviation = Math.abs(fps - prevEma) / prevEma;
|
|
1054
|
-
const isOutlier = fps < prevEma && deviation > DEVIATION_LIMIT;
|
|
1055
|
-
outlierStreakRef.current = isOutlier ? outlierStreakRef.current + 1 : 0;
|
|
1056
|
-
if (isOutlier && outlierStreakRef.current < OUTLIER_PERSISTENCE)
|
|
1057
|
-
return;
|
|
1058
|
-
emaRef.current = ALPHA * fps + (1 - ALPHA) * prevEma;
|
|
1059
|
-
setShowLowFpsWarning((prev) => {
|
|
1060
|
-
if (prev && emaRef.current > fpsWarningThresholdUpper)
|
|
1061
|
-
return false;
|
|
1062
|
-
if (!prev && emaRef.current < fpsWarningThresholdLower)
|
|
1063
|
-
return true;
|
|
1064
|
-
return prev;
|
|
1065
|
-
});
|
|
1066
|
-
}, [fpsWarningThresholdLower, fpsWarningThresholdUpper, defaultFps]);
|
|
1067
|
-
const performance = useMemo(() => {
|
|
1068
|
-
if (!backgroundFilter) {
|
|
1069
|
-
return { degraded: false };
|
|
1070
|
-
}
|
|
1071
|
-
const reasons = [];
|
|
1072
|
-
if (showLowFpsWarning) {
|
|
1073
|
-
reasons.push(PerformanceDegradationReason.FRAME_DROP);
|
|
1074
|
-
}
|
|
1075
|
-
const qualityLimitationReasons = callStatsReport?.publisherStats?.qualityLimitationReasons;
|
|
1076
|
-
if (showLowFpsWarning &&
|
|
1077
|
-
qualityLimitationReasons &&
|
|
1078
|
-
qualityLimitationReasons?.includes('cpu')) {
|
|
1079
|
-
reasons.push(PerformanceDegradationReason.CPU_THROTTLING);
|
|
1080
|
-
}
|
|
1081
|
-
return {
|
|
1082
|
-
degraded: reasons.length > 0,
|
|
1083
|
-
reason: reasons.length > 0 ? reasons : undefined,
|
|
1084
|
-
};
|
|
1085
|
-
}, [
|
|
1086
|
-
showLowFpsWarning,
|
|
1087
|
-
callStatsReport?.publisherStats?.qualityLimitationReasons,
|
|
1088
|
-
backgroundFilter,
|
|
1089
|
-
]);
|
|
1090
|
-
const prevDegradedRef = useRef(undefined);
|
|
1091
|
-
useEffect(() => {
|
|
1092
|
-
const currentDegraded = performance.degraded;
|
|
1093
|
-
const prevDegraded = prevDegradedRef.current;
|
|
1094
|
-
if (!!backgroundFilter &&
|
|
1095
|
-
prevDegraded !== undefined &&
|
|
1096
|
-
prevDegraded !== currentDegraded) {
|
|
1097
|
-
call?.tracer.trace('backgroundFilters.performance', {
|
|
1098
|
-
degraded: currentDegraded,
|
|
1099
|
-
reason: performance?.reason,
|
|
1100
|
-
fps: emaRef.current,
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
prevDegradedRef.current = currentDegraded;
|
|
1104
|
-
}, [
|
|
1105
|
-
performanceThresholds,
|
|
1106
|
-
performance.degraded,
|
|
1107
|
-
performance.reason,
|
|
1108
|
-
backgroundFilter,
|
|
1109
|
-
call?.tracer,
|
|
1110
|
-
]);
|
|
1111
|
-
const applyBackgroundImageFilter = useCallback((imageUrl) => {
|
|
1112
|
-
setBackgroundFilter('image');
|
|
1113
|
-
setBackgroundImage(imageUrl);
|
|
1114
|
-
}, []);
|
|
1115
|
-
const applyBackgroundBlurFilter = useCallback((blurLevel = 'high') => {
|
|
1116
|
-
setBackgroundFilter('blur');
|
|
1117
|
-
setBackgroundBlurLevel(blurLevel);
|
|
1118
|
-
}, []);
|
|
1119
|
-
const disableBackgroundFilter = useCallback(() => {
|
|
1120
|
-
setBackgroundFilter(undefined);
|
|
1121
|
-
setBackgroundImage(undefined);
|
|
1122
|
-
setBackgroundBlurLevel(undefined);
|
|
1123
|
-
emaRef.current = defaultFps;
|
|
1124
|
-
outlierStreakRef.current = 0;
|
|
1125
|
-
setShowLowFpsWarning(false);
|
|
1126
|
-
}, [defaultFps]);
|
|
1127
|
-
const [engine, setEngine] = useState(FilterEngine.NONE);
|
|
1128
|
-
const [isSupported, setIsSupported] = useState(false);
|
|
1129
|
-
useEffect(() => {
|
|
1130
|
-
determineEngine(useLegacyFilter, forceSafariSupport, forceMobileSupport).then((determinedEngine) => {
|
|
1131
|
-
setEngine(determinedEngine);
|
|
1132
|
-
setIsSupported(determinedEngine !== FilterEngine.NONE);
|
|
1133
|
-
});
|
|
1134
|
-
}, [forceMobileSupport, forceSafariSupport, useLegacyFilter]);
|
|
1135
|
-
const [tfLite, setTfLite] = useState();
|
|
1136
|
-
useEffect(() => {
|
|
1137
|
-
if (engine !== FilterEngine.TF)
|
|
1138
|
-
return;
|
|
1139
|
-
loadTFLite({ basePath, modelFilePath, tfFilePath })
|
|
1140
|
-
.then(setTfLite)
|
|
1141
|
-
.catch((err) => console.error('Failed to load TFLite', err));
|
|
1142
|
-
}, [basePath, engine, modelFilePath, tfFilePath]);
|
|
1143
|
-
const [mediaPipe, setMediaPipe] = useState();
|
|
1144
|
-
useEffect(() => {
|
|
1145
|
-
if (engine !== FilterEngine.MEDIA_PIPE)
|
|
1146
|
-
return;
|
|
1147
|
-
loadMediaPipe({
|
|
1148
|
-
basePath: basePath,
|
|
1149
|
-
modelPath: modelFilePath,
|
|
1150
|
-
})
|
|
1151
|
-
.then(setMediaPipe)
|
|
1152
|
-
.catch((err) => console.error('Failed to preload MediaPipe', err));
|
|
1153
|
-
}, [engine, modelFilePath, basePath]);
|
|
1154
|
-
const handleError = useCallback((error) => {
|
|
1155
|
-
console.warn('[filters] Filter encountered an error and will be disabled');
|
|
1156
|
-
disableBackgroundFilter();
|
|
1157
|
-
onError?.(error);
|
|
1158
|
-
}, [disableBackgroundFilter, onError]);
|
|
1159
|
-
const isReady = useLegacyFilter ? !!tfLite : !!mediaPipe;
|
|
1160
|
-
return (jsxs(BackgroundFiltersContext.Provider, { value: {
|
|
1161
|
-
isSupported,
|
|
1162
|
-
performance,
|
|
1163
|
-
isReady,
|
|
1164
|
-
backgroundImage,
|
|
1165
|
-
backgroundBlurLevel,
|
|
1166
|
-
backgroundFilter,
|
|
1167
|
-
disableBackgroundFilter,
|
|
1168
|
-
applyBackgroundBlurFilter,
|
|
1169
|
-
applyBackgroundImageFilter,
|
|
1170
|
-
backgroundImages,
|
|
1171
|
-
tfFilePath,
|
|
1172
|
-
modelFilePath,
|
|
1173
|
-
basePath,
|
|
1174
|
-
onError: handleError,
|
|
1175
|
-
}, children: [children, isReady && (jsx(BackgroundFilters, { tfLite: tfLite, engine: engine, onStats: handleStats }))] }));
|
|
1176
|
-
};
|
|
1177
|
-
const BackgroundFilters = (props) => {
|
|
1178
|
-
const call = useCall();
|
|
1179
|
-
const { children, start } = useRenderer(props.tfLite, call, props.engine);
|
|
1180
|
-
const { onError, backgroundFilter } = useBackgroundFilters();
|
|
1181
|
-
const handleErrorRef = useRef(undefined);
|
|
1182
|
-
handleErrorRef.current = onError;
|
|
1183
|
-
const handleStatsRef = useRef(undefined);
|
|
1184
|
-
handleStatsRef.current = props.onStats;
|
|
1185
|
-
useEffect(() => {
|
|
1186
|
-
if (!call || !backgroundFilter)
|
|
1187
|
-
return;
|
|
1188
|
-
const { unregister } = call.camera.registerFilter((ms) => {
|
|
1189
|
-
return start(ms, (error) => handleErrorRef.current?.(error), (stats) => handleStatsRef.current?.(stats));
|
|
1190
|
-
});
|
|
1191
|
-
return () => {
|
|
1192
|
-
unregister().catch((err) => console.warn(`Can't unregister filter`, err));
|
|
1193
|
-
};
|
|
1194
|
-
}, [call, start, backgroundFilter]);
|
|
1195
|
-
return children;
|
|
1196
|
-
};
|
|
1197
|
-
const useRenderer = (tfLite, call, engine) => {
|
|
1198
|
-
const { backgroundFilter, backgroundBlurLevel, backgroundImage, modelFilePath, basePath, } = useBackgroundFilters();
|
|
1199
|
-
const videoRef = useRef(null);
|
|
1200
|
-
const canvasRef = useRef(null);
|
|
1201
|
-
const bgImageRef = useRef(null);
|
|
1202
|
-
const [videoSize, setVideoSize] = useState({
|
|
1203
|
-
width: 1920,
|
|
1204
|
-
height: 1080,
|
|
1205
|
-
});
|
|
1206
|
-
const start = useCallback((ms, onError, onStats) => {
|
|
1207
|
-
let outputStream;
|
|
1208
|
-
let processor;
|
|
1209
|
-
let renderer;
|
|
1210
|
-
const output = new Promise((resolve, reject) => {
|
|
1211
|
-
if (!backgroundFilter) {
|
|
1212
|
-
reject(new Error('No filter specified'));
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
const videoEl = videoRef.current;
|
|
1216
|
-
const canvasEl = canvasRef.current;
|
|
1217
|
-
const bgImageEl = bgImageRef.current;
|
|
1218
|
-
const [track] = ms.getVideoTracks();
|
|
1219
|
-
if (!track) {
|
|
1220
|
-
reject(new Error('No video tracks in input media stream'));
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
if (engine === FilterEngine.MEDIA_PIPE) {
|
|
1224
|
-
call?.tracer.trace('backgroundFilters.enable', {
|
|
1225
|
-
backgroundFilter,
|
|
1226
|
-
backgroundBlurLevel,
|
|
1227
|
-
backgroundImage,
|
|
1228
|
-
engine,
|
|
1229
|
-
});
|
|
1230
|
-
if (!videoEl) {
|
|
1231
|
-
reject(new Error('Renderer started before elements are ready'));
|
|
1232
|
-
return;
|
|
1233
|
-
}
|
|
1234
|
-
const trackSettings = track.getSettings();
|
|
1235
|
-
flushSync(() => setVideoSize({
|
|
1236
|
-
width: trackSettings.width ?? 0,
|
|
1237
|
-
height: trackSettings.height ?? 0,
|
|
1238
|
-
}));
|
|
1239
|
-
processor = new VirtualBackground(track, {
|
|
1240
|
-
basePath: basePath,
|
|
1241
|
-
modelPath: modelFilePath,
|
|
1242
|
-
backgroundBlurLevel,
|
|
1243
|
-
backgroundImage,
|
|
1244
|
-
backgroundFilter,
|
|
1245
|
-
}, { onError, onStats });
|
|
1246
|
-
processor
|
|
1247
|
-
.start()
|
|
1248
|
-
.then((processedTrack) => {
|
|
1249
|
-
outputStream = new MediaStream([processedTrack]);
|
|
1250
|
-
resolve(outputStream);
|
|
1251
|
-
})
|
|
1252
|
-
.catch((error) => {
|
|
1253
|
-
reject(error);
|
|
1254
|
-
});
|
|
1255
|
-
return;
|
|
1256
|
-
}
|
|
1257
|
-
if (engine === FilterEngine.TF) {
|
|
1258
|
-
if (!videoEl || !canvasEl || (backgroundImage && !bgImageEl)) {
|
|
1259
|
-
reject(new Error('Renderer started before elements are ready'));
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
videoEl.srcObject = ms;
|
|
1263
|
-
videoEl.play().then(() => {
|
|
1264
|
-
const trackSettings = track.getSettings();
|
|
1265
|
-
flushSync(() => setVideoSize({
|
|
1266
|
-
width: trackSettings.width ?? 0,
|
|
1267
|
-
height: trackSettings.height ?? 0,
|
|
1268
|
-
}));
|
|
1269
|
-
call?.tracer.trace('backgroundFilters.enable', {
|
|
1270
|
-
backgroundFilter,
|
|
1271
|
-
backgroundBlurLevel,
|
|
1272
|
-
backgroundImage,
|
|
1273
|
-
engine,
|
|
1274
|
-
});
|
|
1275
|
-
if (!tfLite) {
|
|
1276
|
-
reject(new Error('TensorFlow Lite not loaded'));
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
renderer = createRenderer(tfLite, videoEl, canvasEl, {
|
|
1280
|
-
backgroundFilter,
|
|
1281
|
-
backgroundBlurLevel,
|
|
1282
|
-
backgroundImage: bgImageEl ?? undefined,
|
|
1283
|
-
}, onError);
|
|
1284
|
-
outputStream = canvasEl.captureStream();
|
|
1285
|
-
resolve(outputStream);
|
|
1286
|
-
}, () => {
|
|
1287
|
-
reject(new Error('Could not play the source video stream'));
|
|
1288
|
-
});
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
reject(new Error('No supported engine available'));
|
|
1292
|
-
});
|
|
1293
|
-
return {
|
|
1294
|
-
output,
|
|
1295
|
-
stop: () => {
|
|
1296
|
-
call?.tracer.trace('backgroundFilters.disable', null);
|
|
1297
|
-
processor?.stop();
|
|
1298
|
-
renderer?.dispose();
|
|
1299
|
-
if (videoRef.current)
|
|
1300
|
-
videoRef.current.srcObject = null;
|
|
1301
|
-
if (outputStream)
|
|
1302
|
-
disposeOfMediaStream(outputStream);
|
|
1303
|
-
},
|
|
1304
|
-
};
|
|
1305
|
-
}, [
|
|
1306
|
-
backgroundBlurLevel,
|
|
1307
|
-
backgroundFilter,
|
|
1308
|
-
backgroundImage,
|
|
1309
|
-
call?.tracer,
|
|
1310
|
-
tfLite,
|
|
1311
|
-
engine,
|
|
1312
|
-
modelFilePath,
|
|
1313
|
-
basePath,
|
|
1314
|
-
]);
|
|
1315
|
-
const children = (jsxs("div", { className: "str-video__background-filters", children: [jsx("video", { className: clsx('str-video__background-filters__video', videoSize.height > videoSize.width &&
|
|
1316
|
-
'str-video__background-filters__video--tall'), ref: videoRef, playsInline: true, muted: true, controls: false, ...videoSize }), backgroundImage && (jsx("img", { className: "str-video__background-filters__background-image", alt: "Background", ref: bgImageRef, crossOrigin: "anonymous", src: backgroundImage, ...videoSize })), jsx("canvas", { className: "str-video__background-filters__target-canvas", ...videoSize, ref: canvasRef })] }));
|
|
1317
|
-
return { start, children };
|
|
1094
|
+
const { SuspenseFallback = null, ...filterProps } = props;
|
|
1095
|
+
return (jsx(Suspense, { fallback: SuspenseFallback, children: jsx(BackgroundFiltersProviderImpl, { ...filterProps, ContextProvider: BackgroundFiltersContext }) }));
|
|
1318
1096
|
};
|
|
1319
1097
|
|
|
1320
1098
|
const IconButton = forwardRef(function IconButton(props, ref) {
|
|
@@ -1786,7 +1564,7 @@ const SpeakerTest = (props) => {
|
|
|
1786
1564
|
const audioElementRef = useRef(null);
|
|
1787
1565
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
1788
1566
|
const { t } = useI18n();
|
|
1789
|
-
const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.
|
|
1567
|
+
const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.29.0"}/assets/piano.mp3`, } = props;
|
|
1790
1568
|
// Update audio output device when selection changes
|
|
1791
1569
|
useEffect(() => {
|
|
1792
1570
|
const audio = audioElementRef.current;
|
|
@@ -3279,7 +3057,7 @@ hostElement, limit) => {
|
|
|
3279
3057
|
};
|
|
3280
3058
|
|
|
3281
3059
|
const DefaultParticipantViewUIBar = () => (jsx(DefaultParticipantViewUI, { menuPlacement: "top-end" }));
|
|
3282
|
-
const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUI, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, mirrorLocalParticipantVideo = true, excludeLocalParticipant = false, filterParticipants, pageArrowsVisible = true, muted, }) => {
|
|
3060
|
+
const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUI, VideoPlaceholder, PictureInPicturePlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, mirrorLocalParticipantVideo = true, excludeLocalParticipant = false, filterParticipants, pageArrowsVisible = true, muted, enableDragToScroll = false, }) => {
|
|
3283
3061
|
const call = useCall();
|
|
3284
3062
|
const { useParticipants } = useCallStateHooks();
|
|
3285
3063
|
const allParticipants = useParticipants();
|
|
@@ -3300,6 +3078,9 @@ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, Par
|
|
|
3300
3078
|
}, [participantsBarWrapperElement, call]);
|
|
3301
3079
|
const isOneOnOneCall = allParticipants.length === 2;
|
|
3302
3080
|
useSpeakerLayoutSortPreset(call, isOneOnOneCall);
|
|
3081
|
+
useDragToScroll(participantsBarWrapperElement, {
|
|
3082
|
+
enabled: enableDragToScroll,
|
|
3083
|
+
});
|
|
3303
3084
|
let participantsWithAppliedLimit = otherParticipants;
|
|
3304
3085
|
const hardLimitToApply = isVertical
|
|
3305
3086
|
? hardLimit.vertical
|
|
@@ -3452,7 +3233,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
|
|
|
3452
3233
|
return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
|
|
3453
3234
|
};
|
|
3454
3235
|
|
|
3455
|
-
const [major, minor, patch] = ("1.
|
|
3236
|
+
const [major, minor, patch] = ("1.29.0").split('.');
|
|
3456
3237
|
setSdkInfo({
|
|
3457
3238
|
type: SfuModels.SdkType.REACT,
|
|
3458
3239
|
major,
|
|
@@ -3460,5 +3241,5 @@ setSdkInfo({
|
|
|
3460
3241
|
patch,
|
|
3461
3242
|
});
|
|
3462
3243
|
|
|
3463
|
-
export { AcceptCallButton, Audio, AudioVolumeIndicator, Avatar, AvatarFallback, BackgroundFiltersProvider, BackstageLayout, BaseVideo, CallControls, CallParticipantListing, CallParticipantListingItem, CallParticipantsList, CallPreview, CallRecordingList, CallRecordingListHeader, CallRecordingListItem, CallStats, CallStatsButton, CancelCallButton, CancelCallConfirmButton, CompositeButton, DefaultParticipantViewUI, DefaultReactionsMenu, DefaultScreenShareOverlay, DefaultVideoPlaceholder, DeviceSelector, DeviceSelectorAudioInput, DeviceSelectorAudioOutput, DeviceSelectorVideo, DeviceSettings, DropDownSelect, DropDownSelectOption, EmptyCallRecordingListing, GenericMenu, GenericMenuButtonItem, Icon, IconButton, LivestreamLayout, LivestreamPlayer, LoadingCallRecordingListing, LoadingIndicator, MenuToggle, MenuVisualType, NoiseCancellationProvider, Notification, PaginatedGridLayout, ParticipantActionsContextMenu, ParticipantDetails, ParticipantView, ParticipantViewContext, ParticipantsAudio,
|
|
3244
|
+
export { AcceptCallButton, Audio, AudioVolumeIndicator, Avatar, AvatarFallback, BackgroundFiltersProvider, BackstageLayout, BaseVideo, CallControls, CallParticipantListing, CallParticipantListingItem, CallParticipantsList, CallPreview, CallRecordingList, CallRecordingListHeader, CallRecordingListItem, CallStats, CallStatsButton, CancelCallButton, CancelCallConfirmButton, CompositeButton, DefaultParticipantViewUI, DefaultReactionsMenu, DefaultScreenShareOverlay, DefaultVideoPlaceholder, DeviceSelector, DeviceSelectorAudioInput, DeviceSelectorAudioOutput, DeviceSelectorVideo, DeviceSettings, DropDownSelect, DropDownSelectOption, EmptyCallRecordingListing, GenericMenu, GenericMenuButtonItem, Icon, IconButton, LivestreamLayout, LivestreamPlayer, LoadingCallRecordingListing, LoadingIndicator, MenuToggle, MenuVisualType, NoiseCancellationProvider, Notification, PaginatedGridLayout, ParticipantActionsContextMenu, ParticipantDetails, ParticipantView, ParticipantViewContext, ParticipantsAudio, PermissionNotification, PermissionRequestList, PermissionRequests, PipLayout, Reaction, ReactionsButton, RecordCallButton, RecordCallConfirmationButton, RecordingInProgressNotification, RingingCall, RingingCallControls, ScreenShareButton, SearchInput, SearchResults, SpeakerLayout, SpeakerTest, SpeakingWhileMutedNotification, SpeechIndicator, StatCard, StreamCall, StreamTheme, StreamVideo, TextButton, ToggleAudioOutputButton, ToggleAudioPreviewButton, ToggleAudioPublishingButton, ToggleVideoPreviewButton, ToggleVideoPublishingButton, Tooltip, Video$1 as Video, VideoPreview, WithTooltip, applyFilter, defaultEmojiReactionMap, defaultReactions, translations, useBackgroundFilters, useDeviceList, useFilteredParticipants, useHorizontalScrollPosition, useMenuContext, useModeration, useNoiseCancellation, useParticipantViewContext, usePersistedDevicePreferences, useRequestPermission, useTrackElementVisibility, useVerticalScrollPosition };
|
|
3464
3245
|
//# sourceMappingURL=index.es.js.map
|