Automatic Reassingment of users and Modification of subsessions to open not working

Description
In a previous correspondence with @vic.yang, he informed me that I could create more than the required subsessions and only open the ones which I wanted to use per time. I had created new logic to either reduce the number of subsessions or increase the number of subsessions as required.

The problem now is that it doesn’t work as expected. Below I will share all relevant snippets of my code with you so you can help point out where I may not be getting it right.

Browser Console Error
“exceeded max limit”

Which Web Video SDK version?
1.9.5

Video SDK Code Snippets
Code to open the subsessions. This can be called independent of the create subsessions

const openSubsessions = useCallback(async () => {
    if (ssClient) {
      const guests = zmClient.getAllUser().filter(
        p => !p.displayName.endsWith('(Dozent:in)') &&
          !p.displayName.endsWith('(Moderator:in)')
      );

      if (guests.length < 1) {
        message.info(
          'Bitte versuchen Sie es erneut, wenn Teilnehmer verfügbar sind.'
        );
        return 0;
      }

      console.log('subsessions to open', subsessions);

      setRetryOpeningSubsessions(true);
      setIsRetryingOpenSubSessions(true);
      try {
        if (!subsessions.length || !ssClient.getSubsessionList().length || unassignedUserList.length) {
          console.log('in attempt');
          if (!subsessions.length) {
            await ssClient.createSubsessions(
              roomsToOpen,
              SubsessionAllocationPattern.Automatically
            ).then(async (newSubSessions) => {
              await ssClient.openSubsessions(
                [...subsessions, ...newSubSessions as unknown as Subsession[]],
                options
              ).then(() => {
                setSubsessionStatus(SubsessionStatus.InProgress);
                setSubsessions(() => [...subsessions, ...newSubSessions as unknown as Subsession[]]);

                if (chatClient && chatClient.getPrivilege() === ChatPrivilege.NoOne) {
                  chatClient?.setPrivilege(ChatPrivilege.EveryonePublicly).then(() => {
                    message.info('Die Chat-Berechtigung "Alle dürfen sprechen" wurde wieder aktiviert, um sicherzustellen, dass die Nutzer in den Breakout-Räumen schreiben können.');

                    setAllowChatting(true);
                  });
                }

                if (guests.length) {
                  guests.forEach(async (g) => {
                    await mediaStream?.unmuteAudio(g.userId);
                    console.log('muted', g);
                  });

                  setUnmuteAllParticipants(true);
                  setCurrentQueue((currentQueue: HandQueue[]) => []);
                }

                setDoNotDisturb(false);
                const moderator = zmClient.getAllUser().filter((u) => u.displayName.endsWith('(Moderator:in)'));
                if (moderator.length) {
                  let command = {
                    action: 'sent-moderation-update',
                    dnd: false,
                  }

                  cmdClient?.send(JSON.stringify(command), moderator[0].userId);
                }

                setRetryOpeningSubsessions(false);
                setIsRetryingOpenSubSessions(false);
              }).catch((err) => {
                if (err.reason === 'only host can do the operation') {
                  message.error('Nur der Host kann den Vorgang ausführen');
                } else {
                  message.error(err.reason)
                }
              });
            }).catch((err) => {
              if (err.reason === 'only host can do the operation') {
                message.error('Nur der Host kann den Vorgang ausführen');
              } else {
                message.error(err.reason)
              }
            });
          } else {
            ssClient.openSubsessions(subsessions, options).then(() => {
              setSubsessionStatus(SubsessionStatus.InProgress);

              if (unassignedUserList.length) {
                const randomSubSession = subsessions[Math.floor(Math.random() * subsessions.length)];
                let participant: Participant | null;
                unassignedUserList.forEach(async (user) => {
                  participant = user;
                  ssClient.assignUserToSubsession(
                    user.userId,
                    randomSubSession.subsessionId
                  ).then(() => {
                    setSubsessions(
                      produce((subsessions: Subsession[]) => {
                        const subsession = subsessions.find((r: Subsession) => r.subsessionId === randomSubSession.subsessionId);
                        if (subsession && participant) {
                          subsession.userList.push({
                            userId: participant.userId,
                            displayName: participant.displayName,
                            avatar: participant.avatar,
                            isInSubsession: true
                          });
                        }
                      })
                    );
                  });
                });
              }

              if (chatClient && chatClient.getPrivilege() === ChatPrivilege.NoOne) {
                chatClient?.setPrivilege(ChatPrivilege.EveryonePublicly).then(() => {
                  message.info('Die Chat-Berechtigung "Alle dürfen sprechen" wurde wieder aktiviert, um sicherzustellen, dass die Nutzer in den Breakout-Räumen schreiben können.');

                  setAllowChatting(true);
                });
              }

              if (guests.length) {
                guests.forEach(async (g) => {
                  await mediaStream?.unmuteAudio(g.userId);
                  console.log('muted', g);
                });

                setUnmuteAllParticipants(true);
                setCurrentQueue((currentQueue: HandQueue[]) => []);
              }

              setDoNotDisturb(false);
              const moderator = zmClient.getAllUser().filter((u) => u.displayName.endsWith('(Moderator:in)'));
              if (moderator.length) {
                let command = {
                  action: 'sent-moderation-update',
                  dnd: false,
                }

                cmdClient?.send(JSON.stringify(command), moderator[0].userId);
              }
            }).catch((err) => {
              if (err.reason === 'only host can do the operation') {
                message.error('Nur der Host kann den Vorgang ausführen');
              } else {
                message.error(err.reason)
              }
            });
          }
          console.log('after attempt');
        } else {
          await ssClient.openSubsessions(subsessions, options).then(() => {
            setSubsessionStatus(SubsessionStatus.InProgress);

            if (chatClient && chatClient.getPrivilege() === ChatPrivilege.NoOne) {
              chatClient?.setPrivilege(ChatPrivilege.EveryonePublicly).then(() => {
                message.info('Die Chat-Berechtigung "Alle dürfen sprechen" wurde wieder aktiviert, um sicherzustellen, dass die Nutzer in den Breakout-Räumen schreiben können.');

                setAllowChatting(true);
              });
            }

            const participants = zmClient.getAllUser();
            const guests = participants.filter(
              (p) => !p.isHost && !p.isManager && p.userId !== zmClient.getCurrentUserInfo().userId
            );

            if (guests.length) {
              guests.forEach(async (g) => {
                await mediaStream?.unmuteAudio(g.userId);
                console.log('muted', g);
              });

              setUnmuteAllParticipants(true);
              setCurrentQueue((currentQueue: HandQueue[]) => []);
            }

            setDoNotDisturb(false);
            const moderator = zmClient.getAllUser().filter((u) => u.displayName.endsWith('(Moderator:in)'));
            if (moderator.length) {
              let command = {
                action: 'sent-moderation-update',
                dnd: false,
              }

              cmdClient?.send(JSON.stringify(command), moderator[0].userId);
            }
          }).catch((err) => {
            if (err.reason === 'only host can do the operation') {
              message.error('Nur der Host kann den Vorgang ausführen');
            } else {
              message.error(err.reason)
            }
          });
        }
      } catch (err) {
        console.log(err);
      }

      setRetryOpeningSubsessions(false);
      setIsRetryingOpenSubSessions(false);
      // await assignUnassignedUsersToAnyRunningSubsessions();
      console.log('sub open status', subsessionStatus);
      console.log('ssclient stat', ssClient.getSubsessionStatus());
    }
  }, [
    ssClient,
    zmClient,
    setIsRetryingOpenSubSessions,
    subsessionStatus,
    subsessions,
    unassignedUserList,
    roomsToOpen,
    options,
    chatClient,
    setDoNotDisturb,
    setAllowChatting,
    setUnmuteAllParticipants,
    setCurrentQueue,
    mediaStream,
    cmdClient
  ]);

