signal-desktop/ts/components/conversation/WaveformScrubber.tsx

126 lines
3.2 KiB
TypeScript
Raw Normal View History

2023-03-02 20:55:40 +00:00
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useRef } from 'react';
import { useRefMerger } from '../../hooks/useRefMerger';
import type { LocalizerType } from '../../types/Util';
import { durationToPlaybackText } from '../../util/durationToPlaybackText';
import { Waveform } from './Waveform';
2023-04-20 17:03:43 +00:00
import { arrow } from '../../util/keyboard';
2023-03-02 20:55:40 +00:00
type Props = Readonly<{
i18n: LocalizerType;
peaks: ReadonlyArray<number>;
currentTime: number;
duration: number | undefined;
barMinHeight: number;
barMaxHeight: number;
onClick: (positionAsRatio: number) => void;
onScrub: (positionAsRatio: number) => void;
}>;
const BAR_COUNT = 47;
const REWIND_BAR_COUNT = 2;
// Increments for keyboard audio seek (in seconds)\
const SMALL_INCREMENT = 1;
const BIG_INCREMENT = 5;
export const WaveformScrubber = React.forwardRef(function WaveformScrubber(
{
i18n,
peaks,
barMinHeight,
barMaxHeight,
currentTime,
duration,
onClick,
onScrub,
}: Props,
ref
): JSX.Element {
const refMerger = useRefMerger();
const waveformRef = useRef<HTMLDivElement | null>(null);
// Clicking waveform moves playback head position and starts playback.
const handleClick = useCallback(
(event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
if (!waveformRef.current) {
return;
}
const boundingRect = waveformRef.current.getBoundingClientRect();
let progress = (event.pageX - boundingRect.left) / boundingRect.width;
if (progress <= REWIND_BAR_COUNT / BAR_COUNT) {
progress = 0;
}
onClick(progress);
},
[waveformRef, onClick]
);
// Keyboard navigation for waveform. Pressing keys moves playback head
// forward/backwards.
const handleKeyDown = (event: React.KeyboardEvent) => {
if (!duration) {
return;
}
let increment: number;
2023-04-20 17:03:43 +00:00
if (event.key === 'ArrowUp' || event.key === arrow('end')) {
2023-03-02 20:55:40 +00:00
increment = +SMALL_INCREMENT;
2023-04-20 17:03:43 +00:00
} else if (event.key === 'ArrowDown' || event.key === arrow('start')) {
2023-03-02 20:55:40 +00:00
increment = -SMALL_INCREMENT;
} else if (event.key === 'PageUp') {
increment = +BIG_INCREMENT;
} else if (event.key === 'PageDown') {
increment = -BIG_INCREMENT;
} else {
// We don't handle other keys
return;
}
event.preventDefault();
event.stopPropagation();
const currentPosition = currentTime / duration;
const positionIncrement = increment / duration;
const newPosition = currentPosition + positionIncrement;
onScrub(newPosition);
};
return (
<div
ref={refMerger(waveformRef, ref)}
className="WaveformScrubber"
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={0}
role="slider"
2023-03-30 00:03:25 +00:00
aria-label={i18n('icu:MessageAudio--slider')}
2023-03-02 20:55:40 +00:00
aria-orientation="horizontal"
aria-valuenow={currentTime}
aria-valuemin={0}
aria-valuemax={duration}
aria-valuetext={durationToPlaybackText(currentTime)}
>
<Waveform
peaks={peaks}
barMinHeight={barMinHeight}
barMaxHeight={barMaxHeight}
currentTime={currentTime}
duration={duration}
/>
</div>
);
});