Skip to content

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

Merged
merged 22 commits into from
May 19, 2022

Conversation

olipyskoty
Copy link
Contributor

Contributing to Twilio

All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under.

  • I acknowledge that all my contributions will be made under the project's license.

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.

  • When the dominantSpeaker is "on screen" (on the first page of the grid), their video will be outlined in green, and their position in the grid will not change.
  • When the dominantSpeaker is "off screen" (not on the first page of the grid), their video will be outlined in green and they will replace the least recent (oldest) dominantSpeaker on the first page. The least recent dominantSpeaker is moved to the end of the participant list.

gridModeDS

Burndown

Before review

  • Updated CHANGELOG.md if necessary
  • Added unit tests if necessary
  • Updated affected documentation
  • Verified locally with npm test
  • Manually sanity tested running locally
  • Included screenshot as PR comment (if needed)
  • Ready for review

Before merge

  • Got one or more +1s
  • Re-tested if necessary

@olipyskoty olipyskoty requested review from timrozum and timmydoza May 10, 2022 19:00
1,
newDominantSpeakerWithStartTime!
);
// Add the least recent dominant speaker back into the array at the end:
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

@timmydoza timmydoza left a 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.

Comment on lines 16 to 18
if (newDominantSpeaker !== null || (newDominantSpeaker === null && includeNull)) {
setDominantSpeaker(newDominantSpeaker);
}
Copy link
Contributor

@timmydoza timmydoza May 11, 2022

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:

Suggested change
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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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', () => {

Comment on lines +40 to 54
// 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;
}
}
Copy link
Contributor

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';
Copy link
Contributor

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';
Copy link
Contributor

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;
Copy link
Contributor

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;
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@timmydoza timmydoza left a 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.

@olipyskoty olipyskoty merged commit e923527 into feature/grid-mode May 19, 2022
@olipyskoty olipyskoty deleted the VIDEO-8472-add-dominant-speaker branch May 19, 2022 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants