@stream-io/video-react-sdk 1.27.1 → 1.28.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.cjs.js CHANGED
@@ -466,6 +466,136 @@ function useDeviceList(devices, selectedDeviceId) {
466
466
  }, [devices, selectedDeviceId, t]);
467
467
  }
468
468
 
469
+ const isFullScreenBlurPlatformSupported = () => {
470
+ if (typeof window === 'undefined' ||
471
+ typeof OffscreenCanvas === 'undefined' ||
472
+ typeof VideoFrame === 'undefined' ||
473
+ !window.WebGL2RenderingContext) {
474
+ return false;
475
+ }
476
+ try {
477
+ const canvas = new OffscreenCanvas(1, 1);
478
+ return !!canvas.getContext('webgl2', {
479
+ alpha: false,
480
+ antialias: false,
481
+ desynchronized: true,
482
+ });
483
+ }
484
+ catch {
485
+ return false;
486
+ }
487
+ };
488
+ const useModeration = (options) => {
489
+ const { duration = 5000 } = options || {};
490
+ const call = videoReactBindings.useCall();
491
+ const timeoutRef = react.useRef(null);
492
+ const processorRef = react.useRef(null);
493
+ const unregisterRef = react.useRef(null);
494
+ const blurModulePromise = react.useRef(null);
495
+ /**
496
+ * Lazily loads and caches the video-filters-web module.
497
+ */
498
+ const loadVideoFiltersWebModule = react.useCallback(() => {
499
+ if (!blurModulePromise.current) {
500
+ blurModulePromise.current = import('@stream-io/video-filters-web')
501
+ .then((module) => module.FullScreenBlur)
502
+ .catch((error) => {
503
+ console.error('[moderation] Failed to import blur module:', error);
504
+ throw error;
505
+ });
506
+ }
507
+ return blurModulePromise.current;
508
+ }, []);
509
+ const disableBlur = react.useCallback(() => {
510
+ if (timeoutRef.current) {
511
+ clearTimeout(timeoutRef.current);
512
+ timeoutRef.current = null;
513
+ }
514
+ unregisterRef
515
+ .current?.()
516
+ .catch((err) => console.error('[moderation] unregister error:', err));
517
+ unregisterRef.current = null;
518
+ }, []);
519
+ const handleFallback = react.useCallback(async () => {
520
+ try {
521
+ await call?.camera.disable();
522
+ }
523
+ catch (error) {
524
+ console.error('[moderation] Failed to disable camera:', error);
525
+ }
526
+ }, [call]);
527
+ react.useEffect(() => {
528
+ if (!call)
529
+ return;
530
+ return call.on('call.moderation_warning', async () => {
531
+ try {
532
+ await loadVideoFiltersWebModule();
533
+ }
534
+ catch (importErr) {
535
+ console.error('[moderation] Failed to import blur module:', importErr);
536
+ }
537
+ });
538
+ }, [call, loadVideoFiltersWebModule]);
539
+ react.useEffect(() => {
540
+ if (!call)
541
+ return;
542
+ return call.on('call.moderation_blur', async () => {
543
+ if (unregisterRef.current)
544
+ return;
545
+ let FullScreenBlurClass;
546
+ try {
547
+ FullScreenBlurClass = await loadVideoFiltersWebModule();
548
+ }
549
+ catch (importErr) {
550
+ console.error('[moderation] Failed to import blur module:', importErr);
551
+ await handleFallback();
552
+ return;
553
+ }
554
+ if (!isFullScreenBlurPlatformSupported()) {
555
+ console.warn('[moderation] Blur not supported on this platform');
556
+ await handleFallback();
557
+ return;
558
+ }
559
+ const { unregister } = call.camera.registerFilter((inputStream) => {
560
+ unregisterRef.current = unregister;
561
+ const outputPromise = new Promise(async (resolve, reject) => {
562
+ const [track] = inputStream.getVideoTracks();
563
+ let processor;
564
+ try {
565
+ processor = new FullScreenBlurClass(track);
566
+ processorRef.current = processor;
567
+ const result = await processor.start();
568
+ const output = new MediaStream([result]);
569
+ resolve(output);
570
+ if (duration > 0) {
571
+ timeoutRef.current = setTimeout(disableBlur, duration);
572
+ }
573
+ }
574
+ catch (error) {
575
+ reject(error);
576
+ console.error('[moderation] Processor init failed:', error);
577
+ await unregisterRef.current?.();
578
+ unregisterRef.current = null;
579
+ processorRef.current = null;
580
+ await handleFallback();
581
+ return;
582
+ }
583
+ });
584
+ return {
585
+ output: outputPromise,
586
+ stop: () => {
587
+ if (processorRef.current) {
588
+ processorRef.current.stop();
589
+ processorRef.current = null;
590
+ }
591
+ },
592
+ };
593
+ });
594
+ });
595
+ }, [call, loadVideoFiltersWebModule, disableBlur, handleFallback, duration]);
596
+ react.useEffect(() => disableBlur, [disableBlur]);
597
+ };
598
+
469
599
  exports.MenuVisualType = void 0;
