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 && (
-
- )}
-
-
-
- );
-}
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