-
Notifications
You must be signed in to change notification settings - Fork 733
Video 8472 add dominant speaker #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Video 8472 add dominant speaker #676
Conversation
1, | ||
newDominantSpeakerWithStartTime! | ||
); | ||
// Add the least recent dominant speaker back into the array at the end: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the only discrepancy between iOS and web. Currently iOS moves the oldest dominant speaker on the first page to the start of the second page. This way the entire grid is ordered by dominant speaker time + connection time which seems intuitive. I am thinking this is how zoom works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timmydoza I think what @timrozum did with iOS makes sense. However, it is kind of tricky with the current implementation for the pagination feature. Since we are filling each page with the number of maxGridParticipants
, the first participant on the second page may actually be an onscreenParticipant
from the first page.
For instance, if a room has 13 participants, and maxGridParticipants
is 9, the first participant on the second page will actually be the fifth participant from the first page (when on iOS the first participant on the second page would be the 10th participant). Did we come to a decision about updating the usePagination
hook to mirror was iOS does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! It functions great, and the binary search stuff looks awesome.
I've been thinking about the use of useCollaborationParticipants
in useMainParticipant
, MenuBar
, and ParticipantAudioTracks
. None of those hooks/components need to know when the domainant speaker changes, but useCollaborationParticipants
adds a listener to that event and updates its state when the dominant speaker changes.
I'm wondering if we could use a plain useParticipants
hooks in these instances, where the array of participants is not updated when the dominant speaker is updated. I originally thought that a plain useParticipants
hook could also provide an array of participants to the useCollaborationParticipants
and useGridParticipants
hooks, but I think that might be a bit trickier than I first thought.
So I'm going to think about this a little more. There's an opportunity here to get rid of some rerenders, but I'm not sure if it's really worth the effort. We can chat more about it later.
if (newDominantSpeaker !== null || (newDominantSpeaker === null && includeNull)) { | ||
setDominantSpeaker(newDominantSpeaker); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be simplified a little to be easier to understand:
if (newDominantSpeaker !== null || (newDominantSpeaker === null && includeNull)) { | |
setDominantSpeaker(newDominantSpeaker); | |
} | |
if (includeNull || newDominantSpeaker !== null) { | |
setDominantSpeaker(newDominantSpeaker); | |
} |
This caught my eye because we see newDominantSpeaker !== null
and newDominantSpeaker === null
in the same if statement. We don't need both, because if newDominantSpeaker === null
is true, then we can infer that newDominantSpeaker !== null
is false without having to check.
@@ -33,6 +33,15 @@ describe('the useDominantSpeaker hook', () => { | |||
expect(result.current).toBe('mockDominantSpeaker'); | |||
}); | |||
|
|||
it('should set "null" when there is no dominant speaker and excludeNull is true', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it('should set "null" when there is no dominant speaker and excludeNull is true', () => { | |
it('should set "null" when there is no dominant speaker and includeNull is true', () => { |
// Here we use binary search to guess the new size of each video in the grid | ||
// so that they all fit nicely for any screen size up to a width of 16384px. | ||
let minVideoWidth = 0; | ||
let maxVideoWidth = 2 ** 14; | ||
|
||
// Here we try to guess the new size of each video by increasing the width by 1. | ||
// Once layoutIsTooSmall becomes false, we have found the correct video width: | ||
while (layoutIsTooSmall(newParticipantVideoWidth + 1, participantCount, containerWidth, containerHeight)) { | ||
newParticipantVideoWidth++; | ||
while (maxVideoWidth - minVideoWidth > 1) { | ||
let mid = (maxVideoWidth - minVideoWidth) / 2 + minVideoWidth; | ||
const isLower = layoutIsTooSmall(mid, participantCount, containerWidth, containerHeight); | ||
|
||
if (isLower) { | ||
minVideoWidth = mid; | ||
} else { | ||
maxVideoWidth = mid; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great!
@@ -3,7 +3,7 @@ import clsx from 'clsx'; | |||
import Participant from '../Participant/Participant'; | |||
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; | |||
import useMainParticipant from '../../hooks/useMainParticipant/useMainParticipant'; | |||
import useParticipants from '../../hooks/useParticipants/useParticipants'; | |||
import useParticipants from '../../hooks/useCollaborationParticipants/useCollaborationParticipants'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if we should also change the name of the variable here:
import useCollaborationParticipants from //...
?
@@ -1,7 +1,7 @@ | |||
import { act, renderHook } from '@testing-library/react-hooks'; | |||
import EventEmitter from 'events'; | |||
import useDominantSpeaker from '../useDominantSpeaker/useDominantSpeaker'; | |||
import useParticipants from './useParticipants'; | |||
import useParticipants from './useCollaborationParticipants'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: import useCollaborationParticipants
?
// update the participant's dominantSpeakerStartTime to when they became the new dominant speaker: | ||
newDominantSpeaker.dominantSpeakerStartTime = Date.now(); | ||
} else { | ||
return newParticipantsArray; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, I wonder if it would be better to return prevParticipants
instead.
On this line, we just want to bail out if there is no dominant speaker. So if we return newParticipantsArray
, it's going to be a different reference than the array that is stored in state (thanks to .slice()
), which (I think) would cause this hook to update it's state, which would then cause components to rerender.
Returning prevParticipants
is the same reference, so the state would not update, and components wouldn't update either.
I don't think this makes any real difference, but it crossed my mind.
} | ||
|
||
// Here we use maxGridParticipants - 1 since the localParticipant will always be the first in the grid | ||
let maxOnScreenParticipants = maxGridParticipants - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be const
?
|
||
// Here we use maxGridParticipants - 1 since the localParticipant will always be the first in the grid | ||
let maxOnScreenParticipants = maxGridParticipants - 1; | ||
const onscreenParticipants = newParticipantsArray.slice(0, maxOnScreenParticipants); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this app, I don't think onscreenParticipants
is the right variable name. Maybe something like firstPageParticipants
? This hook is just moving dominant speakers to the first page, but they may not be 'on screen' if the user is looking at a page other than the first.
(a, b) => a.dominantSpeakerStartTime - b.dominantSpeakerStartTime | ||
); | ||
const leastRecentDominantSpeaker = sortedOnscreenParticipants[0]; | ||
const newDominantSpeakerWithStartTime = newParticipantsArray.find(p => p.participant === dominantSpeaker); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I don't think we need to save a new variable, since it's going to be the same object reference as newDominantSpeaker
.
With this we can just use newDominantSpeaker
in the following lines (without the !
since TypeScript knows we can't get to this point without this variable thanks to the return
on line 35.
/** Reorder the onscreen participants */
// Temporarily remove the newest dominant speaker:
newParticipantsArray.splice(newParticipantsArray.indexOf(newDominantSpeaker), 1);
// Remove the least recent dominant speaker and replace them with the newest:
newParticipantsArray.splice(newParticipantsArray.indexOf(leastRecentDominantSpeaker), 1, newDominantSpeaker);
// Add the least recent dominant speaker back into the array after the last onscreen participant.
newParticipantsArray.splice(maxGridParticipants - 1, 0, leastRecentDominantSpeaker);
export default function useGridParticipants() { | ||
const [orderedParticipants, setOrderedParticipants] = useState<OrderedParticipant[]>([]); | ||
const { room } = useVideoContext(); | ||
const dominantSpeaker = useDominantSpeaker(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to includeNull
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, looks like it isn't needed. I thought it was so that when the dominant speaker stops talking (and becomes null), they wouldn't be highlighted in green any more. Now I realize that by passing includeNull
in the <GridView />
component, this is taken care of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! My only thought is that some comments in useGridParticipant still refer to 'onscreen' participants, while the new variable name is firstPageParticipants
.
Contributing to Twilio
Pull Request Details
JIRA link(s):
Description
This PR adds the dominantSpeaker functionality for grid mode. The dominant speaker UI adds a green highlight around the participant's video when they are actively speaking.
Burndown
Before review
npm test
Before merge