470
600
  (function (MenuVisualType) {
471
601
  MenuVisualType["PORTAL"] = "portal";
@@ -1656,7 +1786,7 @@ const SpeakerTest = (props) => {
1656
1786
  const audioElementRef = react.useRef(null);
1657
1787
  const [isPlaying, setIsPlaying] = react.useState(false);
1658
1788
  const { t } = videoReactBindings.useI18n();
1659
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.27.1"}/assets/piano.mp3`, } = props;
1789
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.28.0"}/assets/piano.mp3`, } = props;
1660
1790
  // Update audio output device when selection changes
1661
1791
  react.useEffect(() => {
1662
1792
  const audio = audioElementRef.current;
@@ -1886,7 +2016,7 @@ var Status;
1886
2016
  Status["BAD"] = "Bad";
1887
2017
  })(Status || (Status = {}));
1888
2018
  const CallStats = (props) => {
1889
- const { latencyLowBound = 75, latencyHighBound = 400, showCodecInfo = false, LatencyChartSuspenseFallback = null, } = props;
2019
+ const { latencyLowBound = 75, latencyHighBound = 400, audioJitterLowBound = 10, audioJitterHighBound = 30, videoJitterLowBound = 20, videoJitterHighBound = 50, showCodecInfo = false, LatencyChartSuspenseFallback = null, } = props;
1890
2020
  const [latencyBuffer, setLatencyBuffer] = react.useState(() => {
1891
2021
  const now = Date.now();
1892
2022
  return Array.from({ length: 20 }, (_, i) => ({ x: now + i, y: 0 }));
@@ -1894,6 +2024,8 @@ const CallStats = (props) => {
1894
2024
  const { t } = videoReactBindings.useI18n();
1895
2025
  const [publishBitrate, setPublishBitrate] = react.useState('-');
1896
2026
  const [subscribeBitrate, setSubscribeBitrate] = react.useState('-');
2027
+ const [publishAudioBitrate, setPublishAudioBitrate] = react.useState('-');
2028
+ const [subscribeAudioBitrate, setSubscribeAudioBitrate] = react.useState('-');
1897
2029
  const previousStats = react.useRef(undefined);
1898
2030
  const { useCallStatsReport } = videoReactBindings.useCallStateHooks();
1899
2031
  const callStatsReport = useCallStatsReport();
@@ -1911,11 +2043,18 @@ const CallStats = (props) => {
1911
2043
  setSubscribeBitrate(() => {
1912
2044
  return calculateSubscribeBitrate(previousCallStatsReport, callStatsReport);
1913
2045
  });
2046
+ setPublishAudioBitrate(() => {
2047
+ return calculatePublishAudioBitrate(previousCallStatsReport, callStatsReport);
2048
+ });
2049
+ setSubscribeAudioBitrate(() => {
2050
+ return calculateSubscribeAudioBitrate(previousCallStatsReport, callStatsReport);
2051
+ });
1914
2052
  setLatencyBuffer((latencyBuf) => {
1915
2053
  const newLatencyBuffer = latencyBuf.slice(-19);
1916
2054
  newLatencyBuffer.push({
1917
2055
  x: callStatsReport.timestamp,
1918
- y: callStatsReport.publisherStats.averageRoundTripTimeInMs,
2056
+ y: callStatsReport.publisherStats.averageRoundTripTimeInMs ||
2057
+ callStatsReport.publisherAudioStats.averageRoundTripTimeInMs,
1919
2058
  });
1920
2059
  return newLatencyBuffer;
1921
2060
  });
@@ -1926,13 +2065,27 @@ const CallStats = (props) => {
1926
2065
  highBound: latencyHighBound,
1927
2066
  value: callStatsReport?.publisherStats.averageRoundTripTimeInMs || 0,
1928
2067
  };
1929
- return (jsxRuntime.jsx("div", { className: "str-video__call-stats", children: callStatsReport && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "call-latency" }), t('Call Latency')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.') })] }), jsxRuntime.jsx("div", { className: "str-video__call-stats__latencychart", children: jsxRuntime.jsx(react.Suspense, { fallback: LatencyChartSuspenseFallback, children: jsxRuntime.jsx(CallStatsLatencyChart, { values: latencyBuffer }) }) }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "network-quality" }), t('Call performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key data points below to assess call performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: t('Region'), value: callStatsReport.datacenter }), jsxRuntime.jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: t('Receive jitter'), value: `${callStatsReport.subscriberStats.averageJitterInMs} ms.`, comparison: {
1930
- ...latencyComparison,
2068
+ const audioJitterComparison = {
2069
+ lowBound: audioJitterLowBound,
2070
+ highBound: audioJitterHighBound,
2071
+ };
2072
+ const videoJitterComparison = {
2073
+ lowBound: videoJitterLowBound,
2074
+ highBound: videoJitterHighBound,
2075
+ };
2076
+ return (jsxRuntime.jsx("div", { className: "str-video__call-stats", children: callStatsReport && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "call-latency" }), t('Call Latency')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.') })] }), jsxRuntime.jsx("div", { className: "str-video__call-stats__latencychart", children: jsxRuntime.jsx(react.Suspense, { fallback: LatencyChartSuspenseFallback, children: jsxRuntime.jsx(CallStatsLatencyChart, { values: latencyBuffer }) }) }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "network-quality" }), t('Video performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key data points below to assess call performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: t('Region'), value: callStatsReport.datacenter }), jsxRuntime.jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: t('Receive jitter'), value: `${callStatsReport.subscriberStats.averageJitterInMs} ms.`, comparison: {
2077
+ ...videoJitterComparison,
1931
2078
  value: callStatsReport.subscriberStats.averageJitterInMs,
1932
2079
  } }), jsxRuntime.jsx(StatCard, { label: t('Publish jitter'), value: `${callStatsReport.publisherStats.averageJitterInMs} ms.`, comparison: {
1933
- ...latencyComparison,
2080
+ ...videoJitterComparison,
1934
2081
  value: callStatsReport.publisherStats.averageJitterInMs,
1935
- } }), jsxRuntime.jsx(StatCard, { label: `${t('Publish resolution')}${showCodecInfo ? formatCodec(callStatsReport) : ''}`, value: toFrameSize(callStatsReport.publisherStats) }), jsxRuntime.jsx(StatCard, { label: t('Publish quality drop reason'), value: callStatsReport.publisherStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Receiving resolution'), value: toFrameSize(callStatsReport.subscriberStats) }), jsxRuntime.jsx(StatCard, { label: t('Receive quality drop reason'), value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Publish bitrate'), value: publishBitrate }), jsxRuntime.jsx(StatCard, { label: t('Receiving bitrate'), value: subscribeBitrate })] })] })) }));
2082
+ } }), jsxRuntime.jsx(StatCard, { label: `${t('Publish resolution')}${showCodecInfo ? formatCodec(callStatsReport) : ''}`, value: toFrameSize(callStatsReport.publisherStats) }), jsxRuntime.jsx(StatCard, { label: t('Publish quality drop reason'), value: callStatsReport.publisherStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Receiving resolution'), value: toFrameSize(callStatsReport.subscriberStats) }), jsxRuntime.jsx(StatCard, { label: t('Receive quality drop reason'), value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Publish bitrate'), value: publishBitrate }), jsxRuntime.jsx(StatCard, { label: t('Receiving bitrate'), value: subscribeBitrate })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "mic" }), t('Audio Performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key audio data points below to assess audio performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherAudioStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: t('Audio bitrate (publish)'), value: publishAudioBitrate }), jsxRuntime.jsx(StatCard, { label: t('Audio bitrate (receive)'), value: subscribeAudioBitrate }), jsxRuntime.jsx(StatCard, { label: t('Audio jitter (publish)'), value: `${callStatsReport.publisherAudioStats.averageJitterInMs} ms.`, comparison: {
2083
+ ...audioJitterComparison,
2084
+ value: callStatsReport.publisherAudioStats.averageJitterInMs,
2085
+ } }), jsxRuntime.jsx(StatCard, { label: t('Audio jitter (receive)'), value: `${callStatsReport.subscriberAudioStats.averageJitterInMs} ms.`, comparison: {
2086
+ ...audioJitterComparison,
2087
+ value: callStatsReport.subscriberAudioStats.averageJitterInMs,
2088
+ } }), jsxRuntime.jsx(StatCard, { label: t('Audio codec'), value: formatAudioCodec(callStatsReport) })] })] })) }));
1936
2089
  };
