@stream-io/video-react-sdk 0.3.29 → 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.
Files changed (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -5
  3. package/dist/css/styles.css +100 -0
  4. package/dist/css/styles.css.map +1 -1
  5. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js +2 -1
  6. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js.map +1 -1
  7. package/dist/src/core/components/Audio/ParticipantsAudio.d.ts +14 -0
  8. package/dist/src/core/components/Audio/ParticipantsAudio.js +11 -0
  9. package/dist/src/core/components/Audio/ParticipantsAudio.js.map +1 -0
  10. package/dist/src/core/components/Audio/index.d.ts +1 -0
  11. package/dist/src/core/components/Audio/index.js +1 -0
  12. package/dist/src/core/components/Audio/index.js.map +1 -1
  13. package/dist/src/core/components/CallLayout/LivestreamLayout.d.ts +39 -0
  14. package/dist/src/core/components/CallLayout/LivestreamLayout.js +91 -0
  15. package/dist/src/core/components/CallLayout/LivestreamLayout.js.map +1 -0
  16. package/dist/src/core/components/CallLayout/PaginatedGridLayout.js +4 -2
  17. package/dist/src/core/components/CallLayout/PaginatedGridLayout.js.map +1 -1
  18. package/dist/src/core/components/CallLayout/SpeakerLayout.d.ts +1 -1
  19. package/dist/src/core/components/CallLayout/SpeakerLayout.js +7 -28
  20. package/dist/src/core/components/CallLayout/SpeakerLayout.js.map +1 -1
  21. package/dist/src/core/components/CallLayout/hooks.d.ts +3 -0
  22. package/dist/src/core/components/CallLayout/hooks.js +41 -0
  23. package/dist/src/core/components/CallLayout/hooks.js.map +1 -0
  24. package/dist/src/core/components/CallLayout/index.d.ts +1 -0
  25. package/dist/src/core/components/CallLayout/index.js +1 -0
  26. package/dist/src/core/components/CallLayout/index.js.map +1 -1
  27. package/dist/src/translations/en.json +1 -0
  28. package/dist/src/translations/index.d.ts +1 -0
  29. package/dist/version.d.ts +1 -1
  30. package/dist/version.js +1 -1
  31. package/package.json +4 -4
  32. package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +2 -1
  33. package/src/core/components/Audio/ParticipantsAudio.tsx +35 -0
  34. package/src/core/components/Audio/index.ts +1 -0
  35. package/src/core/components/CallLayout/LivestreamLayout.tsx +231 -0
  36. package/src/core/components/CallLayout/PaginatedGridLayout.tsx +5 -4
  37. package/src/core/components/CallLayout/SpeakerLayout.tsx +8 -40
  38. package/src/core/components/CallLayout/hooks.ts +54 -0
  39. package/src/core/components/CallLayout/index.ts +1 -0
  40. package/src/translations/en.json +2 -0
@@ -1,13 +1,14 @@
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 { CallTypes, combineComparators, defaultSortPreset, screenSharing, SfuModels, speakerLayoutSortPreset, } from '@stream-io/video-client';
4
- import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
5
3
  import clsx from 'clsx';
4
+ import { SfuModels } from '@stream-io/video-client';
5
+ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
6
6
  import { DefaultParticipantViewUI, ParticipantView, } from '../ParticipantView';
7
7
  import { IconButton } from '../../../components';
8
8
  import { useHorizontalScrollPosition, useVerticalScrollPosition, } from '../../../hooks';
9
+ import { useSpeakerLayoutSortPreset } from './hooks';
9
10
  import { useCalculateHardLimit } from '../../hooks/useCalculateHardLimit';
10
- import { Audio } from '../Audio';
11
+ import { ParticipantsAudio } from '../Audio';
11
12
  const DefaultParticipantViewUIBar = () => (_jsx(DefaultParticipantViewUI, { menuPlacement: "top-end" }));
12
13
  const DefaultParticipantViewUISpotlight = () => _jsx(DefaultParticipantViewUI, {});
13
14
  export const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, ParticipantViewUISpotlight = DefaultParticipantViewUISpotlight, VideoPlaceholder, participantsBarPosition = 'bottom', participantsBarLimit, }) => {
@@ -22,29 +23,14 @@ export const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIB
22
23
  const hardLimit = useCalculateHardLimit(buttonsWrapperElement, participantsBarElement, participantsBarLimit);
23
24
  const isVertical = participantsBarPosition === 'left' || participantsBarPosition === 'right';
24
25
  const isHorizontal = participantsBarPosition === 'top' || participantsBarPosition === 'bottom';
25
- const isOneOnOneCall = otherParticipants.length === 1;
26
26
  useEffect(() => {
27
27
  if (!participantsBarWrapperElement || !call)
28
28
  return;
29
29
  const cleanup = call.setViewport(participantsBarWrapperElement);
30
30
  return () => cleanup();
31
31
  }, [participantsBarWrapperElement, call]);
32
- useEffect(() => {
33
- if (!call)
34
- return;
35
- // always show the remote participant in the spotlight
36
- if (isOneOnOneCall) {
37
- call.setSortParticipantsBy(combineComparators(screenSharing, loggedIn));
38
- }
39
- else {
40
- call.setSortParticipantsBy(speakerLayoutSortPreset);
41
- }
42
- return () => {
43
- // reset the sorting to the default for the call type
44
- const callConfig = CallTypes.get(call.type);
45
- call.setSortParticipantsBy(callConfig.options.sortParticipantsBy || defaultSortPreset);
46
- };
47
- }, [call, isOneOnOneCall]);
32
+ const isOneOnOneCall = otherParticipants.length === 1;
33
+ useSpeakerLayoutSortPreset(call, isOneOnOneCall);
48
34
  let participantsWithAppliedLimit = otherParticipants;
49
35
  if (typeof participantsBarLimit !== 'undefined') {
50
36
  const hardLimitToApply = isVertical
@@ -57,7 +43,7 @@ export const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIB
57
43
  }
58
44
  if (!call)
59
45
  return null;
60
- return (_jsxs("div", Object.assign({ className: "str-video__speaker-layout__wrapper" }, { children: [remoteParticipants.map((participant) => (_jsx(Audio, { participant: participant }, participant.sessionId))), _jsxs("div", Object.assign({ className: clsx('str-video__speaker-layout', participantsBarPosition &&
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 &&
61
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 }))] })))] }))] })));
62
48
  };
