Skip to content
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

Grid mode use sdk v3 #677

Draft
wants to merge 53 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
138756c
Grid mode (#651)
Mar 8, 2022
58ef66c
Grid mode pagination (#654)
Mar 14, 2022
3eb727c
Install twilio-video version 3 rc
May 11, 2022
aa97f7e
Change VideoBandwidthProfileOptions to VideoBandwidthProfile
May 11, 2022
fa66c75
Update useIsTrackEnabled to handle switchedOn and switchedOff events
May 11, 2022
cf8eaaf
Update useMediaStreamTrack to handle switchedOn and switchedOff events
May 11, 2022
2f5db14
Add optional chaining to fix error
May 11, 2022
378d7e9
Install new twilo-video RC
May 12, 2022
c9aa011
Use correct event name
May 12, 2022
87d26b8
Add backend functions that create large group rooms
May 17, 2022
088861d
Add local track support to useIsTrackEnabled
May 17, 2022
c1d04db
Reduce max grid participants to 4
May 17, 2022
51116cd
Blur switched off videos
May 17, 2022
7823c13
Add useTrackSwitchOffReason hook
May 17, 2022
f5486ed
Remove AudioLevelIndicator
May 17, 2022
cbfbce3
Show avatar for unpublished video tracks
May 17, 2022
048511b
Use MediaRegion us2 when creating rooms
May 18, 2022
e923527
Video 8472 add dominant speaker (#676)
olipyskoty May 19, 2022
a053875
Use twilio-video 3.0.0-rc4 and enable SDK logging
May 20, 2022
80f24e9
Video 8473 mobile grid mode (#681)
olipyskoty May 24, 2022
e4e1ffd
Video 10096 persist grid settings (#684)
Jun 1, 2022
9dab5ba
Handle screenshare in grid mode (#683)
Jun 1, 2022
adb57c0
Merge branch 'feature/grid-mode' into grid-mode-use-sdk-v3
Jun 1, 2022
3c3ae0e
Use twilio-video 3.0.0 rc5
Jun 1, 2022
abaccd5
Add new settings for testing
Jun 1, 2022
0146bf5
Add some testing info to participant tile
Jun 7, 2022
05ff339
Merge branch 'master' into feature/grid-mode
Jun 7, 2022
0b91cbb
Add updated room monitor tarball
Jun 7, 2022
b913d5e
Finalize grid mode (#699)
olipyskoty Jun 10, 2022
1f30620
Dogfood feedback (#705)
olipyskoty Jun 17, 2022
0ec7685
Merge branch 'master' into feature/grid-mode
Jun 21, 2022
d096932
Merge branch 'feature/grid-mode' into grid-mode-use-sdk-v3
Jun 21, 2022
983e818
Grid mode bugfix (#707)
Jun 22, 2022
10cfac9
Add Chrome 103 fix for AudioLevelIndicator
Jun 28, 2022
77e030e
Add fix for usePresentationParticipants
Jun 28, 2022
ec4770b
Merge branch 'master' into feature/grid-mode
Jun 29, 2022
e4cd381
Video 10448 dogfood feedback (#716)
olipyskoty Jun 30, 2022
00afa5e
Pinned participant fix (#717)
olipyskoty Jul 1, 2022
43753f0
Update SDK to RC6
Jul 1, 2022
9fcae2f
Turn off debug logging
Jul 5, 2022
f251575
prep release (#719)
olipyskoty Jul 5, 2022
f8e3c80
Update server to handle stage
Jul 13, 2022
e12d30e
Merge branch 'master' into feature/grid-mode
Jul 14, 2022
8e62a10
Merge branch 'feature/grid-mode' into grid-mode-use-sdk-v3
Jul 14, 2022
261a151
Install rc7 sdk
Jul 14, 2022
129d5bc
Update to rc8
Jul 20, 2022
9bc1b39
Update app to use large rooms branch from the pluing-rtc repo
Jul 22, 2022
df817ce
Only set maxSwitched on tracks when exlicitly passed as query parameters
Jul 22, 2022
f2f8ad0
Add package-lock
Jul 22, 2022
80eec7f
upgrade twilio-video to preview
olipyskoty Aug 1, 2022
3cf0024
Use video sdk rc 10
Aug 19, 2022
e06f956
Use kilobits for max bitrate values
Aug 23, 2022
f524fd7
Update twilio video SDK RC version
Aug 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
{
name: '@storybook/addon-essentials',
options: { backgrounds: false, docs: false },
},
'@storybook/preset-create-react-app',
],
framework: '@storybook/react',
webpackFinal: config => {
config.resolve.alias['twilio-video'] = require.resolve('../src/stories/mocks/twilio-video.js');
return config;
},
};
15 changes: 15 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { decorator as TwilioVideoMockDecorator } from '../src/stories/mocks/twilio-video.js';

// Add the decorator to all stories
export const decorators = [TwilioVideoMockDecorator];

export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
layout: 'fullscreen',
};
62,596 changes: 36,518 additions & 26,078 deletions package-lock.json

Large diffs are not rendered by default.

27 changes: 23 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@twilio-labs/plugin-rtc": "^0.8.2",
"@twilio/conversations": "^1.2.3",
"@twilio/video-processors": "^1.0.1",
Expand Down Expand Up @@ -43,10 +44,16 @@
"strip-color": "^0.1.0",
"ts-node": "^9.1.1",
"twilio": "^3.63.1",
"twilio-video": "^2.20.1",
"typescript": "^3.8.3"
"twilio-video": "https://github.com/twilio/twilio-video.js/archive/refs/tags/3.0.0-rc3.tar.gz",
"typescript": "^4.6.2"
},
"devDependencies": {
"@storybook/addon-actions": "^6.4.18",
"@storybook/addon-essentials": "^6.4.18",
"@storybook/addon-links": "^6.4.18",
"@storybook/node-logger": "^6.4.18",
"@storybook/preset-create-react-app": "^3.2.0",
"@storybook/react": "^6.4.18",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/react-hooks": "^3.2.1",
Expand Down Expand Up @@ -87,7 +94,9 @@
"cypress:open": "cypress open",
"cypress:run": "cypress run --browser chrome",
"cypress:ci": "cross-env CYPRESS_baseUrl=http://localhost:8081 start-server-and-test server http://localhost:8081 cypress:run",
"deploy:twilio-cli": "cross-env REACT_APP_SET_AUTH=passcode npm run build && twilio rtc:apps:video:deploy --authentication=passcode --app-directory ./build"
"deploy:twilio-cli": "cross-env REACT_APP_SET_AUTH=passcode npm run build && twilio rtc:apps:video:deploy --authentication=passcode --app-directory ./build",
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook -s public"
},
"eslintConfig": {
"extends": "react-app",
Expand All @@ -96,7 +105,17 @@
"@typescript-eslint/no-shadow": [
"warn"
]
}
},
"overrides": [
{
"files": [
"**/*.stories.*"
],
"rules": {
"import/no-anonymous-default-export": "off"
}
}
]
},
"browserslist": {
"production": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import VideoInputList from './VideoInputList/VideoInputList';
import MaxGridParticipants from './MaxGridParticipants/MaxGridParticipants';

const useStyles = makeStyles((theme: Theme) => ({
container: {
Expand Down Expand Up @@ -70,6 +71,13 @@ export default function DeviceSelectionDialog({ open, onClose }: { open: boolean
<div className={classes.listSection}>
<AudioOutputList />
</div>
<Divider />
<div className={classes.listSection}>
<Typography variant="h6" className={classes.headline}>
Grid View
</Typography>
<MaxGridParticipants />
</div>
</DialogContent>
<Divider />
<DialogActions>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { FormControl, MenuItem, Typography, Select, Grid } from '@material-ui/core';
import { useAppState } from '../../../state';

const MAX_PARTICIPANT_OPTIONS = [9, 16, 25, 36, 49];

export default function MaxGridParticipants() {
const { maxGridParticipants, setMaxGridParticipants } = useAppState();

return (
<div>
<Typography variant="subtitle2" gutterBottom>
Max Grid Participants
</Typography>
<Grid container alignItems="center" justifyContent="space-between">
<div className="inputSelect">
<FormControl fullWidth>
<Select
onChange={e => setMaxGridParticipants(parseInt(e.target.value as string))}
value={maxGridParticipants}
variant="outlined"
>
{MAX_PARTICIPANT_OPTIONS.map(option => (
<MenuItem value={option} key={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</Grid>
</div>
);
}
107 changes: 107 additions & 0 deletions src/components/GridView/GridView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { GridView } from './GridView';
import { shallow } from 'enzyme';
import useGridLayout from '../../hooks/useGridLayout/useGridLayout';
import { usePagination } from '../../hooks/usePagination/usePagination';

const mockLocalParticipant = { identity: 'test-local-participant', sid: 0 };
const mockParticipants = [
{ identity: 'test-participant-1', sid: 1 },
{ identity: 'test-participant-2', sid: 2 },
{ identity: 'test-participant-3', sid: 3 },
{ identity: 'test-participant-4', sid: 4 },
];

jest.mock('../../constants', () => ({
GRID_MODE_ASPECT_RATIO: 9 / 16,
GRID_MODE_MARGIN: 3,
}));
jest.mock('../../hooks/useParticipants/useParticipants', () => () => mockParticipants);
jest.mock('../../hooks/useVideoContext/useVideoContext', () => () => ({
room: {
localParticipant: mockLocalParticipant,
},
}));
jest.mock('../../hooks/useGridLayout/useGridLayout', () =>
jest.fn(() => ({
participantVideoWidth: 720,
containerRef: { current: null },
}))
);

jest.mock('../../hooks/usePagination/usePagination', () => ({
usePagination: jest.fn(() => ({
currentPage: 2,
totalPages: 4,
setCurrentPage: jest.fn(),
paginatedParticipants: [mockLocalParticipant, ...mockParticipants],
})),
}));

const mockUsePagination = usePagination as jest.Mock<any>;

describe('the GridView component', () => {
it('should render correctly', () => {
const wrapper = shallow(<GridView />);
expect(wrapper).toMatchSnapshot();
expect(useGridLayout).toHaveBeenCalledWith(5);
});

it('should not render the previous page button when the user is viewing the first page', () => {
mockUsePagination.mockImplementationOnce(() => ({
currentPage: 1,
totalPages: 4,
setCurrentPage: jest.fn(),
paginatedParticipants: [mockLocalParticipant, ...mockParticipants],
}));

const wrapper = shallow(<GridView />);
expect(
wrapper
.find('.makeStyles-buttonContainerLeft-4')
.childAt(0)
.exists()
).toBe(false);
expect(
wrapper
.find('.makeStyles-buttonContainerRight-5')
.childAt(0)
.exists()
).toBe(true);
});

it('should not render the next page button when the user is viewing the last page', () => {
mockUsePagination.mockImplementationOnce(() => ({
currentPage: 4,
totalPages: 4,
setCurrentPage: jest.fn(),
paginatedParticipants: [mockLocalParticipant, ...mockParticipants],
}));

const wrapper = shallow(<GridView />);
expect(
wrapper
.find('.makeStyles-buttonContainerLeft-4')
.childAt(0)
.exists()
).toBe(true);
expect(
wrapper
.find('.makeStyles-buttonContainerRight-5')
.childAt(0)
.exists()
).toBe(false);
});

it('should not render the Pagination component when there is only one page', () => {
mockUsePagination.mockImplementationOnce(() => ({
currentPage: 1,
totalPages: 1,
setCurrentPage: jest.fn(),
paginatedParticipants: [mockLocalParticipant, ...mockParticipants],
}));

const wrapper = shallow(<GridView />);
expect(wrapper.find('.makeStyles-pagination-8').exists()).toBe(false);
});
});
134 changes: 134 additions & 0 deletions src/components/GridView/GridView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React from 'react';
import ArrowBack from '@material-ui/icons/ArrowBack';
import ArrowForward from '@material-ui/icons/ArrowForward';
import clsx from 'clsx';
import { GRID_MODE_ASPECT_RATIO, GRID_MODE_MARGIN } from '../../constants';
import { IconButton, makeStyles } from '@material-ui/core';
import { Pagination } from '@material-ui/lab';
import Participant from '../Participant/Participant';
import useGridLayout from '../../hooks/useGridLayout/useGridLayout';
import useParticipants from '../../hooks/useParticipants/useParticipants';
import useVideoContext from '../../hooks/useVideoContext/useVideoContext';
import { usePagination } from '../../hooks/usePagination/usePagination';

const CONTAINER_GUTTER = '50px';

const useStyles = makeStyles({
container: {
position: 'relative',
gridArea: '1 / 1 / 2 / 3',
},
participantContainer: {
position: 'absolute',
display: 'flex',
top: CONTAINER_GUTTER,
right: CONTAINER_GUTTER,
bottom: CONTAINER_GUTTER,
left: CONTAINER_GUTTER,
margin: '0 auto',
alignContent: 'center',
flexWrap: 'wrap',
justifyContent: 'center',
},
buttonContainer: {
position: 'absolute',
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},

buttonContainerLeft: {
right: `calc(100% - ${CONTAINER_GUTTER})`,
left: 0,
},
buttonContainerRight: {
right: 0,
left: `calc(100% - ${CONTAINER_GUTTER})`,
},
pagination: {
'& .MuiPaginationItem-root': {
color: 'white',
},
},
paginationButton: {
color: 'black',
background: 'rgba(255, 255, 255, 0.8)',
width: '40px',
height: '40px',
'&:hover': {
background: 'rgba(255, 255, 255)',
},
},
paginationContainer: {
position: 'absolute',
top: `calc(100% - ${CONTAINER_GUTTER})`,
right: 0,
bottom: 0,
left: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
});

export function GridView() {
const classes = useStyles();
const { room } = useVideoContext();
const participants = useParticipants();

const { paginatedParticipants, setCurrentPage, currentPage, totalPages } = usePagination([
room!.localParticipant,
...participants,
]);

const { participantVideoWidth, containerRef } = useGridLayout(paginatedParticipants.length);

const participantWidth = `${participantVideoWidth}px`;
const participantHeight = `${Math.floor(participantVideoWidth * GRID_MODE_ASPECT_RATIO)}px`;

return (
<div className={classes.container}>
<div className={clsx(classes.buttonContainer, classes.buttonContainerLeft)}>
{!(currentPage === 1) && (
<IconButton className={classes.paginationButton} onClick={() => setCurrentPage(page => page - 1)}>
<ArrowBack />
</IconButton>
)}
</div>
<div className={clsx(classes.buttonContainer, classes.buttonContainerRight)}>
{!(currentPage === totalPages) && (
<IconButton className={classes.paginationButton} onClick={() => setCurrentPage(page => page + 1)}>
<ArrowForward />
</IconButton>
)}
</div>
<div className={classes.paginationContainer}>
{totalPages > 1 && (
<Pagination
className={classes.pagination}
page={currentPage}
count={totalPages}
onChange={(_, value) => setCurrentPage(value)}
shape="rounded"
color="primary"
size="small"
hidePrevButton
hideNextButton
/>
)}
</div>
<div className={classes.participantContainer} ref={containerRef}>
{paginatedParticipants.map(participant => (
<div
key={participant.sid}
style={{ width: participantWidth, height: participantHeight, margin: GRID_MODE_MARGIN }}
>
<Participant participant={participant} isLocalParticipant={participant === room!.localParticipant} />
</div>
))}
</div>
</div>
);
}
Loading