1937
2090
  const StatCardExplanation = (props) => {
1938
2091
  const { description } = props;
@@ -1988,6 +2141,14 @@ const formatCodec = (callStatsReport) => {
1988
2141
  const [, name] = codecPerTrackType[videoClient.SfuModels.TrackType.VIDEO].split('/');
1989
2142
  return name ? ` (${name})` : '';
1990
2143
  };
2144
+ const formatAudioCodec = (callStatsReport) => {
2145
+ const { codecPerTrackType } = callStatsReport.publisherAudioStats;
2146
+ if (!codecPerTrackType || !codecPerTrackType[videoClient.SfuModels.TrackType.AUDIO]) {
2147
+ return '';
2148
+ }
2149
+ const [, name] = codecPerTrackType[videoClient.SfuModels.TrackType.AUDIO].split('/');
2150
+ return name ?? '';
2151
+ };
1991
2152
  const calculatePublishBitrate = (previousCallStatsReport, callStatsReport) => {
1992
2153
  const { publisherStats: { totalBytesSent: previousTotalBytesSent, timestamp: previousTimestamp, }, } = previousCallStatsReport;
1993
2154
  const { publisherStats: { totalBytesSent, timestamp }, } = callStatsReport;
@@ -2002,6 +2163,20 @@ const calculateSubscribeBitrate = (previousCallStatsReport, callStatsReport) =>
2002
2163
  const timeElapsed = timestamp - previousTimestamp;
2003
2164
  return `${((bytesReceived * 8) / timeElapsed).toFixed(2)} kbps`;
2004
2165
  };
2166
+ const calculatePublishAudioBitrate = (previousCallStatsReport, callStatsReport) => {
2167
+ const previousAudioStats = previousCallStatsReport.publisherAudioStats;
2168
+ const audioStats = callStatsReport.publisherAudioStats;
2169
+ const bytesSent = audioStats.totalBytesSent - previousAudioStats.totalBytesSent;
2170
+ const timeElapsed = audioStats.timestamp - previousAudioStats.timestamp;
2171
+ return `${((bytesSent * 8) / timeElapsed).toFixed(2)} kbps`;
2172
+ };
2173
+ const calculateSubscribeAudioBitrate = (previousCallStatsReport, callStatsReport) => {
2174
+ const previousAudioStats = previousCallStatsReport.subscriberAudioStats;
2175
+ const audioStats = callStatsReport.subscriberAudioStats;
2176
+ const bytesReceived = audioStats.totalBytesReceived - previousAudioStats.totalBytesReceived;
2177
+ const timeElapsed = audioStats.timestamp - previousAudioStats.timestamp;
2178
+ return `${((bytesReceived * 8) / timeElapsed).toFixed(2)} kbps`;
2179
+ };
2005
2180
 
2006
2181
  const CallStatsButton = () => (jsxRuntime.jsx(MenuToggle, { placement: "top-end", ToggleButton: ToggleMenuButton, children: jsxRuntime.jsx(CallStats, {}) }));
2007
2182
  const ToggleMenuButton = react.forwardRef(function ToggleMenuButton(props, ref) {
@@ -2301,10 +2476,14 @@ const NoiseCancellationProvider = (props) => {
2301
2476
  ? enabledOrSetter(isEnabled)
2302
2477
  : enabledOrSetter;
2303
2478
  if (enable) {
2304
- noiseCancellation.enable();
2479
+ noiseCancellation.enable().catch((err) => {
2480
+ console.error('Failed to enable noise cancellation', err);
2481
+ });
2305
2482
  }
2306
2483
  else {
2307
- noiseCancellation.disable();
2484
+ noiseCancellation.disable().catch((err) => {
2485
+ console.error('Failed to disable noise cancellation', err);
2486
+ });
2308
2487
  }
2309
2488
  },
2310
2489
  }), [isEnabled, isSupported, noiseCancellation]);