63
49
  const HorizontalScrollButtons = ({ scrollWrapper, }) => {
@@ -81,11 +67,4 @@ const VerticalScrollButtons = ({ scrollWrapper, }) => {
81
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" }))] }));
82
68
  };
83
69
  const hasScreenShare = (p) => !!(p === null || p === void 0 ? void 0 : p.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE));
84
- const loggedIn = (a, b) => {
85
- if (a.isLocalParticipant)
86
- return 1;
87
- if (b.isLocalParticipant)
88
- return -1;
89
- return 0;
90
- };
91
70
  //# sourceMappingURL=SpeakerLayout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpeakerLayout.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/SpeakerLayout.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SpeakerLayout.js","sourceRoot":"","sources":["../../../../../src/core/components/CallLayout/SpeakerLayout.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Call } from '@stream-io/video-client';
2
+ export declare const usePaginatedLayoutSortPreset: (call: Call | undefined) => void;
3
+ export declare const useSpeakerLayoutSortPreset: (call: Call | undefined, isOneOnOneCall: boolean) => void;
@@ -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,2 +1,3 @@
1
+ export * from './LivestreamLayout';
1
2
  export * from './PaginatedGridLayout';
2
3
  export * from './SpeakerLayout';
@@ -1,3 +1,4 @@
1
+ export * from './LivestreamLayout';
1
2
  export * from './PaginatedGridLayout';
2
3
  export * from './SpeakerLayout';
3
4
  //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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.",
@@ -12,6 +12,7 @@ export declare const translations: {
12
12
  Speakers: string;
13
13
  Video: string;
14
14
  "You are muted. Unmute to speak.": string;
15
+ Live: string;
15
16
  "You can now speak.": string;
16
17
  "Awaiting for an approval to speak.": string;
17
18
  "You can no longer speak.": string;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "0.3.29";
1
+ export declare const version = "0.3.30";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '0.3.29';
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.29",
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.24",
37
- "@stream-io/video-react-bindings": "^0.2.25",
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.9",
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
+ };
@@ -1 +1,2 @@
1
1
  export * from './Audio';
2
+ export * from './ParticipantsAudio';
@@ -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
+ };
@@ -11,9 +11,10 @@ import {
11
11
  ParticipantView,
12
12
  ParticipantViewProps,
13
13
  } from '../ParticipantView';
14
- import { Audio } from '../Audio';
14
+ import { ParticipantsAudio } from '../Audio';
15
15
  import { IconButton } from '../../../components';
16
16
  import { chunk } from '../../../utilities';
17
+ import { usePaginatedLayoutSortPreset } from './hooks';
17
18
 
18
19
  const GROUP_SIZE = 16;
19
20
 
