From be12e7bf0a583d0d8dc9aad22ba99012375b9a6f Mon Sep 17 00:00:00 2001 From: Faiq Naufal <43695578+faiq-naufal@users.noreply.github.com> Date: Thu, 1 Feb 2024 13:03:13 +0700 Subject: [PATCH] fix: webinar speaker mode update (#136) * refactor: rename component and style names * fix: hidden participants are still rendered as audio tag * refactor: wrap the calculation logic into 1 usememo --- .../components/conference-speaker-layout.tsx | 74 ------------ app/_features/room/components/conference.tsx | 2 +- .../components/meeting-gallery-layout.tsx | 20 ++-- .../meeting-presentation-layout.tsx | 28 +++-- .../webinar-presentation-layout.tsx | 52 ++++---- .../components/webinar-speaker-layout.tsx | 113 ++++++++++++++++++ ...speaker.css => webinar-speaker-layout.css} | 0 7 files changed, 168 insertions(+), 121 deletions(-) delete mode 100644 app/_features/room/components/conference-speaker-layout.tsx create mode 100644 app/_features/room/components/webinar-speaker-layout.tsx rename app/_features/room/styles/{conference-speaker.css => webinar-speaker-layout.css} (100%) diff --git a/app/_features/room/components/conference-speaker-layout.tsx b/app/_features/room/components/conference-speaker-layout.tsx deleted file mode 100644 index 6bae6fd8..00000000 --- a/app/_features/room/components/conference-speaker-layout.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import ConferenceScreen from '@/_features/room/components/conference-screen'; -import { type ParticipantStream } from '@/_features/room/contexts/participant-context'; -import '../styles/conference-speaker.css'; -import { useMetadataContext } from '@/_features/room/contexts/metadata-context'; - -export default function ConferenceSpeakerLayout({ - streams, -}: { - streams: ParticipantStream[]; -}) { - const { moderatorClientIDs, speakerClientIDs } = useMetadataContext(); - - const speakers = useMemo(() => { - return streams.filter((stream) => { - return ( - (moderatorClientIDs.includes(stream.clientId) && - stream.source === 'media') || - (speakerClientIDs.includes(stream.clientId) && - stream.source === 'media') - ); - }); - }, [streams, moderatorClientIDs, speakerClientIDs]); - - const participants = useMemo(() => { - return streams.filter((stream) => { - return ( - !moderatorClientIDs.includes(stream.clientId) && - !speakerClientIDs.includes(stream.clientId) && - stream.source === 'media' - ); - }); - }, [streams, moderatorClientIDs, speakerClientIDs]); - - const MAX_VISIBLE_PARTICIPANTS = 20; - const slicedParticipants = participants.slice(0, MAX_VISIBLE_PARTICIPANTS); - - return ( -
-
- {speakers.map((speaker) => { - return ( -
- -
- ); - })} -
-
-
- {slicedParticipants.map((participant) => { - return ( -
- -
- ); - })} - {participants.length > MAX_VISIBLE_PARTICIPANTS && ( -
-
- More+ -
-
- )} -
-
-
- ); -} diff --git a/app/_features/room/components/conference.tsx b/app/_features/room/components/conference.tsx index 8b84d4f5..ffd41e6d 100644 --- a/app/_features/room/components/conference.tsx +++ b/app/_features/room/components/conference.tsx @@ -9,7 +9,7 @@ import { usePeerContext } from '@/_features/room/contexts/peer-context'; import MeetingOneOnOneLayout from './meeting-one-on-one-layout'; import MeetingGalleryLayout from './meeting-gallery-layout'; import MeetingPresentationLayout from './meeting-presentation-layout'; -import WebinarSpeakerLayout from './conference-speaker-layout'; +import WebinarSpeakerLayout from './webinar-speaker-layout'; import WebinarPresentationLayout from './webinar-presentation-layout'; import PlugConnectedFillIcon from '@/_shared/components/icons/plug-connected-fill-icon'; import PlugDisconnectedFillIcon from '@/_shared/components/icons/plug-disconnected-fill-icon'; diff --git a/app/_features/room/components/meeting-gallery-layout.tsx b/app/_features/room/components/meeting-gallery-layout.tsx index 050127cb..ceccedbe 100644 --- a/app/_features/room/components/meeting-gallery-layout.tsx +++ b/app/_features/room/components/meeting-gallery-layout.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useMemo } from 'react'; import { type ParticipantStream } from '@/_features/room/contexts/participant-context'; import '../styles/meeting-gallery-layout.css'; import ConferenceScreen from './conference-screen'; @@ -12,13 +13,18 @@ export default function MeetingGalleryLayout({ }) { const MAX_VISIBLE_PARTICIPANTS = 49; const moreThanMax = streams.length > MAX_VISIBLE_PARTICIPANTS; - const visibleParticipants = streams.slice( - 0, - moreThanMax ? MAX_VISIBLE_PARTICIPANTS - 1 : MAX_VISIBLE_PARTICIPANTS - ); - const hiddenParticipants = streams.slice( - moreThanMax ? MAX_VISIBLE_PARTICIPANTS - 1 : MAX_VISIBLE_PARTICIPANTS - ); + + const { visibleParticipants, hiddenParticipants } = useMemo(() => { + const visibleParticipants = streams.slice( + 0, + moreThanMax ? MAX_VISIBLE_PARTICIPANTS - 1 : MAX_VISIBLE_PARTICIPANTS + ); + const hiddenParticipants = streams.slice( + moreThanMax ? MAX_VISIBLE_PARTICIPANTS - 1 : MAX_VISIBLE_PARTICIPANTS + ); + + return { visibleParticipants, hiddenParticipants }; + }, [streams, moreThanMax]); const maxColumns = Math.ceil(Math.sqrt(visibleParticipants.length)); diff --git a/app/_features/room/components/meeting-presentation-layout.tsx b/app/_features/room/components/meeting-presentation-layout.tsx index 4666a4f6..ff1ea581 100644 --- a/app/_features/room/components/meeting-presentation-layout.tsx +++ b/app/_features/room/components/meeting-presentation-layout.tsx @@ -13,8 +13,8 @@ export default function MeetingPresentationLayout({ }) { const MAX_VISIBLE_PARTICIPANTS = 6; - const { screens, medias } = useMemo(() => { - return streams.reduce( + const { spotlightScreen, visibleStreams, hiddenStreams } = useMemo(() => { + const { screens, medias } = streams.reduce( (accumulator, currentValue) => { if (currentValue.source === 'screen') { return { @@ -30,20 +30,22 @@ export default function MeetingPresentationLayout({ }, { screens: [] as ParticipantStream[], medias: [] as ParticipantStream[] } ); - }, [streams]); - const spotlightScreen = screens.pop(); - const visibleScreens = screens.slice(0, MAX_VISIBLE_PARTICIPANTS); - const inVisibleScreens = screens.slice(MAX_VISIBLE_PARTICIPANTS); + const spotlightScreen = screens.pop(); + const visibleScreens = screens.slice(0, MAX_VISIBLE_PARTICIPANTS); + const inVisibleScreens = screens.slice(MAX_VISIBLE_PARTICIPANTS); + + const visibleParticipants = + visibleScreens.length < MAX_VISIBLE_PARTICIPANTS + ? medias.slice(0, MAX_VISIBLE_PARTICIPANTS - visibleScreens.length) + : []; + const invisibleParticipants = medias.slice(visibleParticipants.length); - const visibleParticipants = - visibleScreens.length < MAX_VISIBLE_PARTICIPANTS - ? medias.slice(0, MAX_VISIBLE_PARTICIPANTS - visibleScreens.length) - : []; - const invisibleParticipants = medias.slice(visibleParticipants.length); + const visibleStreams = [...visibleScreens, ...visibleParticipants]; + const hiddenStreams = [...inVisibleScreens, ...invisibleParticipants]; - const visibleStreams = [...visibleScreens, ...visibleParticipants]; - const hiddenStreams = [...inVisibleScreens, ...invisibleParticipants]; + return { spotlightScreen, visibleStreams, hiddenStreams }; + }, [streams]); const maxColumns = Math.ceil(Math.sqrt(visibleStreams.length)); diff --git a/app/_features/room/components/webinar-presentation-layout.tsx b/app/_features/room/components/webinar-presentation-layout.tsx index f4388e5f..c677374e 100644 --- a/app/_features/room/components/webinar-presentation-layout.tsx +++ b/app/_features/room/components/webinar-presentation-layout.tsx @@ -15,8 +15,8 @@ export default function WebinarPresentationLayout({ const { moderatorClientIDs, speakerClientIDs } = useMetadataContext(); const MAX_VISIBLE_PARTICIPANTS = 6; - const { speakers, participants } = useMemo(() => { - return streams.reduce( + const { spotlightScreen, visibleStreams, hiddenStreams } = useMemo(() => { + const { speakers, participants } = streams.reduce( (accumulator, currentStream) => { if ( moderatorClientIDs.includes(currentStream.clientId) || @@ -38,10 +38,8 @@ export default function WebinarPresentationLayout({ participants: [] as ParticipantStream[], } ); - }, [streams, moderatorClientIDs, speakerClientIDs]); - const { speakerMedias, speakerScreens } = useMemo(() => { - return speakers.reduce( + const { speakerMedias, speakerScreens } = speakers.reduce( (accumulator, currentValue) => { if (currentValue.source === 'screen') { return { @@ -60,31 +58,33 @@ export default function WebinarPresentationLayout({ speakerMedias: [] as ParticipantStream[], } ); - }, [speakers]); - const spotlightScreen = speakerScreens.pop(); - const visibleSpeakerScreens = speakerScreens.slice( - 0, - MAX_VISIBLE_PARTICIPANTS - ); - const hiddenSpeakerScreens = speakerScreens.slice(MAX_VISIBLE_PARTICIPANTS); + const spotlightScreen = speakerScreens.pop(); + const visibleSpeakerScreens = speakerScreens.slice( + 0, + MAX_VISIBLE_PARTICIPANTS + ); + const hiddenSpeakerScreens = speakerScreens.slice(MAX_VISIBLE_PARTICIPANTS); - const visibleSpeakers = - visibleSpeakerScreens.length < MAX_VISIBLE_PARTICIPANTS - ? speakerMedias.slice( - 0, - MAX_VISIBLE_PARTICIPANTS - visibleSpeakerScreens.length - ) - : []; + const visibleSpeakers = + visibleSpeakerScreens.length < MAX_VISIBLE_PARTICIPANTS + ? speakerMedias.slice( + 0, + MAX_VISIBLE_PARTICIPANTS - visibleSpeakerScreens.length + ) + : []; - const hiddenSpeakers = speakerMedias.slice(visibleSpeakers.length); + const hiddenSpeakers = speakerMedias.slice(visibleSpeakers.length); - const visibleStreams = [...visibleSpeakerScreens, ...visibleSpeakers]; - const hiddenStreams = [ - ...hiddenSpeakerScreens, - ...hiddenSpeakers, - ...participants, - ]; + const visibleStreams = [...visibleSpeakerScreens, ...visibleSpeakers]; + const hiddenStreams = [ + ...hiddenSpeakerScreens, + ...hiddenSpeakers, + ...participants, + ]; + + return { spotlightScreen, visibleStreams, hiddenStreams }; + }, [streams, moderatorClientIDs, speakerClientIDs]); const maxColumns = Math.ceil(Math.sqrt(visibleStreams.length)); diff --git a/app/_features/room/components/webinar-speaker-layout.tsx b/app/_features/room/components/webinar-speaker-layout.tsx new file mode 100644 index 00000000..665068ec --- /dev/null +++ b/app/_features/room/components/webinar-speaker-layout.tsx @@ -0,0 +1,113 @@ +'use client'; + +import { useMemo } from 'react'; +import { type ParticipantStream } from '@/_features/room/contexts/participant-context'; +import { useMetadataContext } from '@/_features/room/contexts/metadata-context'; +import ConferenceScreen from '@/_features/room/components/conference-screen'; +import ConferenceScreenHidden from '@/_features/room/components/conference-screen-hidden'; +import '../styles/webinar-speaker-layout.css'; + +export default function WebinarSpeakerLayout({ + streams, +}: { + streams: ParticipantStream[]; +}) { + const MAX_VISIBLE_PARTICIPANTS = 20; + const { moderatorClientIDs, speakerClientIDs } = useMetadataContext(); + + const { + speakers, + visibleParticipants, + hiddenParticipants, + participantsMoreThanMax, + } = useMemo(() => { + const { speakers, participants } = streams.reduce( + (accumulator, currentStream) => { + if ( + moderatorClientIDs.includes(currentStream.clientId) || + speakerClientIDs.includes(currentStream.clientId) + ) { + return { + ...accumulator, + speakers: [...accumulator.speakers, currentStream], + }; + } else { + return { + ...accumulator, + participants: [...accumulator.participants, currentStream], + }; + } + }, + { + speakers: [] as ParticipantStream[], + participants: [] as ParticipantStream[], + } + ); + + const participantsMoreThanMax = + participants.length > MAX_VISIBLE_PARTICIPANTS; + + const visibleParticipants = participants.slice( + 0, + participantsMoreThanMax + ? MAX_VISIBLE_PARTICIPANTS - 1 + : MAX_VISIBLE_PARTICIPANTS + ); + + const hiddenParticipants = participants.slice( + participantsMoreThanMax + ? MAX_VISIBLE_PARTICIPANTS - 1 + : MAX_VISIBLE_PARTICIPANTS + ); + + return { + speakers, + visibleParticipants, + hiddenParticipants, + participantsMoreThanMax, + }; + }, [streams, moderatorClientIDs, speakerClientIDs]); + + return ( +
+
+ {speakers.map((speaker) => { + return ( +
+ +
+ ); + })} +
+
+
+ {visibleParticipants.map((stream) => { + return ( +
+ +
+ ); + })} + {participantsMoreThanMax && ( +
+
+ More+ +
+ {hiddenParticipants.map((stream) => { + return ( + + ); + })} +
+ )} +
+
+
+ ); +} diff --git a/app/_features/room/styles/conference-speaker.css b/app/_features/room/styles/webinar-speaker-layout.css similarity index 100% rename from app/_features/room/styles/conference-speaker.css rename to app/_features/room/styles/webinar-speaker-layout.css