@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/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { hasAudio, hasScreenShareAudio, CallingState, hasVideo, hasScreenShare, OwnCapability, Browsers, VisibilityState, hasPausedTrack, disposeOfMediaStream, createSoundDetector, SfuModels, isPinned, name, NoiseCancellationSettingsModeEnum, paginatedLayoutSortPreset, combineComparators, screenSharing, speakerLayoutSortPreset, CallTypes, defaultSortPreset, humanize, setSdkInfo } from '@stream-io/video-client';
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
- * Constants for FPS warning calculation.
962
- * Smooths out quick spikes using an EMA, ignores brief outliers,
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 { children, backgroundImages = [], backgroundFilter: bgFilterFromProps = undefined, backgroundImage: bgImageFromProps = undefined, backgroundBlurLevel: bgBlurLevelFromProps = undefined, tfFilePath, modelFilePath, useLegacyFilter, basePath, onError, performanceThresholds, forceSafariSupport, forceMobileSupport, } = props;
1030
- const call = useCall();
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.28.1"}/assets/piano.mp3`, } = props;
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.28.1").split('.');
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, PerformanceDegradationReason, 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 };
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