@stream-io/video-react-sdk 0.3.28 → 0.3.30
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 +19 -0
- package/README.md +7 -5
- package/dist/css/styles.css +100 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js +2 -1
- package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js.map +1 -1
- package/dist/src/core/components/Audio/ParticipantsAudio.d.ts +14 -0
- package/dist/src/core/components/Audio/ParticipantsAudio.js +11 -0
- package/dist/src/core/components/Audio/ParticipantsAudio.js.map +1 -0
- package/dist/src/core/components/Audio/index.d.ts +1 -0
- package/dist/src/core/components/Audio/index.js +1 -0
- package/dist/src/core/components/Audio/index.js.map +1 -1
- package/dist/src/core/components/CallLayout/LivestreamLayout.d.ts +39 -0
- package/dist/src/core/components/CallLayout/LivestreamLayout.js +91 -0
- package/dist/src/core/components/CallLayout/LivestreamLayout.js.map +1 -0
- package/dist/src/core/components/CallLayout/PaginatedGridLayout.js +5 -3
- package/dist/src/core/components/CallLayout/PaginatedGridLayout.js.map +1 -1
- package/dist/src/core/components/CallLayout/SpeakerLayout.d.ts +7 -2
- package/dist/src/core/components/CallLayout/SpeakerLayout.js +32 -37
- package/dist/src/core/components/CallLayout/SpeakerLayout.js.map +1 -1
- package/dist/src/core/components/CallLayout/hooks.d.ts +3 -0
- package/dist/src/core/components/CallLayout/hooks.js +41 -0
- package/dist/src/core/components/CallLayout/hooks.js.map +1 -0
- package/dist/src/core/components/CallLayout/index.d.ts +1 -0
- package/dist/src/core/components/CallLayout/index.js +1 -0
- package/dist/src/core/components/CallLayout/index.js.map +1 -1
- package/dist/src/core/hooks/useCalculateHardLimit.d.ts +4 -0
- package/dist/src/core/hooks/useCalculateHardLimit.js +56 -0
- package/dist/src/core/hooks/useCalculateHardLimit.js.map +1 -0
- package/dist/src/translations/en.json +1 -0
- package/dist/src/translations/index.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +2 -1
- package/src/core/components/Audio/ParticipantsAudio.tsx +35 -0
- package/src/core/components/Audio/index.ts +1 -0
- package/src/core/components/CallLayout/LivestreamLayout.tsx +231 -0
- package/src/core/components/CallLayout/PaginatedGridLayout.tsx +33 -36
- package/src/core/components/CallLayout/SpeakerLayout.tsx +74 -56
- package/src/core/components/CallLayout/hooks.ts +54 -0
- package/src/core/components/CallLayout/index.ts +1 -0
- package/src/core/hooks/useCalculateHardLimit.ts +72 -0
- package/src/translations/en.json +2 -0
|
@@ -1,48 +1,50 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { SfuModels } from '@stream-io/video-client';
|
|
4
5
|
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
5
6
|
import { DefaultParticipantViewUI, ParticipantView, } from '../ParticipantView';
|
|
6
7
|
import { IconButton } from '../../../components';
|
|
7
8
|
import { useHorizontalScrollPosition, useVerticalScrollPosition, } from '../../../hooks';
|
|
8
|
-
import
|
|
9
|
+
import { useSpeakerLayoutSortPreset } from './hooks';
|
|
10
|
+
import { useCalculateHardLimit } from '../../hooks/useCalculateHardLimit';
|
|
11
|
+
import { ParticipantsAudio } from '../Audio';
|
|
9
12
|
const DefaultParticipantViewUIBar = () => (_jsx(DefaultParticipantViewUI, { menuPlacement: "top-end" }));
|
|
10
13
|
const DefaultParticipantViewUISpotlight = () => _jsx(DefaultParticipantViewUI, {});
|
|
11
|
-
export const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUISpotlight, VideoPlaceholder, participantsBarPosition = 'bottom', }) => {
|
|
14
|
+
export const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUISpotlight, VideoPlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, }) => {
|
|
12
15
|
const call = useCall();
|
|
13
|
-
const { useParticipants } = useCallStateHooks();
|
|
16
|
+
const { useParticipants, useRemoteParticipants } = useCallStateHooks();
|
|
14
17
|
const [participantInSpotlight, ...otherParticipants] = useParticipants();
|
|
15
|
-
const
|
|
16
|
-
const
|
|
18
|
+
const remoteParticipants = useRemoteParticipants();
|
|
19
|
+
const [participantsBarWrapperElement, setParticipantsBarWrapperElement] = useState(null);
|
|
20
|
+
const [participantsBarElement, setParticipantsBarElement] = useState(null);
|
|
21
|
+
const [buttonsWrapperElement, setButtonsWrapperElement] = useState(null);
|
|
22
|
+
const isSpeakerScreenSharing = hasScreenShare(participantInSpotlight);
|
|
23
|
+
const hardLimit = useCalculateHardLimit(buttonsWrapperElement, participantsBarElement, participantsBarLimit);
|
|
24
|
+
const isVertical = participantsBarPosition === 'left' || participantsBarPosition === 'right';
|
|
25
|
+
const isHorizontal = participantsBarPosition === 'top' || participantsBarPosition === 'bottom';
|
|
17
26
|
useEffect(() => {
|
|
18
|
-
if (!
|
|
27
|
+
if (!participantsBarWrapperElement || !call)
|
|
19
28
|
return;
|
|
20
|
-
const cleanup = call.setViewport(
|
|
29
|
+
const cleanup = call.setViewport(participantsBarWrapperElement);
|
|
21
30
|
return () => cleanup();
|
|
22
|
-
}, [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const callConfig = CallTypes.get(call.type);
|
|
36
|
-
call.setSortParticipantsBy(callConfig.options.sortParticipantsBy || defaultSortPreset);
|
|
37
|
-
};
|
|
38
|
-
}, [call, isOneOnOneCall]);
|
|
31
|
+
}, [participantsBarWrapperElement, call]);
|
|
32
|
+
const isOneOnOneCall = otherParticipants.length === 1;
|
|
33
|
+
useSpeakerLayoutSortPreset(call, isOneOnOneCall);
|
|
34
|
+
let participantsWithAppliedLimit = otherParticipants;
|
|
35
|
+
if (typeof participantsBarLimit !== 'undefined') {
|
|
36
|
+
const hardLimitToApply = isVertical
|
|
37
|
+
? hardLimit.vertical
|
|
38
|
+
: hardLimit.horizontal;
|
|
39
|
+
participantsWithAppliedLimit = otherParticipants.slice(0,
|
|
40
|
+
// subtract 1 if speaker is sharing screen as
|
|
41
|
+
// that one is rendered independently from otherParticipants array
|
|
42
|
+
hardLimitToApply - (isSpeakerScreenSharing ? 1 : 0));
|
|
43
|
+
}
|
|
39
44
|
if (!call)
|
|
40
45
|
return null;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`str-video__speaker-layout--variant-${participantsBarPosition}`) }, { children: [_jsx("div", Object.assign({ className: "str-video__speaker-layout__spotlight" }, { children: participantInSpotlight && (_jsx(ParticipantView, { participant: participantInSpotlight, muteAudio: isSpeakerScreenSharing, trackType: isSpeakerScreenSharing ? 'screenShareTrack' : 'videoTrack', ParticipantViewUI: ParticipantViewUISpotlight, VideoPlaceholder: VideoPlaceholder })) })), otherParticipants.length > 0 && participantsBarPosition && (_jsxs("div", Object.assign({ className: "str-video__speaker-layout__participants-bar-buttons-wrapper" }, { children: [_jsx("div", Object.assign({ className: "str-video__speaker-layout__participants-bar-wrapper", ref: setScrollWrapper }, { children: _jsxs("div", Object.assign({ className: "str-video__speaker-layout__participants-bar" }, { children: [isSpeakerScreenSharing && (_jsx("div", Object.assign({ className: "str-video__speaker-layout__participant-tile" }, { children: _jsx(ParticipantView, { participant: participantInSpotlight, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder }) }), participantInSpotlight.sessionId)), otherParticipants.map((participant) => (_jsx("div", Object.assign({ className: "str-video__speaker-layout__participant-tile" }, { children: _jsx(ParticipantView, { participant: participant, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder }) }), participant.sessionId)))] })) })), (participantsBarPosition === 'left' ||
|
|
44
|
-
participantsBarPosition === 'right') && (_jsx(VerticalScrollButtons, { scrollWrapper: scrollWrapper })), (participantsBarPosition === 'top' ||
|
|
45
|
-
participantsBarPosition === 'bottom') && (_jsx(HorizontalScrollButtons, { scrollWrapper: scrollWrapper }))] })))] })) })));
|
|
46
|
+
return (_jsxs("div", Object.assign({ className: "str-video__speaker-layout__wrapper" }, { children: [_jsx(ParticipantsAudio, { participants: remoteParticipants }), _jsxs("div", Object.assign({ className: clsx('str-video__speaker-layout', participantsBarPosition &&
|
|
47
|
+
`str-video__speaker-layout--variant-${participantsBarPosition}`) }, { children: [_jsx("div", Object.assign({ className: "str-video__speaker-layout__spotlight" }, { children: participantInSpotlight && (_jsx(ParticipantView, { participant: participantInSpotlight, muteAudio: true, trackType: isSpeakerScreenSharing ? 'screenShareTrack' : 'videoTrack', ParticipantViewUI: ParticipantViewUISpotlight, VideoPlaceholder: VideoPlaceholder })) })), participantsWithAppliedLimit.length > 0 && participantsBarPosition && (_jsxs("div", Object.assign({ ref: setButtonsWrapperElement, className: "str-video__speaker-layout__participants-bar-buttons-wrapper" }, { children: [_jsx("div", Object.assign({ className: "str-video__speaker-layout__participants-bar-wrapper", ref: setParticipantsBarWrapperElement }, { children: _jsxs("div", Object.assign({ ref: setParticipantsBarElement, className: "str-video__speaker-layout__participants-bar" }, { children: [isSpeakerScreenSharing && (_jsx("div", Object.assign({ className: "str-video__speaker-layout__participant-tile" }, { children: _jsx(ParticipantView, { participant: participantInSpotlight, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder, muteAudio: true }) }), participantInSpotlight.sessionId)), participantsWithAppliedLimit.map((participant) => (_jsx("div", Object.assign({ className: "str-video__speaker-layout__participant-tile" }, { children: _jsx(ParticipantView, { participant: participant, ParticipantViewUI: ParticipantViewUIBar, VideoPlaceholder: VideoPlaceholder, muteAudio: true }) }), participant.sessionId)))] })) })), isVertical && (_jsx(VerticalScrollButtons, { scrollWrapper: participantsBarWrapperElement })), isHorizontal && (_jsx(HorizontalScrollButtons, { scrollWrapper: participantsBarWrapperElement }))] })))] }))] })));
|
|
46
48
|
};
|
|
47
49
|
const HorizontalScrollButtons = ({ scrollWrapper, }) => {
|
|
48
50
|
const scrollPosition = useHorizontalScrollPosition(scrollWrapper);
|
|
@@ -65,11 +67,4 @@ const VerticalScrollButtons = ({ scrollWrapper, }) => {
|
|
|
65
67
|
return (_jsxs(_Fragment, { children: [scrollPosition && scrollPosition !== 'top' && (_jsx(IconButton, { onClick: scrollTopClickHandler, icon: "caret-up", className: "str-video__speaker-layout__participants-bar--button-top" })), scrollPosition && scrollPosition !== 'bottom' && (_jsx(IconButton, { onClick: scrollBottomClickHandler, icon: "caret-down", className: "str-video__speaker-layout__participants-bar--button-bottom" }))] }));
|
|
66
68
|
};
|
|
67
69
|
const hasScreenShare = (p) => !!(p === null || p === void 0 ? void 0 : p.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE));
|
|
68
|
-
const loggedIn = (a, b) => {
|
|
69
|
-
if (a.isLocalParticipant)
|
|
70
|
-
return 1;
|
|
71
|
-
if (b.isLocalParticipant)
|
|
72
|
-
return -1;
|
|
73
|
-
return 0;
|
|
74
|
-
};
|
|
75
70
|
//# sourceMappingURL=SpeakerLayout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpeakerLayout.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/SpeakerLayout.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SpeakerLayout.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/SpeakerLayout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { CallTypes, combineComparators, defaultSortPreset, paginatedLayoutSortPreset, screenSharing, speakerLayoutSortPreset, } from '@stream-io/video-client';
|
|
3
|
+
export const usePaginatedLayoutSortPreset = (call) => {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (!call)
|
|
6
|
+
return;
|
|
7
|
+
call.setSortParticipantsBy(paginatedLayoutSortPreset);
|
|
8
|
+
return () => {
|
|
9
|
+
resetSortPreset(call);
|
|
10
|
+
};
|
|
11
|
+
}, [call]);
|
|
12
|
+
};
|
|
13
|
+
export const useSpeakerLayoutSortPreset = (call, isOneOnOneCall) => {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!call)
|
|
16
|
+
return;
|
|
17
|
+
// always show the remote participant in the spotlight
|
|
18
|
+
if (isOneOnOneCall) {
|
|
19
|
+
call.setSortParticipantsBy(combineComparators(screenSharing, loggedIn));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
call.setSortParticipantsBy(speakerLayoutSortPreset);
|
|
23
|
+
}
|
|
24
|
+
return () => {
|
|
25
|
+
resetSortPreset(call);
|
|
26
|
+
};
|
|
27
|
+
}, [call, isOneOnOneCall]);
|
|
28
|
+
};
|
|
29
|
+
const resetSortPreset = (call) => {
|
|
30
|
+
// reset the sorting to the default for the call type
|
|
31
|
+
const callConfig = CallTypes.get(call.type);
|
|
32
|
+
call.setSortParticipantsBy(callConfig.options.sortParticipantsBy || defaultSortPreset);
|
|
33
|
+
};
|
|
34
|
+
const loggedIn = (a, b) => {
|
|
35
|
+
if (a.isLocalParticipant)
|
|
36
|
+
return 1;
|
|
37
|
+
if (b.isLocalParticipant)
|
|
38
|
+
return -1;
|
|
39
|
+
return 0;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAEL,SAAS,EACT,kBAAkB,EAElB,iBAAiB,EACjB,yBAAyB,EACzB,aAAa,EACb,uBAAuB,GAExB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,IAAsB,EAAE,EAAE;IACrE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,IAAI,CAAC,qBAAqB,CAAC,yBAAyB,CAAC,CAAC;QACtD,OAAO,GAAG,EAAE;YACV,eAAe,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,IAAsB,EACtB,cAAuB,EACvB,EAAE;IACF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,sDAAsD;QACtD,IAAI,cAAc,EAAE;YAClB,IAAI,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;SACzE;aAAM;YACL,IAAI,CAAC,qBAAqB,CAAC,uBAAuB,CAAC,CAAC;SACrD;QACD,OAAO,GAAG,EAAE;YACV,eAAe,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,IAAU,EAAE,EAAE;IACrC,qDAAqD;IACrD,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,qBAAqB,CACxB,UAAU,CAAC,OAAO,CAAC,kBAAkB,IAAI,iBAAiB,CAC3D,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAuC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;IAC5D,IAAI,CAAC,CAAC,kBAAkB;QAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,kBAAkB;QAAE,OAAO,CAAC,CAAC,CAAC;IACpC,OAAO,CAAC,CAAC;AACX,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export const useCalculateHardLimit = (
|
|
3
|
+
/**
|
|
4
|
+
* Element that stretches to 100% of the whole layout component
|
|
5
|
+
*/
|
|
6
|
+
wrapperElement,
|
|
7
|
+
/**
|
|
8
|
+
* Element that directly hosts individual `ParticipantView` (or wrapper) elements
|
|
9
|
+
*/
|
|
10
|
+
hostElement, limit) => {
|
|
11
|
+
const [calculatedLimit, setCalculatedLimit] = useState({
|
|
12
|
+
vertical: typeof limit === 'number' ? limit : 1,
|
|
13
|
+
horizontal: typeof limit === 'number' ? limit : 1,
|
|
14
|
+
});
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!hostElement ||
|
|
17
|
+
!wrapperElement ||
|
|
18
|
+
typeof limit === 'number' ||
|
|
19
|
+
typeof limit === 'undefined')
|
|
20
|
+
return;
|
|
21
|
+
let childWidth = null;
|
|
22
|
+
let childHeight = null;
|
|
23
|
+
const resizeObserver = new ResizeObserver((entries, observer) => {
|
|
24
|
+
// this part should ideally run as little times as possible
|
|
25
|
+
// get child measurements and disconnect
|
|
26
|
+
// does not consider dynamically sized children
|
|
27
|
+
// this hook is for SpeakerLayout use only, where children in the bar are fixed size
|
|
28
|
+
if (entries.length > 1) {
|
|
29
|
+
const child = hostElement.firstChild;
|
|
30
|
+
if (child) {
|
|
31
|
+
childHeight = child.clientHeight;
|
|
32
|
+
childWidth = child.clientWidth;
|
|
33
|
+
observer.unobserve(hostElement);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// keep the state at { vertical: 1, horizontal: 1 }
|
|
37
|
+
// until we get the proper child measurements
|
|
38
|
+
if (childHeight === null || childWidth === null)
|
|
39
|
+
return;
|
|
40
|
+
const vertical = Math.floor(wrapperElement.clientHeight / childHeight);
|
|
41
|
+
const horizontal = Math.floor(wrapperElement.clientWidth / childWidth);
|
|
42
|
+
setCalculatedLimit((pv) => {
|
|
43
|
+
if (pv.vertical !== vertical || pv.horizontal !== horizontal)
|
|
44
|
+
return { vertical, horizontal };
|
|
45
|
+
return pv;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
resizeObserver.observe(wrapperElement);
|
|
49
|
+
resizeObserver.observe(hostElement);
|
|
50
|
+
return () => {
|
|
51
|
+
resizeObserver.disconnect();
|
|
52
|
+
};
|
|
53
|
+
}, [hostElement, limit, wrapperElement]);
|
|
54
|
+
return calculatedLimit;
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=useCalculateHardLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCalculateHardLimit.js","sourceRoot":"","sources":["../../../../src/core/hooks/useCalculateHardLimit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,CAAC,MAAM,qBAAqB,GAAG;AACnC;;GAEG;AACH,cAAqC;AACrC;;GAEG;AACH,WAAkC,EAClC,KAA0B,EAC1B,EAAE;IACF,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAGnD;QACD,QAAQ,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/C,UAAU,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KAClD,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IACE,CAAC,WAAW;YACZ,CAAC,cAAc;YACf,OAAO,KAAK,KAAK,QAAQ;YACzB,OAAO,KAAK,KAAK,WAAW;YAE5B,OAAO;QAET,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,IAAI,WAAW,GAAkB,IAAI,CAAC;QAEtC,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YAC9D,2DAA2D;YAC3D,wCAAwC;YACxC,+CAA+C;YAC/C,oFAAoF;YACpF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;gBACtB,MAAM,KAAK,GAAG,WAAW,CAAC,UAAgC,CAAC;gBAE3D,IAAI,KAAK,EAAE;oBACT,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC;oBACjC,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC;oBAC/B,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;iBACjC;aACF;YAED,mDAAmD;YACnD,6CAA6C;YAC7C,IAAI,WAAW,KAAK,IAAI,IAAI,UAAU,KAAK,IAAI;gBAAE,OAAO;YAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,GAAG,WAAW,CAAC,CAAC;YACvE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,GAAG,UAAU,CAAC,CAAC;YAEvE,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE;gBACxB,IAAI,EAAE,CAAC,QAAQ,KAAK,QAAQ,IAAI,EAAE,CAAC,UAAU,KAAK,UAAU;oBAC1D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;gBAClC,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACvC,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAEpC,OAAO,GAAG,EAAE;YACV,cAAc,CAAC,UAAU,EAAE,CAAC;QAC9B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;IAEzC,OAAO,eAAe,CAAC;AACzB,CAAC,CAAC"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Speakers": "Speakers",
|
|
12
12
|
"Video": "Video",
|
|
13
13
|
"You are muted. Unmute to speak.": "You are muted. Unmute to speak.",
|
|
14
|
+
"Live": "Live",
|
|
14
15
|
"You can now speak.": "You can now speak.",
|
|
15
16
|
"Awaiting for an approval to speak.": "Awaiting for an approval to speak.",
|
|
16
17
|
"You can no longer speak.": "You can no longer speak.",
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.3.
|
|
1
|
+
export declare const version = "0.3.30";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '0.3.
|
|
1
|
+
export const version = '0.3.30';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.30",
|
|
4
4
|
"packageManager": "yarn@3.2.4",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"@nivo/core": "^0.80.0",
|
|
34
34
|
"@nivo/line": "^0.80.0",
|
|
35
35
|
"@stream-io/i18n": "^0.1.2",
|
|
36
|
-
"@stream-io/video-client": "^0.3.
|
|
37
|
-
"@stream-io/video-react-bindings": "^0.2.
|
|
36
|
+
"@stream-io/video-client": "^0.3.25",
|
|
37
|
+
"@stream-io/video-react-bindings": "^0.2.26",
|
|
38
38
|
"clsx": "^1.2.1",
|
|
39
39
|
"prop-types": "^15.8.1",
|
|
40
40
|
"rxjs": "~7.8.1"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"react-dom": "^18.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@stream-io/video-styling": "^0.1.
|
|
47
|
+
"@stream-io/video-styling": "^0.1.10",
|
|
48
48
|
"@types/prop-types": "^15.7.5",
|
|
49
49
|
"react": "^18.2.0",
|
|
50
50
|
"react-dom": "^18.2.0",
|
|
@@ -211,11 +211,12 @@ export const ParticipantActionsContextMenu = ({
|
|
|
211
211
|
};
|
|
212
212
|
|
|
213
213
|
const toggleFullscreenMode = () => {
|
|
214
|
-
if (!fullscreenModeOn)
|
|
214
|
+
if (!fullscreenModeOn) {
|
|
215
215
|
return participantViewElement
|
|
216
216
|
?.requestFullscreen()
|
|
217
217
|
.then(() => setFullscreenModeOn(true))
|
|
218
218
|
.catch(console.error);
|
|
219
|
+
}
|
|
219
220
|
|
|
220
221
|
document
|
|
221
222
|
.exitFullscreen()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ComponentProps } from 'react';
|
|
2
|
+
import { SfuModels, StreamVideoParticipant } from '@stream-io/video-client';
|
|
3
|
+
import { Audio } from './Audio';
|
|
4
|
+
|
|
5
|
+
export type ParticipantsAudioProps = {
|
|
6
|
+
/**
|
|
7
|
+
* The participants to play audio for.
|
|
8
|
+
*/
|
|
9
|
+
participants: StreamVideoParticipant[];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props to pass to the underlying `Audio` components.
|
|
13
|
+
*/
|
|
14
|
+
audioProps?: ComponentProps<typeof Audio>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ParticipantsAudio = (props: ParticipantsAudioProps) => {
|
|
18
|
+
const { participants, audioProps } = props;
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{participants.map(
|
|
22
|
+
(participant) =>
|
|
23
|
+
!participant.isLocalParticipant &&
|
|
24
|
+
participant.publishedTracks.includes(SfuModels.TrackType.AUDIO) &&
|
|
25
|
+
participant.audioStream && (
|
|
26
|
+
<Audio
|
|
27
|
+
{...audioProps}
|
|
28
|
+
participant={participant}
|
|
29
|
+
key={participant.sessionId}
|
|
30
|
+
/>
|
|
31
|
+
),
|
|
32
|
+
)}
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
useCall,
|
|
5
|
+
useCallStateHooks,
|
|
6
|
+
useI18n,
|
|
7
|
+
} from '@stream-io/video-react-bindings';
|
|
8
|
+
import { SfuModels, StreamVideoParticipant } from '@stream-io/video-client';
|
|
9
|
+
import { ParticipantView, useParticipantViewContext } from '../ParticipantView';
|
|
10
|
+
import { ParticipantsAudio } from '../Audio';
|
|
11
|
+
import { usePaginatedLayoutSortPreset } from './hooks';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The props for the {@link LivestreamLayout} component.
|
|
15
|
+
*/
|
|
16
|
+
export type LivestreamLayoutProps = {
|
|
17
|
+
/**
|
|
18
|
+
* Whether the livestream is muted. Defaults to `false`.
|
|
19
|
+
*/
|
|
20
|
+
muted?: boolean;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether to show the participant count. Defaults to `true`.
|
|
24
|
+
*/
|
|
25
|
+
showParticipantCount?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether to enable fullscreen mode. Defaults to `true`.
|
|
29
|
+
*/
|
|
30
|
+
enableFullScreen?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether to show the duration of the call. Defaults to `true`.
|
|
34
|
+
*/
|
|
35
|
+
showDuration?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether to show the live badge. Defaults to `true`.
|
|
39
|
+
*/
|
|
40
|
+
showLiveBadge?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether to show the speaker name. Defaults to `false`.
|
|
44
|
+
*/
|
|
45
|
+
showSpeakerName?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The props to pass to the floating participant element.
|
|
49
|
+
*/
|
|
50
|
+
floatingParticipantProps?: LivestreamLayoutProps & {
|
|
51
|
+
/**
|
|
52
|
+
* The position of the floating participant element. Defaults to `top-right`.
|
|
53
|
+
*/
|
|
54
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const LivestreamLayout = (props: LivestreamLayoutProps) => {
|
|
59
|
+
const { useParticipants, useRemoteParticipants, useHasOngoingScreenShare } =
|
|
60
|
+
useCallStateHooks();
|
|
61
|
+
const call = useCall();
|
|
62
|
+
const [currentSpeaker, ...otherParticipants] = useParticipants();
|
|
63
|
+
const remoteParticipants = useRemoteParticipants();
|
|
64
|
+
const hasOngoingScreenShare = useHasOngoingScreenShare();
|
|
65
|
+
const presenter = hasOngoingScreenShare
|
|
66
|
+
? hasScreenShare(currentSpeaker) && currentSpeaker
|
|
67
|
+
: otherParticipants.find(hasScreenShare);
|
|
68
|
+
|
|
69
|
+
usePaginatedLayoutSortPreset(call);
|
|
70
|
+
|
|
71
|
+
const Overlay = (
|
|
72
|
+
<ParticipantOverlay
|
|
73
|
+
showParticipantCount={props.showParticipantCount}
|
|
74
|
+
showDuration={props.showDuration}
|
|
75
|
+
showLiveBadge={props.showLiveBadge}
|
|
76
|
+
showSpeakerName={props.showSpeakerName}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const { floatingParticipantProps } = props;
|
|
81
|
+
const FloatingParticipantOverlay = hasOngoingScreenShare && (
|
|
82
|
+
<ParticipantOverlay
|
|
83
|
+
// these elements aren't needed for the video feed
|
|
84
|
+
showParticipantCount={
|
|
85
|
+
floatingParticipantProps?.showParticipantCount ?? false
|
|
86
|
+
}
|
|
87
|
+
showDuration={floatingParticipantProps?.showDuration ?? false}
|
|
88
|
+
showLiveBadge={floatingParticipantProps?.showLiveBadge ?? false}
|
|
89
|
+
showSpeakerName={floatingParticipantProps?.showSpeakerName ?? true}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="str-video__livestream-layout__wrapper">
|
|
95
|
+
<ParticipantsAudio participants={remoteParticipants} />
|
|
96
|
+
{hasOngoingScreenShare && presenter && (
|
|
97
|
+
<ParticipantView
|
|
98
|
+
className="str-video__livestream-layout__screen-share"
|
|
99
|
+
participant={presenter}
|
|
100
|
+
ParticipantViewUI={Overlay}
|
|
101
|
+
trackType="screenShareTrack"
|
|
102
|
+
muteAudio // audio is rendered by ParticipantsAudio
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
<ParticipantView
|
|
106
|
+
className={clsx(
|
|
107
|
+
hasOngoingScreenShare &&
|
|
108
|
+
'str-video__livestream-layout__floating-participant',
|
|
109
|
+
(hasOngoingScreenShare &&
|
|
110
|
+
floatingParticipantProps?.position &&
|
|
111
|
+
`str-video__livestream-layout__floating-participant--${floatingParticipantProps.position}`) ??
|
|
112
|
+
'str-video__livestream-layout__floating-participant--top-right',
|
|
113
|
+
)}
|
|
114
|
+
participant={currentSpeaker}
|
|
115
|
+
ParticipantViewUI={FloatingParticipantOverlay || Overlay}
|
|
116
|
+
muteAudio // audio is rendered by ParticipantsAudio
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const hasScreenShare = (p?: StreamVideoParticipant) =>
|
|
123
|
+
!!p?.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE);
|
|
124
|
+
|
|
125
|
+
const ParticipantOverlay = (props: {
|
|
126
|
+
enableFullScreen?: boolean;
|
|
127
|
+
showParticipantCount?: boolean;
|
|
128
|
+
showDuration?: boolean;
|
|
129
|
+
showLiveBadge?: boolean;
|
|
130
|
+
showSpeakerName?: boolean;
|
|
131
|
+
}) => {
|
|
132
|
+
const {
|
|
133
|
+
enableFullScreen = true,
|
|
134
|
+
showParticipantCount = true,
|
|
135
|
+
showDuration = true,
|
|
136
|
+
showLiveBadge = true,
|
|
137
|
+
showSpeakerName = false,
|
|
138
|
+
} = props;
|
|
139
|
+
const { participant } = useParticipantViewContext();
|
|
140
|
+
const { useParticipantCount } = useCallStateHooks();
|
|
141
|
+
const participantCount = useParticipantCount();
|
|
142
|
+
const duration = useUpdateCallDuration();
|
|
143
|
+
const toggleFullScreen = useToggleFullScreen();
|
|
144
|
+
const { t } = useI18n();
|
|
145
|
+
return (
|
|
146
|
+
<div className="str-video__livestream-layout__overlay">
|
|
147
|
+
<div className="str-video__livestream-layout__overlay__bar">
|
|
148
|
+
{showLiveBadge && (
|
|
149
|
+
<span className="str-video__livestream-layout__live-badge">
|
|
150
|
+
{t('Live')}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
{showParticipantCount && (
|
|
154
|
+
<span className="str-video__livestream-layout__viewers-count">
|
|
155
|
+
{participantCount}
|
|
156
|
+
</span>
|
|
157
|
+
)}
|
|
158
|
+
{showSpeakerName && (
|
|
159
|
+
<span
|
|
160
|
+
className="str-video__livestream-layout__speaker-name"
|
|
161
|
+
title={participant.name || participant.userId || ''}
|
|
162
|
+
>
|
|
163
|
+
{participant.name || participant.userId || ''}
|
|
164
|
+
</span>
|
|
165
|
+
)}
|
|
166
|
+
{showDuration && (
|
|
167
|
+
<span className="str-video__livestream-layout__duration">
|
|
168
|
+
{formatDuration(duration)}
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
{enableFullScreen && (
|
|
172
|
+
<span
|
|
173
|
+
className="str-video__livestream-layout__go-fullscreen"
|
|
174
|
+
onClick={toggleFullScreen}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const useUpdateCallDuration = () => {
|
|
183
|
+
const { useIsCallLive, useCallSession } = useCallStateHooks();
|
|
184
|
+
const isCallLive = useIsCallLive();
|
|
185
|
+
const session = useCallSession();
|
|
186
|
+
const [duration, setDuration] = useState(() => {
|
|
187
|
+
if (!session || !session.live_started_at) return 0;
|
|
188
|
+
const liveStartTime = new Date(session.live_started_at);
|
|
189
|
+
const now = new Date();
|
|
190
|
+
return Math.floor((now.getTime() - liveStartTime.getTime()) / 1000);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!isCallLive) return;
|
|
195
|
+
const interval = setInterval(() => {
|
|
196
|
+
setDuration((d) => d + 1);
|
|
197
|
+
}, 1000);
|
|
198
|
+
return () => {
|
|
199
|
+
clearInterval(interval);
|
|
200
|
+
};
|
|
201
|
+
}, [isCallLive]);
|
|
202
|
+
|
|
203
|
+
return duration;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const useToggleFullScreen = () => {
|
|
207
|
+
const { participantViewElement } = useParticipantViewContext();
|
|
208
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
209
|
+
return useCallback(() => {
|
|
210
|
+
if (isFullscreen) {
|
|
211
|
+
document.exitFullscreen().then(() => {
|
|
212
|
+
setIsFullscreen(false);
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
participantViewElement?.requestFullscreen().then(() => {
|
|
216
|
+
setIsFullscreen(true);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}, [isFullscreen, participantViewElement]);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const formatDuration = (durationInMs: number) => {
|
|
223
|
+
const days = Math.floor(durationInMs / 86400);
|
|
224
|
+
const hours = Math.floor(durationInMs / 3600);
|
|
225
|
+
const minutes = Math.floor((durationInMs % 3600) / 60);
|
|
226
|
+
const seconds = durationInMs % 60;
|
|
227
|
+
|
|
228
|
+
return `${days ? days + ' ' : ''}${hours ? hours + ':' : ''}${
|
|
229
|
+
minutes < 10 ? '0' : ''
|
|
230
|
+
}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
231
|
+
};
|