From 32ea63bad0bd3a93ece18bdca2c78e8a0c3be961 Mon Sep 17 00:00:00 2001 From: Olivia Pyskoty Date: Wed, 13 Jul 2022 10:02:08 -0700 Subject: [PATCH 1/5] storybook changes to turn off video one at a time --- src/stories/App.stories.jsx | 12 ++++++------ src/stories/mocks/twilio-video.js | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/stories/App.stories.jsx b/src/stories/App.stories.jsx index daa0311c5..016e20deb 100644 --- a/src/stories/App.stories.jsx +++ b/src/stories/App.stories.jsx @@ -20,11 +20,11 @@ export default { unpublishAllAudio: { control: { type: 'boolean' }, }, - unpublishAllVideo: { - control: { type: 'boolean' }, + unpublishVideo: { + control: { type: 'text' }, }, - switchOffAllVideo: { - control: { type: 'boolean' }, + switchOffVideo: { + control: { type: 'text' }, }, }, }; @@ -38,6 +38,6 @@ Prod.args = { presentationParticipant: null, disableAllAudio: false, unpublishAllAudio: false, - unpublishAllVideo: false, - switchOffAllVideo: false, + unpublishVideo: null, + switchOffVideo: null, }; diff --git a/src/stories/mocks/twilio-video.js b/src/stories/mocks/twilio-video.js index cb1680a40..40165ed3c 100644 --- a/src/stories/mocks/twilio-video.js +++ b/src/stories/mocks/twilio-video.js @@ -223,20 +223,24 @@ export function decorator(story, { args }) { videoTrack?.enable(); } - if (args.unpublishAllAudio) { - mockParticipant.unpublishTrack('audio'); - } else { - mockParticipant.publishTrack('audio'); - } - - if (args.unpublishAllVideo) { - mockParticipant.unpublishTrack('video'); + if (args.unpublishVideo) { + const pList = args.unpublishVideo.split(','); + if (pList.includes(i.toString())) { + mockParticipant.unpublishTrack('video'); + } else { + mockParticipant.publishTrack('video'); + } } else { mockParticipant.publishTrack('video'); } - if (args.switchOffAllVideo) { - videoTrack?.switchOff(); + if (args.switchOffVideo) { + const pList = args.switchOffVideo.split(','); + if (pList.includes(i.toString())) { + videoTrack?.switchOff(); + } else { + videoTrack?.switchOn(); + } } else { videoTrack?.switchOn(); } From 349498ecf2e53d78ba76c57a965698151990fdef Mon Sep 17 00:00:00 2001 From: Olivia Pyskoty Date: Wed, 13 Jul 2022 10:02:21 -0700 Subject: [PATCH 2/5] add track switch off blur UI --- .../MainParticipantInfo.test.tsx | 4 +- .../MainParticipantInfo.tsx | 50 ++++++++++++++++++- .../ParticipantInfo/ParticipantInfo.test.tsx | 5 +- .../ParticipantInfo/ParticipantInfo.tsx | 47 ++++++++++++++++- 4 files changed, 98 insertions(+), 8 deletions(-) diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx index d77866a70..122fb6624 100644 --- a/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx +++ b/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx @@ -60,12 +60,12 @@ describe('the MainParticipantInfo component', () => { expect(wrapper.find(AvatarIcon).exists()).toBe(false); }); - it('should render the AvatarIcon component when video is switched off', () => { + it('should display the video has been switched off message when the video track is switchedOff', () => { mockUseIsTrackSwitchedOff.mockImplementationOnce(() => true); const wrapper = shallow( mock children ); - expect(wrapper.find(AvatarIcon).exists()).toBe(true); + expect(wrapper.text()).toContain('Video has been switched off to conserve bandwidth.'); }); it('should not render the reconnecting UI when the user is connected', () => { diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.tsx index cf70c46a9..b5f3520fd 100644 --- a/src/components/MainParticipantInfo/MainParticipantInfo.tsx +++ b/src/components/MainParticipantInfo/MainParticipantInfo.tsx @@ -22,6 +22,10 @@ const useStyles = makeStyles((theme: Theme) => ({ position: 'relative', display: 'flex', alignItems: 'center', + '& video': { + filter: 'none', + transition: 'filter 1s cubic-bezier(0.22, 0.61, 0.36, 1)', + }, }, identity: { background: 'rgba(0, 0, 0, 0.5)', @@ -52,6 +56,18 @@ const useStyles = makeStyles((theme: Theme) => ({ background: 'rgba(40, 42, 43, 0.75)', zIndex: 1, }, + trackSwitchOffContainer: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + zIndex: 1, + textAlign: 'center', + }, fullWidth: { gridArea: '1 / 1 / 2 / 3', [theme.breakpoints.down('sm')]: { @@ -89,6 +105,30 @@ const useStyles = makeStyles((theme: Theme) => ({ top: 0, }, }, + switchedOffMessage: { + opacity: 0, + textShadow: '0 0 3px rgba(0, 0, 0, 0.7)', + animationName: '$showMessage', + animationDuration: '0.5s', + animationDelay: '2s', + animationFillMode: 'forwards', + color: 'white', + }, + '@keyframes showMessage': { + '0%': { + opacity: 0, + visibility: 'hidden', + }, + '100%': { + opacity: 1, + visibility: 'visible', + }, + }, + blur: { + '& video': { + filter: 'blur(10px)', + }, + }, circle: { height: '12px', width: '12px', @@ -145,6 +185,7 @@ export default function MainParticipantInfo({ participant, children }: MainParti data-cy-participant={participant.identity} className={clsx(classes.container, { [classes.fullWidth]: !isRemoteParticipantScreenSharing, + [classes.blur]: isVideoSwitchedOff, })} >
@@ -173,11 +214,18 @@ export default function MainParticipantInfo({ participant, children }: MainParti )}
- {(!isVideoEnabled || isVideoSwitchedOff) && ( + {!isVideoEnabled && (
)} + {isVideoSwitchedOff && ( +
+ + Video has been switched off to conserve bandwidth. + +
+ )} {isParticipantReconnecting && (
diff --git a/src/components/ParticipantInfo/ParticipantInfo.test.tsx b/src/components/ParticipantInfo/ParticipantInfo.test.tsx index e3ad063cf..6ff492b2f 100644 --- a/src/components/ParticipantInfo/ParticipantInfo.test.tsx +++ b/src/components/ParticipantInfo/ParticipantInfo.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import AvatarIcon from '../../icons/AvatarIcon'; import ParticipantInfo from './ParticipantInfo'; import PinIcon from './PinIcon/PinIcon'; @@ -43,7 +42,7 @@ describe('the ParticipantInfo component', () => { expect(wrapper.find(AvatarIcon).exists()).toBe(false); }); - it('should render the AvatarIcon component when the video track is switchedOff', () => { + it('should display the video has been switched off message when the video track is switchedOff', () => { mockUseIsTrackSwitchedOff.mockImplementation(() => true); mockUsePublications.mockImplementation(() => [{ trackName: '', kind: 'video' }]); const wrapper = shallow( @@ -51,7 +50,7 @@ describe('the ParticipantInfo component', () => { mock children ); - expect(wrapper.find(AvatarIcon).exists()).toBe(true); + expect(wrapper.text()).toContain('Video has been switched off to conserve bandwidth.'); }); it('should not render the reconnecting UI when the user is connected', () => { diff --git a/src/components/ParticipantInfo/ParticipantInfo.tsx b/src/components/ParticipantInfo/ParticipantInfo.tsx index 6b8227d45..d1ca07e8b 100644 --- a/src/components/ParticipantInfo/ParticipantInfo.tsx +++ b/src/components/ParticipantInfo/ParticipantInfo.tsx @@ -30,6 +30,7 @@ const useStyles = makeStyles((theme: Theme) => marginBottom: '0.5em', '& video': { objectFit: 'contain !important', + transition: 'filter 1s cubic-bezier(0.22, 0.61, 0.36, 1)', }, borderRadius: '4px', border: `${theme.participantBorderWidth}px solid rgb(245, 248, 255)`, @@ -91,6 +92,18 @@ const useStyles = makeStyles((theme: Theme) => background: 'rgba(40, 42, 43, 0.75)', zIndex: 1, }, + trackSwitchOffContainer: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + zIndex: 1, + textAlign: 'center', + }, screenShareIconContainer: { background: 'rgba(0, 0, 0, 0.5)', padding: '0.18em 0.3em', @@ -121,6 +134,29 @@ const useStyles = makeStyles((theme: Theme) => fontSize: '0.75rem', }, }, + switchedOffMessage: { + opacity: 0, + textShadow: '0 0 3px rgba(0, 0, 0, 0.7)', + animationName: '$showMessage', + animationDuration: '0.5s', + animationDelay: '2s', + animationFillMode: 'forwards', + }, + '@keyframes showMessage': { + '0%': { + opacity: 0, + visibility: 'hidden', + }, + '100%': { + opacity: 1, + visibility: 'visible', + }, + }, + blur: { + '& video': { + filter: 'blur(5px)', + }, + }, hideParticipant: { display: 'none', }, @@ -214,8 +250,15 @@ export default function ParticipantInfo({
{isSelected && }
-
- {(!isVideoEnabled || isVideoSwitchedOff) && ( +
+ {isVideoSwitchedOff && ( +
+ + Video has been switched off to conserve bandwidth. + +
+ )} + {!isVideoEnabled && (
From d51653a1b22165e17dee92b7706f662eee94eb06 Mon Sep 17 00:00:00 2001 From: Olivia Pyskoty Date: Wed, 13 Jul 2022 14:31:45 -0700 Subject: [PATCH 3/5] fix missing key warning in storybook --- src/stories/mocks/twilio-video.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stories/mocks/twilio-video.js b/src/stories/mocks/twilio-video.js index 40165ed3c..84d15bfe5 100644 --- a/src/stories/mocks/twilio-video.js +++ b/src/stories/mocks/twilio-video.js @@ -105,6 +105,7 @@ class LocalParticipant extends EventEmitter { ]); this.identity = 'Local Participant'; + this.sid = this.identity; } } @@ -122,6 +123,7 @@ const mockRoom = new MockRoom(); class MockParticipant extends EventEmitter { constructor(name) { super(); + this.sid = name; this.identity = name; this.tracks = new Map([ ['video', new MockPublication('video')], From 2ef452c90106687d3f256c3192b0d421c6dd0e25 Mon Sep 17 00:00:00 2001 From: Olivia Pyskoty Date: Wed, 13 Jul 2022 14:32:14 -0700 Subject: [PATCH 4/5] replace animation with transition CSS for track switch off --- .../MainParticipantInfo.tsx | 26 ++++------ .../ParticipantInfo/ParticipantInfo.tsx | 49 ++++++------------- 2 files changed, 24 insertions(+), 51 deletions(-) diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.tsx index b5f3520fd..91ed20ae8 100644 --- a/src/components/MainParticipantInfo/MainParticipantInfo.tsx +++ b/src/components/MainParticipantInfo/MainParticipantInfo.tsx @@ -24,7 +24,7 @@ const useStyles = makeStyles((theme: Theme) => ({ alignItems: 'center', '& video': { filter: 'none', - transition: 'filter 1s cubic-bezier(0.22, 0.61, 0.36, 1)', + transition: 'filter 0.25s cubic-bezier(0.22, 0.61, 0.36, 1)', }, }, identity: { @@ -67,6 +67,9 @@ const useStyles = makeStyles((theme: Theme) => ({ justifyContent: 'center', zIndex: 1, textAlign: 'center', + opacity: 0, + visibility: 'hidden', + transition: 'all 0.25s cubic-bezier(0.22, 0.61, 0.36, 1)', }, fullWidth: { gridArea: '1 / 1 / 2 / 3', @@ -106,27 +109,18 @@ const useStyles = makeStyles((theme: Theme) => ({ }, }, switchedOffMessage: { - opacity: 0, textShadow: '0 0 3px rgba(0, 0, 0, 0.7)', - animationName: '$showMessage', - animationDuration: '0.5s', - animationDelay: '2s', - animationFillMode: 'forwards', color: 'white', }, - '@keyframes showMessage': { - '0%': { - opacity: 0, - visibility: 'hidden', - }, - '100%': { - opacity: 1, - visibility: 'visible', - }, + isSwitchedOff: { + opacity: 1, + visibility: 'visible', + transition: 'all 0.5s linear 2s', }, blur: { '& video': { filter: 'blur(10px)', + transition: 'filter 1s cubic-bezier(0.22, 0.61, 0.36, 1)', }, }, circle: { @@ -220,7 +214,7 @@ export default function MainParticipantInfo({ participant, children }: MainParti
)} {isVideoSwitchedOff && ( -
+
Video has been switched off to conserve bandwidth. diff --git a/src/components/ParticipantInfo/ParticipantInfo.tsx b/src/components/ParticipantInfo/ParticipantInfo.tsx index d1ca07e8b..c2bbcdf84 100644 --- a/src/components/ParticipantInfo/ParticipantInfo.tsx +++ b/src/components/ParticipantInfo/ParticipantInfo.tsx @@ -2,22 +2,18 @@ import React from 'react'; import clsx from 'clsx'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; import { LocalAudioTrack, LocalVideoTrack, Participant, RemoteAudioTrack, RemoteVideoTrack } from 'twilio-video'; - import AudioLevelIndicator from '../AudioLevelIndicator/AudioLevelIndicator'; import AvatarIcon from '../../icons/AvatarIcon'; import NetworkQualityLevel from '../NetworkQualityLevel/NetworkQualityLevel'; import PinIcon from './PinIcon/PinIcon'; import ScreenShareIcon from '../../icons/ScreenShareIcon'; import Typography from '@material-ui/core/Typography'; - import useIsTrackSwitchedOff from '../../hooks/useIsTrackSwitchedOff/useIsTrackSwitchedOff'; import usePublications from '../../hooks/usePublications/usePublications'; import useTrack from '../../hooks/useTrack/useTrack'; import useParticipantIsReconnecting from '../../hooks/useParticipantIsReconnecting/useParticipantIsReconnecting'; import { useAppState } from '../../state'; - const borderWidth = 2; - const useStyles = makeStyles((theme: Theme) => createStyles({ container: { @@ -30,7 +26,7 @@ const useStyles = makeStyles((theme: Theme) => marginBottom: '0.5em', '& video': { objectFit: 'contain !important', - transition: 'filter 1s cubic-bezier(0.22, 0.61, 0.36, 1)', + transition: 'filter 0.25s cubic-bezier(0.22, 0.61, 0.36, 1)', }, borderRadius: '4px', border: `${theme.participantBorderWidth}px solid rgb(245, 248, 255)`, @@ -103,6 +99,9 @@ const useStyles = makeStyles((theme: Theme) => justifyContent: 'center', zIndex: 1, textAlign: 'center', + opacity: 0, + visibility: 'hidden', + transition: 'all 0.25s cubic-bezier(0.22, 0.61, 0.36, 1)', }, screenShareIconContainer: { background: 'rgba(0, 0, 0, 0.5)', @@ -135,26 +134,17 @@ const useStyles = makeStyles((theme: Theme) => }, }, switchedOffMessage: { - opacity: 0, textShadow: '0 0 3px rgba(0, 0, 0, 0.7)', - animationName: '$showMessage', - animationDuration: '0.5s', - animationDelay: '2s', - animationFillMode: 'forwards', }, - '@keyframes showMessage': { - '0%': { - opacity: 0, - visibility: 'hidden', - }, - '100%': { - opacity: 1, - visibility: 'visible', - }, + isSwitchedOff: { + opacity: 1, + visibility: 'visible', + transition: 'all 0.5s linear 2s', }, blur: { '& video': { filter: 'blur(5px)', + transition: 'filter 1s cubic-bezier(0.22, 0.61, 0.36, 1)', }, }, hideParticipant: { @@ -183,7 +173,6 @@ const useStyles = makeStyles((theme: Theme) => }, }) ); - interface ParticipantInfoProps { participant: Participant; children: React.ReactNode; @@ -193,7 +182,6 @@ interface ParticipantInfoProps { hideParticipant?: boolean; isDominantSpeaker?: boolean; } - export default function ParticipantInfo({ participant, onClick, @@ -204,23 +192,16 @@ export default function ParticipantInfo({ isDominantSpeaker, }: ParticipantInfoProps) { const publications = usePublications(participant); - const audioPublication = publications.find(p => p.kind === 'audio'); const videoPublication = publications.find(p => !p.trackName.includes('screen') && p.kind === 'video'); - const isVideoEnabled = Boolean(videoPublication); const isScreenShareEnabled = publications.find(p => p.trackName.includes('screen')); - const videoTrack = useTrack(videoPublication); const isVideoSwitchedOff = useIsTrackSwitchedOff(videoTrack as LocalVideoTrack | RemoteVideoTrack); - const audioTrack = useTrack(audioPublication) as LocalAudioTrack | RemoteAudioTrack | undefined; const isParticipantReconnecting = useParticipantIsReconnecting(participant); - const { isGalleryViewActive } = useAppState(); - const classes = useStyles(); - return (
{isSelected && }
- {isVideoSwitchedOff && ( -
- - Video has been switched off to conserve bandwidth. - -
- )} +
+ + Video has been switched off to conserve bandwidth. + +
{!isVideoEnabled && (
From ab92a4aaae08474d517695f8ae14fc8a3478c2dd Mon Sep 17 00:00:00 2001 From: Olivia Pyskoty Date: Thu, 14 Jul 2022 11:52:50 -0700 Subject: [PATCH 5/5] remove isVideoSwitchedOff && logic from MainParticipantInfo --- .../MainParticipantInfo/MainParticipantInfo.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.tsx index 91ed20ae8..26070fe79 100644 --- a/src/components/MainParticipantInfo/MainParticipantInfo.tsx +++ b/src/components/MainParticipantInfo/MainParticipantInfo.tsx @@ -213,13 +213,11 @@ export default function MainParticipantInfo({ participant, children }: MainParti
)} - {isVideoSwitchedOff && ( -
- - Video has been switched off to conserve bandwidth. - -
- )} +
+ + Video has been switched off to conserve bandwidth. + +
{isParticipantReconnecting && (