@stream-io/video-react-sdk 1.27.2 → 1.28.1
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 +22 -0
- package/dist/css/styles.css +14 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/index.cjs.js +198 -14
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +199 -16
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/CallStats/CallStats.d.ts +4 -0
- package/dist/src/core/components/CallLayout/LivestreamLayout.d.ts +22 -0
- package/dist/src/hooks/index.d.ts +1 -0
- package/dist/src/hooks/useModeration.d.ts +8 -0
- package/index.ts +1 -0
- package/package.json +5 -5
- package/src/components/CallStats/CallStats.tsx +123 -4
- package/src/core/components/CallLayout/LivestreamLayout.tsx +53 -5
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useModeration.ts +167 -0
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.
|
|
1789
|
+
const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.28.1"}/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
|
-
|
|
1930
|
-
|
|
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
|
-
...
|
|
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) {
|
|
@@ -2901,7 +3076,7 @@ const LivestreamLayout = (props) => {
|
|
|
2901
3076
|
: undefined;
|
|
2902
3077
|
usePaginatedLayoutSortPreset(call);
|
|
2903
3078
|
const { floatingParticipantProps, muted, ParticipantViewUI } = props;
|
|
2904
|
-
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 }));
|
|
2905
3080
|
const floatingParticipantOverlay = hasOngoingScreenShare &&
|
|
2906
3081
|
(ParticipantViewUI ?? (jsxRuntime.jsx(ParticipantOverlay
|
|
2907
3082
|
// these elements aren't needed for the video feed
|
|
@@ -2915,7 +3090,7 @@ const LivestreamLayout = (props) => {
|
|
|
2915
3090
|
};
|
|
2916
3091
|
LivestreamLayout.displayName = 'LivestreamLayout';
|
|
2917
3092
|
const BackstageLayout = (props) => {
|
|
2918
|
-
const { showEarlyParticipantCount = true } = props;
|
|
3093
|
+
const { showEarlyParticipantCount = true, humanizeParticipantCount = true } = props;
|
|
2919
3094
|
const { useParticipantCount, useCallStartsAt } = videoReactBindings.useCallStateHooks();
|
|
2920
3095
|
const participantCount = useParticipantCount();
|
|
2921
3096
|
const startsAt = useCallStartsAt();
|
|
@@ -2923,24 +3098,32 @@ const BackstageLayout = (props) => {
|
|
|
2923
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()
|
|
2924
3099
|
? t('Livestream starts soon')
|
|
2925
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', {
|
|
2926
|
-
count:
|
|
3101
|
+
count: humanizeParticipantCount
|
|
3102
|
+
? videoClient.humanize(participantCount)
|
|
3103
|
+
: participantCount,
|
|
2927
3104
|
}) }))] }) }));
|
|
2928
3105
|
};
|
|
2929
3106
|
BackstageLayout.displayName = 'BackstageLayout';
|
|
2930
3107
|
const ParticipantOverlay = (props) => {
|
|
2931
|
-
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;
|
|
2932
3109
|
const overlayBarVisible = enableFullScreen ||
|
|
2933
3110
|
showParticipantCount ||
|
|
2934
3111
|
showDuration ||
|
|
2935
3112
|
showLiveBadge ||
|
|
3113
|
+
showMuteButton ||
|
|
2936
3114
|
showSpeakerName;
|
|
2937
3115
|
const { participant } = useParticipantViewContext();
|
|
2938
|
-
const { useParticipantCount } = videoReactBindings.useCallStateHooks();
|
|
3116
|
+
const { useParticipantCount, useSpeakerState } = videoReactBindings.useCallStateHooks();
|
|
2939
3117
|
const participantCount = useParticipantCount();
|
|
2940
3118
|
const duration = useUpdateCallDuration();
|
|
2941
3119
|
const toggleFullScreen = useToggleFullScreen();
|
|
3120
|
+
const { speaker, volume } = useSpeakerState();
|
|
3121
|
+
const isSpeakerMuted = volume === 0;
|
|
2942
3122
|
const { t } = videoReactBindings.useI18n();
|
|
2943
|
-
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:
|
|
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 }))] })) }));
|
|
2944
3127
|
};
|
|
2945
3128
|
const useUpdateCallDuration = () => {
|
|
2946
3129
|
const { useIsCallLive, useCallSession } = videoReactBindings.useCallStateHooks();
|
|
@@ -3269,7 +3452,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
|
|
|
3269
3452
|
return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
|
|
3270
3453
|
};
|
|
3271
3454
|
|
|
3272
|
-
const [major, minor, patch] = ("1.
|
|
3455
|
+
const [major, minor, patch] = ("1.28.1").split('.');
|
|
3273
3456
|
videoClient.setSdkInfo({
|
|
3274
3457
|
type: videoClient.SfuModels.SdkType.REACT,
|
|
3275
3458
|
major,
|
|
@@ -3368,6 +3551,7 @@ exports.useDeviceList = useDeviceList;
|
|
|
3368
3551
|
exports.useFilteredParticipants = useFilteredParticipants;
|
|
3369
3552
|
exports.useHorizontalScrollPosition = useHorizontalScrollPosition;
|
|
3370
3553
|
exports.useMenuContext = useMenuContext;
|
|
3554
|
+
exports.useModeration = useModeration;
|
|
3371
3555
|
exports.useNoiseCancellation = useNoiseCancellation;
|
|
3372
3556
|
exports.useParticipantViewContext = useParticipantViewContext;
|
|
3373
3557
|
exports.usePersistedDevicePreferences = usePersistedDevicePreferences;
|