@@ -2897,7 +3076,7 @@ const LivestreamLayout = (props) => {
2897
3076
  : undefined;
2898
3077
  usePaginatedLayoutSortPreset(call);
2899
3078
  const { floatingParticipantProps, muted, ParticipantViewUI } = props;
2900
- const overlay = ParticipantViewUI ?? (jsxRuntime.jsx(ParticipantOverlay, { showParticipantCount: props.showParticipantCount, showDuration: props.showDuration, showLiveBadge: props.showLiveBadge, showSpeakerName: props.showSpeakerName, enableFullScreen: props.enableFullScreen }));
3079
+ const overlay = ParticipantViewUI ?? (jsxRuntime.jsx(ParticipantOverlay, { showParticipantCount: props.showParticipantCount, showDuration: props.showDuration, showLiveBadge: props.showLiveBadge, showMuteButton: props.showMuteButton, showSpeakerName: props.showSpeakerName, enableFullScreen: props.enableFullScreen }));
2901
3080
  const floatingParticipantOverlay = hasOngoingScreenShare &&
2902
3081
  (ParticipantViewUI ?? (jsxRuntime.jsx(ParticipantOverlay
2903
3082
  // these elements aren't needed for the video feed
@@ -2911,7 +3090,7 @@ const LivestreamLayout = (props) => {
2911
3090
  };
2912
3091
  LivestreamLayout.displayName = 'LivestreamLayout';
2913
3092
  const BackstageLayout = (props) => {
2914
- const { showEarlyParticipantCount = true } = props;
3093
+ const { showEarlyParticipantCount = true, humanizeParticipantCount = true } = props;
2915
3094
  const { useParticipantCount, useCallStartsAt } = videoReactBindings.useCallStateHooks();
2916
3095
  const participantCount = useParticipantCount();
2917
3096
  const startsAt = useCallStartsAt();
@@ -2919,24 +3098,32 @@ const BackstageLayout = (props) => {
2919
3098
  return (jsxRuntime.jsx("div", { className: "str-video__livestream-layout__wrapper", children: jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__backstage", children: [startsAt && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__starts-at", children: startsAt.getTime() < Date.now()
2920
3099
  ? t('Livestream starts soon')
2921
3100
  : t('Livestream starts at {{ startsAt }}', { startsAt }) })), showEarlyParticipantCount && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__early-viewers-count", children: t('{{ count }} participants joined early', {
2922
- count: participantCount,
3101
+ count: humanizeParticipantCount
3102
+ ? videoClient.humanize(participantCount)
3103
+ : participantCount,
2923
3104
  }) }))] }) }));