Function to calculate the total number of subsessions to open:


  const calculateNumberOfRooms = useCallback((participantsPerRoom: number): number => {
    const totalParticipants = zmClient.getAllUser().filter(
      p => !p.displayName.endsWith('(Dozent:in)') &&
        !p.displayName.endsWith('(Moderator:in)')
    );

    if (totalParticipants.length < 1) {
      console.log('generated rooms', 0);
      return 0;
    }

    if (totalParticipants.length <= participantsPerRoom) {
      console.log('generated rooms', 1);
      return 1;
    }

    console.log('generated rooms', Math.floor(totalParticipants.length / participantsPerRoom));
    return Math.floor(totalParticipants.length / participantsPerRoom);
  }, [zmClient]);

Function to recreate the subsessions after closing:


  const recreateSubsessions = useCallback(
    async (participantsPerRoom: number, pattern: SubsessionAllocationPattern) => {
      const totalRooms = calculateNumberOfRooms(participantsPerRoom);

      if (totalRooms === 0) {
        message.info('Bitte versuchen Sie es erneut, wenn Teilnehmer verfügbar sind.');
        return;
      }

      setRoomsToOpen(() => totalRooms);

      message.info('Breakout-Räume werden erstellt. Bitte haben Sie etwas Geduld, da dies bis zu 10 Sekunden dauern kann. Wenn der Button nach 10 Sekunden nicht rot wird, klicken Sie bitte erneut.', 10);

      if (ssClient) {
        try {
          setRetryOpeningSubsessions(true);
          setIsRetryingOpenSubSessions(true);

          const currentNumberOfRooms = ssClient.getSubsessionList().length;

          if (currentNumberOfRooms > totalRooms) {
            setSubsessions((subsessions: Subsession[]) => subsessions.slice(0, totalRooms));
          } else if (totalRooms > currentNumberOfRooms) {
            const totalSubSessionsToCreate = totalRooms - currentNumberOfRooms;
            await ssClient.createSubsessions(
              totalSubSessionsToCreate,
              SubsessionAllocationPattern.Automatically
            ).then((newSubSessions) => {
              setSubsessions((subsessions: Subsession[]) => [
                ...subsessions,
                ...newSubSessions as unknown as Subsession[]
              ]);
            }).catch((err) => {
              if (err.reason === 'only host can do the operation') {
                message.error('Nur der Host kann den Vorgang ausführen');
              } else {
                message.error(err.reason)
              }
            });
          }

          openSubsessions();
        } catch (e) {
          console.warn(e);
        }
      }
    },
    [calculateNumberOfRooms, ssClient, setIsRetryingOpenSubSessions, openSubsessions]
  );