@@ -85,6 +86,8 @@ export const PaginatedGridLayout = ({
85
86
  // used to render audio elements
86
87
  const remoteParticipants = useRemoteParticipants();
87
88
 
89
+ usePaginatedLayoutSortPreset(call);
90
+
88
91
  // only used to render video elements
89
92
  const participantGroups = useMemo(
90
93
  () =>
@@ -110,9 +113,7 @@ export const PaginatedGridLayout = ({
110
113
 
111
114
  return (
112
115
  <div className="str-video__paginated-grid-layout__wrapper">
113
- {remoteParticipants.map((participant) => (
114
- <Audio key={participant.sessionId} participant={participant} />
115
- ))}
116
+ <ParticipantsAudio participants={remoteParticipants} />
116
117
  <div className="str-video__paginated-grid-layout">
117
118
  {pageArrowsVisible && pageCount > 1 && (
118
119
  <IconButton
@@ -1,16 +1,7 @@
1
1
  import { useEffect, useState } from 'react';
2
- import {
3
- CallTypes,
4
- combineComparators,
5
- Comparator,
6
- defaultSortPreset,
7
- screenSharing,
8
- SfuModels,
9
- speakerLayoutSortPreset,
10
- StreamVideoParticipant,
11
- } from '@stream-io/video-client';
12
- import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
13
2
  import clsx from 'clsx';
3
+ import { SfuModels, StreamVideoParticipant } from '@stream-io/video-client';
4
+ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
14
5
 
15
6
  import {
16
7
  DefaultParticipantViewUI,
@@ -22,14 +13,15 @@ import {
22
13
  useHorizontalScrollPosition,
23
14
  useVerticalScrollPosition,
24
15
  } from '../../../hooks';
16
+ import { useSpeakerLayoutSortPreset } from './hooks';
25
17
  import { useCalculateHardLimit } from '../../hooks/useCalculateHardLimit';
26
- import { Audio } from '../Audio';
18
+ import { ParticipantsAudio } from '../Audio';
27
19
 
28
20
  export type SpeakerLayoutProps = {
29
21
  ParticipantViewUISpotlight?: ParticipantViewProps['ParticipantViewUI'];
30
22
  ParticipantViewUIBar?: ParticipantViewProps['ParticipantViewUI'];
31
23
  /**
32
- * The position of the particpants who are not in focus.
24
+ * The position of the participants who are not in focus.
33
25
  * Providing `null` will hide the bar.
34
26
  */
35
27
  participantsBarPosition?: 'top' | 'bottom' | 'left' | 'right' | null;
@@ -75,7 +67,6 @@ export const SpeakerLayout = ({
75
67
  participantsBarPosition === 'left' || participantsBarPosition === 'right';
76
68
  const isHorizontal =
77
69
  participantsBarPosition === 'top' || participantsBarPosition === 'bottom';
78
- const isOneOnOneCall = otherParticipants.length === 1;
79
70
 
80
71
  useEffect(() => {
81
72
  if (!participantsBarWrapperElement || !call) return;
@@ -84,23 +75,8 @@ export const SpeakerLayout = ({
84
75
  return () => cleanup();
85
76
  }, [participantsBarWrapperElement, call]);
86
77
 
87
- useEffect(() => {
88
- if (!call) return;
89
- // always show the remote participant in the spotlight
90
- if (isOneOnOneCall) {
91
- call.setSortParticipantsBy(combineComparators(screenSharing, loggedIn));
92
- } else {
93
- call.setSortParticipantsBy(speakerLayoutSortPreset);
94
- }
95
-
96
- return () => {
97
- // reset the sorting to the default for the call type
98
- const callConfig = CallTypes.get(call.type);
99
- call.setSortParticipantsBy(
100
- callConfig.options.sortParticipantsBy || defaultSortPreset,
101
- );
102
- };
103
- }, [call, isOneOnOneCall]);
78
+ const isOneOnOneCall = otherParticipants.length === 1;
79
+ useSpeakerLayoutSortPreset(call, isOneOnOneCall);
104
80
 
105
81
  let participantsWithAppliedLimit = otherParticipants;
106
82
 
@@ -121,9 +97,7 @@ export const SpeakerLayout = ({
121
97
 
122
98
  return (
123
99
  <div className="str-video__speaker-layout__wrapper">
124
- {remoteParticipants.map((participant) => (
125
- <Audio key={participant.sessionId} participant={participant} />
126
- ))}
100
+ <ParticipantsAudio participants={remoteParticipants} />
127
101
  <div
128
102
  className={clsx(
129
103
  'str-video__speaker-layout',
@@ -272,9 +246,3 @@ const VerticalScrollButtons = <T extends HTMLElement>({
272
246
 
273
247
  const hasScreenShare = (p?: StreamVideoParticipant) =>
274
248
  !!p?.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE);
275
-
276
- const loggedIn: Comparator<StreamVideoParticipant> = (a, b) => {
277
- if (a.isLocalParticipant) return 1;
278
- if (b.isLocalParticipant) return -1;
279
- return 0;
280
- };