signal-desktop/ts/components/MiniPlayer.tsx

129 lines
3.1 KiB
TypeScript
Raw Normal View History

2023-02-24 23:18:57 +00:00
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import classnames from 'classnames';
2023-02-24 23:18:57 +00:00
import React, { useCallback } from 'react';
import type { LocalizerType } from '../types/Util';
import { durationToPlaybackText } from '../util/durationToPlaybackText';
2023-03-02 20:55:40 +00:00
import { PlaybackButton } from './PlaybackButton';
2023-02-24 23:18:57 +00:00
import { PlaybackRateButton } from './PlaybackRateButton';
2023-04-20 17:03:43 +00:00
import { UserText } from './UserText';
2023-02-24 23:18:57 +00:00
export enum PlayerState {
loading = 'loading',
playing = 'playing',
paused = 'paused',
}
export type Props = Readonly<{
i18n: LocalizerType;
title: string;
currentTime: number;
2023-02-28 13:07:40 +00:00
// not available until audio has loaded
duration: number | undefined;
2023-02-24 23:18:57 +00:00
playbackRate: number;
state: PlayerState;
// if false or not provided, position:absolute. Otherwise, it's position: relative
shouldFlow?: boolean;
2023-02-24 23:18:57 +00:00
onPlay: () => void;
onPause: () => void;
onPlaybackRate: (rate: number) => void;
onClose: () => void;
}>;
export function MiniPlayer({
i18n,
title,
state,
currentTime,
duration,
playbackRate,
shouldFlow,
2023-02-24 23:18:57 +00:00
onPlay,
onPause,
onPlaybackRate,
onClose,
}: Props): JSX.Element {
const updatePlaybackRate = useCallback(() => {
onPlaybackRate(PlaybackRateButton.nextPlaybackRate(playbackRate));
}, [playbackRate, onPlaybackRate]);
const handleClick = useCallback(() => {
switch (state) {
case PlayerState.playing:
onPause();
break;
case PlayerState.paused:
onPlay();
break;
case PlayerState.loading:
break;
default:
throw new TypeError(`Missing case: ${state}`);
}
}, [state, onPause, onPlay]);
let label: string | undefined;
2023-03-02 20:55:40 +00:00
let mod: 'play' | 'pause' | 'pending';
2023-02-24 23:18:57 +00:00
switch (state) {
case PlayerState.playing:
2023-03-30 00:03:25 +00:00
label = i18n('icu:MessageAudio--pause');
2023-03-02 20:55:40 +00:00
mod = 'pause';
2023-02-24 23:18:57 +00:00
break;
case PlayerState.paused:
2023-03-30 00:03:25 +00:00
label = i18n('icu:MessageAudio--play');
2023-03-02 20:55:40 +00:00
mod = 'play';
2023-02-24 23:18:57 +00:00
break;
case PlayerState.loading:
2023-03-30 00:03:25 +00:00
label = i18n('icu:MessageAudio--pending');
2023-03-02 20:55:40 +00:00
mod = 'pending';
2023-02-24 23:18:57 +00:00
break;
default:
throw new TypeError(`Missing case ${state}`);
}
return (
<div
className={classnames(
'MiniPlayer',
shouldFlow ? 'MiniPlayer--flow' : null
)}
>
2023-03-02 20:55:40 +00:00
<PlaybackButton
context="incoming"
variant="mini"
mod={mod}
label={label}
2023-02-24 23:18:57 +00:00
onClick={handleClick}
/>
<div className="MiniPlayer__state">
2023-04-20 17:03:43 +00:00
<UserText text={title} />
2023-02-24 23:18:57 +00:00
<span className="MiniPlayer__middot">&middot;</span>
2023-02-28 13:07:40 +00:00
{duration !== undefined && (
<span>
{durationToPlaybackText(
state === PlayerState.loading ? duration : currentTime
)}
</span>
)}
2023-02-24 23:18:57 +00:00
</div>
<PlaybackRateButton
i18n={i18n}
variant="mini-player"
playbackRate={playbackRate}
onClick={updatePlaybackRate}
visible={state === 'playing'}
/>
<button
type="button"
className="MiniPlayer__close-button"
onClick={onClose}
2023-03-30 00:03:25 +00:00
aria-label={i18n('icu:close')}
2023-02-24 23:18:57 +00:00
/>
</div>
);
}