Function to create the subsessions at the first instance:


  const createSubsessions = useCallback(
    async (participantsPerRoom: number, pattern: SubsessionAllocationPattern) => {
      const totalRooms = calculateNumberOfRooms(participantsPerRoom);

      if (totalRooms === 0) {
        message.info('Bitte versuchen Sie es erneut, wenn Teilnehmer verfügbar sind.');
        return;
      }

      setRoomsToOpen(() => totalRooms);

      message.info('Breakout-Räume werden erstellt. Bitte haben Sie etwas Geduld, da dies bis zu 10 Sekunden dauern kann. Wenn der Button nach 10 Sekunden nicht rot wird, klicken Sie bitte erneut.', 10);

      if (ssClient) {
        try {
          setRetryOpeningSubsessions(true);
          setIsRetryingOpenSubSessions(true);

          await ssClient.createSubsessions(totalRooms, pattern).then((subsessions) => {
            setSubsessions(() => subsessions as unknown as Subsession[]);
            openSubsessions();
          }).catch((err) => {
            if (err.reason === 'only host can do the operation') {
              message.error('Nur der Host kann den Vorgang ausführen');
            } else {
              message.error(err.reason)
            }
          });
        } catch (e) {
          console.warn(e);
        }
      }
    },
    [calculateNumberOfRooms, ssClient, setIsRetryingOpenSubSessions, openSubsessions]
  );

Then I have a retry interval that retries to open the subsessions if it fails when called by the create or recreate subsessions methods:

let retryInterval = useRef<NodeJS.Timeout | undefined>(undefined);

  useEffect(() => {
    async function retryOpenSubsessions() {
      await openSubsessions();
    }

    retryInterval.current = setInterval(() => {
      if (ssClient) {
        if (
          (ssClient.getSubsessionStatus() === SubsessionStatus.InProgress &&
            ssClient.getSubsessionList().length) ||
          ssClient.getSubsessionStatus() === SubsessionStatus.Closed ||
          ssClient.getSubsessionStatus() === SubsessionStatus.Closing
        ) {
          setRetryOpeningSubsessions(false);
          setIsRetryingOpenSubSessions(false);
          clearInterval(retryInterval.current);
        } else if (
          retryOpeningSubsessions &&
          (
            ssClient.getSubsessionStatus() !== SubsessionStatus.Closed ||
            ssClient.getSubsessionStatus() !== SubsessionStatus.Closing
          )
        ) {
          retryOpenSubsessions();
          console.log('would have retried here');
        }
      }
    }, 10000);
  }, [openSubsessions, retryOpeningSubsessions, ssClient, setRetryOpeningSubsessions, setIsRetryingOpenSubSessions]);

All of these are done in the useSubsession hook, and returned by the hook.

I would greatly appreciate if you can help decipher what I may be missing.

The following are the current behaviour of the things:

  1. The createSubsessions method throws an error sometimes even when I have awaited it to resolve before opening the subsessions. However, the subsessions eventually open, but the behaviour is unstable.
  2. When I recreate subsessions specifying the participants per room, and this results in more rooms than was already created, the browser freezes. If I close or exit those tabs, and refresh, then retry opening the subsessions with the same settings, all the participants are sent to the same subsession.
  3. Recreating with fewer subsessions by specifying more participants per room also causes the same issue as above, and if it eventually succeeds, the participants are not reassigned to different rooms.

Thanks.

Hey @godwin.owonam

Thanks for your feedback.

From my understanding, do you need to implement the following functionality:

  • Allocate users who are not instructors or moderators to fixed-sized subsessions.
  • Throughout the entire session, subsessions may open or close multiple times.
  • New users may join and also need to be allocated to subsessions.

Following is my example for your reference:

function filterOutParticipant(participants: Participant) {
  return participants.filter(
    (p) =>
      !p.displayName.endsWith("(Dozent:in)") &&
      !p.displayName.endsWith("(Moderator:in)")
  );
}

