bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/timer/indicator/component.tsx

237 lines
7.0 KiB
TypeScript
Raw Normal View History

2024-01-17 19:29:19 +08:00
import { useSubscription, useMutation } from '@apollo/client';
2023-09-25 21:04:28 +08:00
import React, { useEffect, useRef, useState } from 'react';
2024-04-30 23:45:05 +08:00
import GET_TIMER, { GetTimerResponse } from '../../../core/graphql/queries/timer';
2023-09-25 21:04:28 +08:00
import logger from '/imports/startup/client/logger';
import Styled from './styles';
import Icon from '/imports/ui/components/common/icon/icon-ts/component';
import humanizeSeconds from '/imports/utils/humanizeSeconds';
import useTimeSync from '/imports/ui/core/local-states/useTimeSync';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
2024-04-30 23:45:05 +08:00
import { layoutSelectInput } from '../../layout/context';
import { Input } from '../../layout/layoutTypes';
import { TIMER_START, TIMER_STOP } from '../mutations';
import useTimer from '/imports/ui/core/hooks/useTImer';
2023-09-25 21:04:28 +08:00
const CDN = window.meetingClientSettings.public.app.cdn;
const BASENAME = window.meetingClientSettings.public.app.basename;
2023-09-25 21:04:28 +08:00
const HOST = CDN + BASENAME;
const trackName = window.meetingClientSettings.public.timer.music;
2023-09-25 21:04:28 +08:00
interface TimerIndicatorProps {
passedTime: number;
stopwatch: boolean;
songTrack: string;
running: boolean;
isModerator: boolean;
sidebarNavigationIsOpen: boolean;
sidebarContentIsOpen: boolean;
startedOn: number;
2023-09-25 21:04:28 +08:00
}
type ObjectKey = keyof typeof trackName;
2023-09-25 21:04:28 +08:00
const TimerIndicator: React.FC<TimerIndicatorProps> = ({
passedTime,
stopwatch,
songTrack,
running,
isModerator,
sidebarNavigationIsOpen,
sidebarContentIsOpen,
startedOn,
2023-09-25 21:04:28 +08:00
}) => {
const [time, setTime] = useState<number>(0);
const timeRef = useRef<HTMLSpanElement>(null);
const intervalRef = useRef<ReturnType<typeof setInterval>>();
const alarm = useRef<HTMLAudioElement>();
const music = useRef<HTMLAudioElement>();
const triggered = useRef<boolean>(true);
const alreadyNotified = useRef<boolean>(false);
2024-01-17 19:29:19 +08:00
const [startTimerMutation] = useMutation(TIMER_START);
const [stopTimerMutation] = useMutation(TIMER_STOP);
2024-03-01 01:54:59 +08:00
const [songTrackState, setSongTrackState] = useState<string>(songTrack);
2024-01-17 19:29:19 +08:00
const startTimer = () => {
startTimerMutation();
};
const stopTimer = () => {
stopTimerMutation();
2024-01-17 19:29:19 +08:00
};
2023-09-25 21:04:28 +08:00
useEffect(() => {
2024-03-01 01:54:59 +08:00
if (songTrackState !== songTrack) {
if (music.current) music.current.pause();
}
2023-09-25 21:04:28 +08:00
if (songTrack in trackName) {
music.current = new Audio(`${HOST}/resources/sounds/${trackName[songTrack as ObjectKey]}.mp3`);
2024-03-01 01:54:59 +08:00
setSongTrackState(songTrack);
2023-09-25 21:04:28 +08:00
music.current.addEventListener('timeupdate', () => {
const buffer = 0.19;
// Start playing the music before it ends to make the loop gapless
if (!music.current) return null;
if (music.current.currentTime > music.current.duration - buffer) {
music.current.currentTime = 0;
music.current.play();
}
return null;
});
}
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
if (music.current) music.current.pause();
};
2024-03-01 01:54:59 +08:00
}, [songTrack]);
useEffect(() => {
setTime(passedTime);
}, []);
2024-03-01 01:54:59 +08:00
useEffect(() => {
alarm.current = new Audio(`${HOST}/resources/sounds/alarm.mp3`);
2023-09-25 21:04:28 +08:00
}, []);
useEffect(() => {
if (running) {
setTime(passedTime);
intervalRef.current = setInterval(() => {
setTime((prev) => {
if (stopwatch) return (Math.round(prev / 1000) * 1000) + 1000;
const t = (Math.floor(prev / 1000) * 1000) - 1000;
if (t <= 0) {
if (!alreadyNotified.current) {
triggered.current = false;
alreadyNotified.current = true;
if (alarm.current) alarm.current.play();
}
}
return t < 0 ? 0 : t;
});
}, 1000);
} else if (!running) {
clearInterval(intervalRef.current);
}
}, [running]);
useEffect(() => {
2024-01-19 04:20:28 +08:00
if (!running) return;
2024-01-18 22:09:13 +08:00
const timePassed = passedTime >= 0 ? passedTime : 0;
2023-09-25 21:04:28 +08:00
setTime((prev) => {
2024-01-18 22:09:13 +08:00
if (timePassed < prev) return timePassed;
if (timePassed > prev) return timePassed;
2023-09-25 21:04:28 +08:00
return prev;
});
}, [passedTime, stopwatch, startedOn]);
2023-09-25 21:04:28 +08:00
useEffect(() => {
if (!timeRef.current) {
if (intervalRef.current) clearInterval(intervalRef.current);
if (music.current) music.current.pause();
if (alarm.current) alarm.current.pause();
}
}, [time]);
2024-03-01 01:54:59 +08:00
useEffect(() => {
2024-03-01 21:50:39 +08:00
if (running && songTrack !== 'noTrack') {
2024-03-01 01:54:59 +08:00
if (music.current) music.current.play();
2024-03-01 21:50:39 +08:00
} else if (!running || songTrack === 'noTrack') {
2024-03-01 01:54:59 +08:00
if (music.current) music.current.pause();
}
if (running && alreadyNotified.current) {
alreadyNotified.current = false;
}
}, [running, songTrackState]);
2023-10-10 22:18:01 +08:00
useEffect(() => {
if (startedOn === 0) {
2023-10-10 22:18:01 +08:00
setTime(passedTime);
}
}, [startedOn]);
2023-10-10 22:18:01 +08:00
2023-09-25 21:04:28 +08:00
const onClick = running ? stopTimer : startTimer;
return (
<Styled.TimerWrapper>
<Styled.Timer>
<Styled.TimerButton
running={running}
disabled={!isModerator}
hide={sidebarNavigationIsOpen && sidebarContentIsOpen}
role="button"
tabIndex={0}
onClick={isModerator ? onClick : () => {}}
data-test="timeIndicator"
2023-09-25 21:04:28 +08:00
>
<Styled.TimerContent>
<Styled.TimerIcon>
<Icon iconName="time" />
</Styled.TimerIcon>
<Styled.TimerTime
aria-hidden
ref={timeRef}
>
{humanizeSeconds(Math.floor(time / 1000))}
</Styled.TimerTime>
</Styled.TimerContent>
</Styled.TimerButton>
</Styled.Timer>
</Styled.TimerWrapper>
);
};
const TimerIndicatorContainer: React.FC = () => {
const { data: currentUser } = useCurrentUser((u) => ({
2023-09-25 21:04:28 +08:00
isModerator: u.isModerator,
}));
const {
data: timerData,
2024-04-30 23:45:05 +08:00
} = useTimer();
2023-09-25 21:04:28 +08:00
const [timeSync] = useTimeSync();
const sidebarNavigation = layoutSelectInput((i: Input) => i.sidebarNavigation);
const sidebarContent = layoutSelectInput((i: Input) => i.sidebarContent);
const sidebarNavigationIsOpen = sidebarNavigation.isOpen;
const sidebarContentIsOpen = sidebarContent.isOpen;
2024-04-30 23:45:05 +08:00
const currentTimer = timerData;
2024-01-19 04:20:28 +08:00
if (!currentTimer?.active) return null;
2023-09-25 21:04:28 +08:00
const {
accumulated,
running,
startedOn,
2023-09-25 21:04:28 +08:00
stopwatch,
songTrack,
time,
} = currentTimer;
const currentDate: Date = new Date();
const startedAtDate: Date = new Date(startedOn || Date.now());
2023-09-25 21:04:28 +08:00
const adjustedCurrent: Date = new Date(currentDate.getTime() + timeSync);
const timeDifferenceMs: number = adjustedCurrent.getTime() - startedAtDate.getTime();
const timePassed = stopwatch ? (
2024-04-30 23:45:05 +08:00
Math.floor(((running ? timeDifferenceMs : 0) + (accumulated ?? 0)))
2023-09-25 21:04:28 +08:00
) : (
2024-04-30 23:45:05 +08:00
Math.floor(((time ?? 0) - ((accumulated ?? 0) + (running ? timeDifferenceMs : 0)))));
2023-09-25 21:04:28 +08:00
return (
<TimerIndicator
2024-01-18 22:09:13 +08:00
passedTime={timePassed}
2024-04-30 23:45:05 +08:00
stopwatch={stopwatch ?? false}
songTrack={songTrack ?? 'noTrack'}
running={running ?? false}
isModerator={currentUser?.isModerator ?? false}
2023-09-25 21:04:28 +08:00
sidebarNavigationIsOpen={sidebarNavigationIsOpen}
sidebarContentIsOpen={sidebarContentIsOpen}
2024-04-30 23:45:05 +08:00
startedOn={startedOn ?? 0}
2023-09-25 21:04:28 +08:00
/>
);
};
export default TimerIndicatorContainer;