2924
3105
  };
2925
3106
  BackstageLayout.displayName = 'BackstageLayout';
2926
3107
  const ParticipantOverlay = (props) => {
2927
- const { enableFullScreen = true, showParticipantCount = true, showDuration = true, showLiveBadge = true, showSpeakerName = false, } = props;
3108
+ const { enableFullScreen = true, showParticipantCount = true, humanizeParticipantCount = true, showDuration = true, showLiveBadge = true, showMuteButton = true, showSpeakerName = false, } = props;
2928
3109
  const overlayBarVisible = enableFullScreen ||
2929
3110
  showParticipantCount ||
2930
3111
  showDuration ||
2931
3112
  showLiveBadge ||
3113
+ showMuteButton ||
2932
3114
  showSpeakerName;
2933
3115
  const { participant } = useParticipantViewContext();
2934
- const { useParticipantCount } = videoReactBindings.useCallStateHooks();
3116
+ const { useParticipantCount, useSpeakerState } = videoReactBindings.useCallStateHooks();
2935
3117
  const participantCount = useParticipantCount();
2936
3118
  const duration = useUpdateCallDuration();
2937
3119
  const toggleFullScreen = useToggleFullScreen();
3120
+ const { speaker, volume } = useSpeakerState();
3121
+ const isSpeakerMuted = volume === 0;
2938
3122
  const { t } = videoReactBindings.useI18n();
2939
- return (jsxRuntime.jsx("div", { className: "str-video__livestream-layout__overlay", children: overlayBarVisible && (jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar", children: [showLiveBadge && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__live-badge", children: t('Live') })), showParticipantCount && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__viewers-count", children: participantCount })), showSpeakerName && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__speaker-name", title: participant.name || participant.userId || '', children: participant.name || participant.userId || '' })), showDuration && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__duration", children: formatDuration(duration) })), enableFullScreen && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__go-fullscreen", onClick: toggleFullScreen }))] })) }));
3123
+ return (jsxRuntime.jsx("div", { className: "str-video__livestream-layout__overlay", children: overlayBarVisible && (jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar", children: [showLiveBadge && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__live-badge", children: t('Live') })), showParticipantCount && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__viewers-count", children: humanizeParticipantCount
3124
+ ? videoClient.humanize(participantCount)
3125
+ : participantCount })), showSpeakerName && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__speaker-name", title: participant.name || participant.userId || '', children: participant.name || participant.userId || '' })), showDuration && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__duration", children: formatDuration(duration) })), showMuteButton && (jsxRuntime.jsx("span", { className: clsx('str-video__livestream-layout__mute-button', isSpeakerMuted &&
3126
+ 'str-video__livestream-layout__mute-button--muted'), onClick: () => speaker.setVolume(isSpeakerMuted ? 1 : 0) })), enableFullScreen && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__go-fullscreen", onClick: toggleFullScreen }))] })) }));
2940
3127
  };
2941
3128
  const useUpdateCallDuration = () => {
2942
3129
  const { useIsCallLive, useCallSession } = videoReactBindings.useCallStateHooks();
@@ -3265,7 +3452,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
3265
3452
  return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
3266
3453
  };
3267
3454
 
3268
- const [major, minor, patch] = ("1.27.1").split('.');
3455
+ const [major, minor, patch] = ("1.28.0").split('.');
3269
3456
  videoClient.setSdkInfo({
3270
3457
  type: videoClient.SfuModels.SdkType.REACT,
3271
3458
  major,
@@ -3364,6 +3551,7 @@ exports.useDeviceList = useDeviceList;
3364
3551
  exports.useFilteredParticipants = useFilteredParticipants;
3365
3552
  exports.useHorizontalScrollPosition = useHorizontalScrollPosition;
3366
3553
  exports.useMenuContext = useMenuContext;
3554
+ exports.useModeration = useModeration;
3367
3555
  exports.useNoiseCancellation = useNoiseCancellation;
3368
3556
  exports.useParticipantViewContext = useParticipantViewContext;
3369
3557
  exports.usePersistedDevicePreferences = usePersistedDevicePreferences;