Search code examples
javascriptreactjssortingthrottlingaws-chime-sdk

Amazon Chime SDK and React sorting remote tiles


Im working on a video chat app, which is implemented using Chime SDK and React. Curently im working on a sorting algorithm to sort user tiles based on their video state and microphone activity.

The attendees are represented as an array of objects which look similar to this (simplified)

attendees = [{id: 1, name "User 1"}, {id: 2, name: "User 2"}, ...];

Then I have an array of user ids which represent user activity baset on the quantity of their activity:

activeSpeakers = [2, 1]

This array can be empty which means nobody is speaking, or if there are items, the first item is the id of the most active speaker and the last is the id of the least active speaker.

I also have an array of user Id which contains chronogicaly ordered id of users who have the camera on. This array also can be empty meaning that nobody has the camera active.

attendeeToTileId = [2]

My task is to sort the user tiles based theese condirions:

  1. Camera active and active speaker
  2. Camera active and inactive speaker
  3. Camera inactive and active speaker
  4. Camera inactive and inactive speaker

Also if you familiar with the Chime SDK, you know that theese states update frequent, and my idea is to throthle the sorting.

So i stared with something like this

useEffect(() => {

// sort logic here
// but should be throtthled 
// or maybe debounced?

}, [attendees, attendeeToTileId, activeSpeakers])

I tryed custom throtling implementation as well as the lodash library, but nothing seems to work correctly.

So in summary:

  1. I need an advice for a sorting algorithm
  2. I need a throthling/debounce strategy for react

With either one, I would be happy. I just need a good starting point.


Solution

  • After a few weeks of tweaking and a few months of delay in writing the answer, I have found a solution that seems to work quite well.

    I have created a calculateSortingScore function that assigns a score to each tile based on the activity of the microphone and camera state.

    Here is the code snippet of the function:

    const calculateSortingScore = (attendee: RosterAttendeeType, activeSpeakers: string[], activeCameras: string[]) => {
            let score = 0
            /** Priorities for sorting:
             * 1. Users with camera on and talking
             * 2. Users with camera on and not talking
             * 3. Users with camera off and talking
             * 4. Users with camera off and not talking
             */
    
            // Camera on and talking
            if (
                activeCameras.length &&
                activeCameras.includes(attendee.chimeAttendeeId) &&
                activeSpeakers.includes(attendee.chimeAttendeeId)
            ) {
                score += 4 + activeSpeakers.indexOf(attendee.chimeAttendeeId)
            }
    
            // Camera on and muted or not talking
            if (
                activeCameras.length &&
                activeCameras.includes(attendee.chimeAttendeeId) &&
                !activeSpeakers.includes(attendee.chimeAttendeeId)
            ) {
                score += 3
            }
    
            // Camera off and talking or not muted
            if (
                activeCameras.length &&
                !activeCameras.includes(attendee.chimeAttendeeId) &&
                activeSpeakers.includes(attendee.chimeAttendeeId)
            ) {
                score += 2 + activeSpeakers.indexOf(attendee.chimeAttendeeId)
            }
            // Camera off and muted or not talking
            if (
                activeCameras.length &&
                !activeCameras.includes(attendee.chimeAttendeeId) &&
                !activeSpeakers.includes(attendee.chimeAttendeeId)
            ) {
                score += 1
            }
            return score
        }
    

    Then, I created a custom sort function that is throttled due to the microphone activity. If there are 20 users speaking, it would be chaotic. Therefore, I throttled the function invocation.

    const sortTiles = throttle((activeSpeakers, activeCameras, attendees) => {
            // 5.
            const sortedAttendees = [...attendees].sort(
                (a, b) =>
                    calculateSortingScore(b, activeSpeakers, activeCameras) - calculateSortingScore(a, activeSpeakers, activeCameras)
            )
            setAttendeesFinal(sortedAttendees)
        }, throttleInterval) // Throttle to once per 10 seconds
    

    In addition, I have defined a throttleInterval of 10000 ms, which seems suitable for my use case but can be adjusted as necessary. However, I would not set it to less than 5000 ms. Therefore, I defined it as follows: const throttleInterval = 10000.

    const throttledFunctionMemoized = useMemo(() => {
            // 4.
            return sortTiles
            // eslint-disable-next-line
        }, []) // This array of dependencies ensures that the function is memoized once
    
    // Create a memoized callback using the throttled function
        const throttledCallback = useCallback(() => {
            // 3.
            throttledFunctionMemoized(activeSpeakerState, Object.keys(attendeeIdToTileId), attendees) // Pass any required parameters to the throttled function
        }, [attendees, activeSpeakerState, attendeeIdToTileId, throttledFunctionMemoized]) // The callback will only change if count or the throttled function changes
    
    useEffect(() => {
            // 2.
            throttledCallback()
            // eslint-disable-next-line
        }, [attendees, activeSpeakerState, attendeeIdToTileId])
    
    useEffect(() => {
            // 1.
            console.log("SORT OUTPUT: ", attendeesFinal)
        }, [attendeesFinal])
    

    The only thing left is to loop over the attendeesFinal array and display the tiles. There might be a better solution to implement this, but this one works.