@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/CHANGELOG.md +30 -0
- package/dist/css/styles.css +14 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/index.cjs.js +204 -16
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +205 -18
- 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 +6 -6
- package/src/components/CallStats/CallStats.tsx +123 -4
- package/src/components/NoiseCancellation/NoiseCancellationProvider.tsx +6 -2
- 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.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export * from './src/core';
|
|
|
4
4
|
export * from './src/components';
|
|
5
5
|
export * from './src/wrappers';
|
|
6
6
|
export * from './src/translations';
|
|
7
|
-
export { useHorizontalScrollPosition, useVerticalScrollPosition, useRequestPermission, usePersistedDevicePreferences, useDeviceList, } from './src/hooks';
|
|
7
|
+
export { useHorizontalScrollPosition, useVerticalScrollPosition, useRequestPermission, usePersistedDevicePreferences, useDeviceList, useModeration, } from './src/hooks';
|
|
8
8
|
export { applyFilter, type Filter } from './src/utilities/filter';
|
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, setSdkInfo } from '@stream-io/video-client';
|
|
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';
|
|
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';
|
|
@@ -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 = useCall();
|
|
491
|
+
const timeoutRef = useRef(null);
|
|
492
|
+
const processorRef = useRef(null);
|
|
493
|
+
const unregisterRef = useRef(null);
|
|
494
|
+
const blurModulePromise = useRef(null);
|
|
495
|
+
/**
|
|
496
|
+
* Lazily loads and caches the video-filters-web module.
|
|
497
|
+
*/
|
|
498
|
+
const loadVideoFiltersWebModule = 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 = 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 = 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
|
+
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
|
+
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
|
+
useEffect(() => disableBlur, [disableBlur]);
|
|
597
|
+
};
|
|
598
|
+
|
|
469
599
|
var MenuVisualType;
|
|
470
600
|
(function (MenuVisualType) {
|
|
471
601
|
MenuVisualType["PORTAL"] = "portal";
|
|
@@ -1656,7 +1786,7 @@ const SpeakerTest = (props) => {
|
|
|
1656
1786
|
const audioElementRef = useRef(null);
|
|
1657
1787
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
1658
1788
|
const { t } = 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.0"}/assets/piano.mp3`, } = props;
|
|
1660
1790
|
// Update audio output device when selection changes
|
|
1661
1791
|
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] = 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 } = useI18n();
|
|
1895
2025
|
const [publishBitrate, setPublishBitrate] = useState('-');
|
|
1896
2026
|
const [subscribeBitrate, setSubscribeBitrate] = useState('-');
|
|
2027
|
+
const [publishAudioBitrate, setPublishAudioBitrate] = useState('-');
|
|
2028
|
+
const [subscribeAudioBitrate, setSubscribeAudioBitrate] = useState('-');
|
|
1897
2029
|
const previousStats = useRef(undefined);
|
|
1898
2030
|
const { useCallStatsReport } = 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 (jsx("div", { className: "str-video__call-stats", children: callStatsReport && (jsxs(Fragment, { children: [jsxs("div", { className: "str-video__call-stats__header", children: [jsxs("h3", { className: "str-video__call-stats__heading", children: [jsx(Icon, { className: "str-video__call-stats__icon", icon: "call-latency" }), t('Call Latency')] }), 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.') })] }), jsx("div", { className: "str-video__call-stats__latencychart", children: jsx(Suspense, { fallback: LatencyChartSuspenseFallback, children: jsx(CallStatsLatencyChart, { values: latencyBuffer }) }) }), jsxs("div", { className: "str-video__call-stats__header", children: [jsxs("h3", { className: "str-video__call-stats__heading", children: [jsx(Icon, { className: "str-video__call-stats__icon", icon: "network-quality" }), t('Video performance')] }), jsx("p", { className: "str-video__call-stats__description", children: t('Review the key data points below to assess call performance') })] }), jsxs("div", { className: "str-video__call-stats__card-container", children: [jsx(StatCard, { label: t('Region'), value: callStatsReport.datacenter }), jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsx(StatCard, { label: t('Receive jitter'), value: `${callStatsReport.subscriberStats.averageJitterInMs} ms.`, comparison: {
|
|
2077
|
+
...videoJitterComparison,
|
|
1931
2078
|
value: callStatsReport.subscriberStats.averageJitterInMs,
|
|
1932
2079
|
} }), jsx(StatCard, { label: t('Publish jitter'), value: `${callStatsReport.publisherStats.averageJitterInMs} ms.`, comparison: {
|
|
1933
|
-
...
|
|
2080
|
+
...videoJitterComparison,
|
|
1934
2081
|
value: callStatsReport.publisherStats.averageJitterInMs,
|
|
1935
|
-
} }), jsx(StatCard, { label: `${t('Publish resolution')}${showCodecInfo ? formatCodec(callStatsReport) : ''}`, value: toFrameSize(callStatsReport.publisherStats) }), jsx(StatCard, { label: t('Publish quality drop reason'), value: callStatsReport.publisherStats.qualityLimitationReasons }), jsx(StatCard, { label: t('Receiving resolution'), value: toFrameSize(callStatsReport.subscriberStats) }), jsx(StatCard, { label: t('Receive quality drop reason'), value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsx(StatCard, { label: t('Publish bitrate'), value: publishBitrate }), jsx(StatCard, { label: t('Receiving bitrate'), value: subscribeBitrate })] })] })) }))
|
|
2082
|
+
} }), jsx(StatCard, { label: `${t('Publish resolution')}${showCodecInfo ? formatCodec(callStatsReport) : ''}`, value: toFrameSize(callStatsReport.publisherStats) }), jsx(StatCard, { label: t('Publish quality drop reason'), value: callStatsReport.publisherStats.qualityLimitationReasons }), jsx(StatCard, { label: t('Receiving resolution'), value: toFrameSize(callStatsReport.subscriberStats) }), jsx(StatCard, { label: t('Receive quality drop reason'), value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsx(StatCard, { label: t('Publish bitrate'), value: publishBitrate }), jsx(StatCard, { label: t('Receiving bitrate'), value: subscribeBitrate })] }), jsxs("div", { className: "str-video__call-stats__header", children: [jsxs("h3", { className: "str-video__call-stats__heading", children: [jsx(Icon, { className: "str-video__call-stats__icon", icon: "mic" }), t('Audio Performance')] }), jsx("p", { className: "str-video__call-stats__description", children: t('Review the key audio data points below to assess audio performance') })] }), jsxs("div", { className: "str-video__call-stats__card-container", children: [jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherAudioStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsx(StatCard, { label: t('Audio bitrate (publish)'), value: publishAudioBitrate }), jsx(StatCard, { label: t('Audio bitrate (receive)'), value: subscribeAudioBitrate }), jsx(StatCard, { label: t('Audio jitter (publish)'), value: `${callStatsReport.publisherAudioStats.averageJitterInMs} ms.`, comparison: {
|
|
2083
|
+
...audioJitterComparison,
|
|
2084
|
+
value: callStatsReport.publisherAudioStats.averageJitterInMs,
|
|
2085
|
+
} }), jsx(StatCard, { label: t('Audio jitter (receive)'), value: `${callStatsReport.subscriberAudioStats.averageJitterInMs} ms.`, comparison: {
|
|
2086
|
+
...audioJitterComparison,
|
|
2087
|
+
value: callStatsReport.subscriberAudioStats.averageJitterInMs,
|
|
2088
|
+
} }), 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[SfuModels.TrackType.VIDEO].split('/');
|
|
1989
2142
|
return name ? ` (${name})` : '';
|
|
1990
2143
|
};
|
|
2144
|
+
const formatAudioCodec = (callStatsReport) => {
|
|
2145
|
+
const { codecPerTrackType } = callStatsReport.publisherAudioStats;
|
|
2146
|
+
if (!codecPerTrackType || !codecPerTrackType[SfuModels.TrackType.AUDIO]) {
|
|
2147
|
+
return '';
|
|
2148
|
+
}
|
|
2149
|
+
const [, name] = codecPerTrackType[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 = () => (jsx(MenuToggle, { placement: "top-end", ToggleButton: ToggleMenuButton, children: jsx(CallStats, {}) }));
|
|
2007
2182
|
const ToggleMenuButton = 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 ?? (jsx(ParticipantOverlay, { showParticipantCount: props.showParticipantCount, showDuration: props.showDuration, showLiveBadge: props.showLiveBadge, showSpeakerName: props.showSpeakerName, enableFullScreen: props.enableFullScreen }));
|
|
3079
|
+
const overlay = ParticipantViewUI ?? (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 ?? (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 } = useCallStateHooks();
|
|
2916
3095
|
const participantCount = useParticipantCount();
|
|
2917
3096
|
const startsAt = useCallStartsAt();
|
|
@@ -2919,24 +3098,32 @@ const BackstageLayout = (props) => {
|
|
|
2919
3098
|
return (jsx("div", { className: "str-video__livestream-layout__wrapper", children: jsxs("div", { className: "str-video__livestream-layout__backstage", children: [startsAt && (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 && (jsx("span", { className: "str-video__livestream-layout__early-viewers-count", children: t('{{ count }} participants joined early', {
|
|
2922
|
-
count:
|
|
3101
|
+
count: humanizeParticipantCount
|
|
3102
|
+
? 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 } = useCallStateHooks();
|
|
3116
|
+
const { useParticipantCount, useSpeakerState } = 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 } = useI18n();
|
|
2939
|
-
return (jsx("div", { className: "str-video__livestream-layout__overlay", children: overlayBarVisible && (jsxs("div", { className: "str-video__livestream-layout__overlay__bar", children: [showLiveBadge && (jsx("span", { className: "str-video__livestream-layout__live-badge", children: t('Live') })), showParticipantCount && (jsx("span", { className: "str-video__livestream-layout__viewers-count", children:
|
|
3123
|
+
return (jsx("div", { className: "str-video__livestream-layout__overlay", children: overlayBarVisible && (jsxs("div", { className: "str-video__livestream-layout__overlay__bar", children: [showLiveBadge && (jsx("span", { className: "str-video__livestream-layout__live-badge", children: t('Live') })), showParticipantCount && (jsx("span", { className: "str-video__livestream-layout__viewers-count", children: humanizeParticipantCount
|
|
3124
|
+
? humanize(participantCount)
|
|
3125
|
+
: participantCount })), showSpeakerName && (jsx("span", { className: "str-video__livestream-layout__speaker-name", title: participant.name || participant.userId || '', children: participant.name || participant.userId || '' })), showDuration && (jsx("span", { className: "str-video__livestream-layout__duration", children: formatDuration(duration) })), showMuteButton && (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 && (jsx("span", { className: "str-video__livestream-layout__go-fullscreen", onClick: toggleFullScreen }))] })) }));
|
|
2940
3127
|
};
|
|
2941
3128
|
const useUpdateCallDuration = () => {
|
|
2942
3129
|
const { useIsCallLive, useCallSession } = 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.
|
|
3455
|
+
const [major, minor, patch] = ("1.28.0").split('.');
|
|
3269
3456
|
setSdkInfo({
|
|
3270
3457
|
type: SfuModels.SdkType.REACT,
|
|
3271
3458
|
major,
|
|
@@ -3273,5 +3460,5 @@ setSdkInfo({
|
|
|
3273
3460
|
patch,
|
|
3274
3461
|
});
|
|
3275
3462
|
|
|
3276
|
-
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, useNoiseCancellation, useParticipantViewContext, usePersistedDevicePreferences, useRequestPermission, useTrackElementVisibility, useVerticalScrollPosition };
|
|
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 };
|
|
3277
3464
|
//# sourceMappingURL=index.es.js.map
|