function calculateNumberOfRooms(
  participants: Participant[],
  participantsPerRoom: number
) {
  if (participants.length === 0) {
    console.log("generated rooms", 0);
    return 0;
  }
  if (participants.length <= participantsPerRoom) {
    console.log("generated rooms", 1);
    return 1;
  }
  const number = Math.ceil(participants.length / participantsPerRoom);
  console.log("generated rooms", number);
  return number;
}

Function to open subsessions

async function openSubsessions() {
  const subsessionStatus = ssClient.getSubsessionStatus();
  const subsessions = ssClient.getSubsessionList();
  const unassignedUsers = ssClient.getUnassignedUserList();
  // Subsession is not started
  if (
    subsessionStatus === SubsessionStatus.NotStarted ||
    subsessionStatus === SubsessionStatus.Closed
  ) {
    const newRoomAmount = calculateNumberOfRooms(
      filterOutParticipant(unassignedUsers),
      participantsPerRoom
    );
    // Randomly assign unassigned users to a new subsession.
    const rooms = await ssClient.createSubsessions(
      newRoomAmount,
      SubsessionAllocationPattern.Automatically
    );
    let totalRooms = rooms;
    // Reuse previously opened subsessions.
    if (subsessions.length > 0) {
      totalRooms = subsessions.concat(rooms);
    }
    await ssClient.openSubsessions(totalRooms);
  } else if (subsessionStatus === SubsessionStatus.InProgress) {
    // Subsession is in progress
    const newUsers = filterOutParticipant(unassignedUsers);
    // Cannot create new subsession when Subsession is in progress
    // Assign new user to the avaliable subsession
    const avaliableRooms = subsessions.filter(
      (item) => item.userList.length < participantsPerRoom
    );
    avaliableRooms.forEach((item) => {
      const vacant = participantsPerRoom - item.userList.length;
      while (vacant > 0 && newUsers.length > 0) {
        const user = newUsers.pop();
        ssClient.assignUserToSubsession(user.userId, item.subsessionId);
        vacant--;
      }
    });
  }
}

P.S.
The maximum number of existing subsessions is 100. It’s the cause of “exceed maximum size” error.

Thanks
Vic

Hi @vic.yang

Thank you for your response.
You’re just in time.

I do have a few questions from your response.

  1. When you say the maximum number of existing subsessions is 100, do you mean per account or per session? If per session, are you saying that it was possible that my logic could generate 100 subsessions? I have tested with maximum of 7 participants only.

  2. Do you also mean that if subsessions are closed then we cannot reopen those closed ones? But need to create new ones. Because from your suggested solution, you checked if the subsessions were not started or closed.

  3. Finally, a new issue developed. I don’t know if this is worthy of it’s own topic or you can promptly answer it. However, I will still create a topic for it. Today while testing I decided to run my old script that did three simple things, create one subsession, open the subsession, close the subsession, and then open it again. While testing, I had the issue that after closing the subsession the first time, it wouldn’t open again, but instead it made the browser tabs to freeze. When I closed the frozen tabs and tried the same sequence again, the same thing happened.

I’ll try your script and let you know. I will also appreciate if you can answer my above doubts.

Once again, thanks and best regards.
@godwin.owonam

One more thing here, you used const for vacant variable. Is this an oversight.
I trust that you had tested the code anyways.

@vic.yang
I have tried the solution snippets and they didn’t create new subsessions nor open existing ones.

Hey @godwin.owonam

First, I apologize for the mistake in the code. The variable ‘vacant’ should be declared as ‘let’.

For the 3 questions about subsessions, let me provide some answers:

  1. Reviewed the Video SDK, another reason for “exceed maximum size” is getting the zero amount of subsession from the parameter. The maximum limit of 100 subsessions applies to per session.

  2. Subsessions are reusable, and you can reopen subsessions that have been closed. However, you can also choose not to reuse them and create new subsessions. In the solution I provided, I am reusing closed subsessions, and you can retrieve them using the ssClient.getSubsessionList() method. Then, I allocate users who have newly joined and haven’t been assigned to a subsession to the newly created subsessions.

  3. I’m not sure what is causing the browser tab to freeze, but in our sample demo, I’ve tested it many times and haven’t encountered the same issue. Could you please share the complete test case with us for further investigation?

Thanks
Vic

Yeah. Thanks @vic.yang

I was able to resolve the issue. It was from somewhere else.

So the bug came from the closeAllSubsessions function which returns a promise. I thought that when the promise resolved, the subsessions will be in “Closed” state. But it turns out that it goes to “Closing” state. So the code put the subessions in an improper state. That was why my browser kept freezing.

That is correct, please see this forum post for more details on the submission status changes.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.