diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx index d77866a70..a1a95eb8d 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 Low bandwidth 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('Low 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..d53cdae21 100644 --- a/src/components/MainParticipantInfo/MainParticipantInfo.tsx +++ b/src/components/MainParticipantInfo/MainParticipantInfo.tsx @@ -8,6 +8,7 @@ import AvatarIcon from '../../icons/AvatarIcon'; import NetworkQualityLevel from '../NetworkQualityLevel/NetworkQualityLevel'; import Tooltip from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; +import Fade from '@material-ui/core/Fade'; import useIsRecording from '../../hooks/useIsRecording/useIsRecording'; import useIsTrackSwitchedOff from '../../hooks/useIsTrackSwitchedOff/useIsTrackSwitchedOff'; @@ -58,7 +59,7 @@ const useStyles = makeStyles((theme: Theme) => ({ gridArea: '1 / 1 / 3 / 3', }, }, - avatarContainer: { + videoOffContainer: { display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -108,6 +109,12 @@ const useStyles = makeStyles((theme: Theme) => ({ background: '#A90000', }, }, + switchOffMessage: { + background: '#1F304C', + borderRadius: '100px', + padding: '0.3em 1.5em', + color: '#FFFFFF', + }, })); interface MainParticipantInfoProps { @@ -173,8 +180,18 @@ export default function MainParticipantInfo({ participant, children }: MainParti )} - {(!isVideoEnabled || isVideoSwitchedOff) && ( -
+ {isVideoSwitchedOff && ( +
+ + + Low bandwidth + + +
+ )} + + {!isVideoEnabled && ( +
)} diff --git a/src/components/ParticipantInfo/ParticipantInfo.test.tsx b/src/components/ParticipantInfo/ParticipantInfo.test.tsx index e3ad063cf..4e75d8077 100644 --- a/src/components/ParticipantInfo/ParticipantInfo.test.tsx +++ b/src/components/ParticipantInfo/ParticipantInfo.test.tsx @@ -43,7 +43,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 Low bandwidth message when the video track is switchedOff', () => { mockUseIsTrackSwitchedOff.mockImplementation(() => true); mockUsePublications.mockImplementation(() => [{ trackName: '', kind: 'video' }]); const wrapper = shallow( @@ -51,7 +51,7 @@ describe('the ParticipantInfo component', () => { mock children ); - expect(wrapper.find(AvatarIcon).exists()).toBe(true); + expect(wrapper.text()).toContain('Low 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..63c80b6ef 100644 --- a/src/components/ParticipantInfo/ParticipantInfo.tsx +++ b/src/components/ParticipantInfo/ParticipantInfo.tsx @@ -15,6 +15,7 @@ import usePublications from '../../hooks/usePublications/usePublications'; import useTrack from '../../hooks/useTrack/useTrack'; import useParticipantIsReconnecting from '../../hooks/useParticipantIsReconnecting/useParticipantIsReconnecting'; import { useAppState } from '../../state'; +import { Fade } from '@material-ui/core'; const borderWidth = 2; @@ -62,7 +63,7 @@ const useStyles = makeStyles((theme: Theme) => background: 'transparent', top: 0, }, - avatarContainer: { + videoOffContainer: { display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -145,6 +146,12 @@ const useStyles = makeStyles((theme: Theme) => dominantSpeaker: { border: `solid ${borderWidth}px #7BEAA5`, }, + switchOffMessage: { + background: '#1F304C', + borderRadius: '100px', + padding: '0.3em 1.5em', + color: '#FFFFFF', + }, }) ); @@ -214,9 +221,20 @@ export default function ParticipantInfo({
{isSelected && }
+
- {(!isVideoEnabled || isVideoSwitchedOff) && ( -
+ {isVideoSwitchedOff && ( +
+ + + Low bandwidth + + +
+ )} + + {!isVideoEnabled && ( +
)} diff --git a/src/icons/AvatarIcon.tsx b/src/icons/AvatarIcon.tsx index b6ff09fb3..29e476911 100644 --- a/src/icons/AvatarIcon.tsx +++ b/src/icons/AvatarIcon.tsx @@ -2,15 +2,13 @@ import React from 'react'; export default function AvatarIcon() { return ( - - - - - + + ); } 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..81899c812 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')], @@ -223,20 +225,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 participantList = args.unpublishVideo.split(','); + if (participantList.includes(i.toString())) { + mockParticipant.unpublishTrack('video'); + } else { + mockParticipant.publishTrack('video'); + } } else { mockParticipant.publishTrack('video'); } - if (args.switchOffAllVideo) { - videoTrack?.switchOff(); + if (args.switchOffVideo) { + const participantList = args.switchOffVideo.split(','); + if (participantList.includes(i.toString())) { + videoTrack?.switchOff(); + } else { + videoTrack?.switchOn(); + } } else { videoTrack?.switchOn(); }