Add controls for touch devices

This commit is contained in:
Eugen Rochko 2025-05-02 01:15:35 +02:00
parent 3d4ebd08de
commit e40b555195
2 changed files with 73 additions and 18 deletions

View File

@ -49,6 +49,8 @@ const restoreVolume = (audio: HTMLAudioElement) => {
audio.muted = muted;
};
const HOVER_FADE_DELAY = 4000;
export const Audio: React.FC<{
src: string;
alt?: string;
@ -116,6 +118,7 @@ export const Audio: React.FC<{
const audioRef = useRef<HTMLAudioElement | null>(null);
const seekRef = useRef<HTMLDivElement>(null);
const volumeRef = useRef<HTMLDivElement>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
const frequencyBands = useAudioVisualizer(audioRef, 3);
const accessibilityId = useId();
@ -372,10 +375,46 @@ export const Audio: React.FC<{
const handleMouseEnter = useCallback(() => {
setHovered(true);
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
}
hoverTimeoutRef.current = setTimeout(() => {
setHovered(false);
}, HOVER_FADE_DELAY);
}, [setHovered]);
const handleMouseMove = useCallback(() => {
setHovered(true);
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
}
hoverTimeoutRef.current = setTimeout(() => {
setHovered(false);
}, HOVER_FADE_DELAY);
}, [setHovered]);
const handleMouseLeave = useCallback(() => {
setHovered(false);
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
}
}, [setHovered]);
const handleTouchEnd = useCallback(() => {
setHovered(true);
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
}
hoverTimeoutRef.current = setTimeout(() => {
setHovered(false);
}, HOVER_FADE_DELAY);
}, [setHovered]);
const handleLoadedData = useCallback(() => {
@ -518,7 +557,9 @@ export const Audio: React.FC<{
} as React.CSSProperties
}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onTouchEnd={handleTouchEnd}
role='button'
tabIndex={0}
onKeyDownCapture={handleKeyDown}
@ -577,15 +618,17 @@ export const Audio: React.FC<{
</div>
<div className='audio-player__controls'>
<button
type='button'
title={intl.formatMessage(messages.skipBackward)}
aria-label={intl.formatMessage(messages.skipBackward)}
className='player-button'
onClick={handleSkipBackward}
>
<Icon id='' icon={Replay5Icon} />
</button>
<div className='audio-player__controls__play'>
<button
type='button'
title={intl.formatMessage(messages.skipBackward)}
aria-label={intl.formatMessage(messages.skipBackward)}
className='player-button'
onClick={handleSkipBackward}
>
<Icon id='' icon={Replay5Icon} />
</button>
</div>
<div className='audio-player__controls__play'>
<svg
@ -680,15 +723,17 @@ export const Audio: React.FC<{
</button>
</div>
<button
type='button'
title={intl.formatMessage(messages.skipForward)}
aria-label={intl.formatMessage(messages.skipForward)}
className='player-button'
onClick={handleSkipForward}
>
<Icon id='' icon={Forward5Icon} />
</button>
<div className='audio-player__controls__play'>
<button
type='button'
title={intl.formatMessage(messages.skipForward)}
aria-label={intl.formatMessage(messages.skipForward)}
className='player-button'
onClick={handleSkipForward}
>
<Icon id='' icon={Forward5Icon} />
</button>
</div>
</div>
<SpoilerButton

View File

@ -6969,6 +6969,7 @@ a.status-card {
outline: 1px solid var(--media-outline-color);
outline-offset: -1px;
aspect-ratio: 16 / 9;
container: audio-player / inline-size;
&__controls {
display: grid;
@ -7065,6 +7066,13 @@ a.status-card {
color: currentColor;
}
@container audio-player (max-width: 400px) {
.video-player__time,
.player-button.video-player__download__icon {
display: none;
}
}
.video-player__seek::before,
.video-player__seek__buffer,
.video-player__seek__progress {
@ -7132,10 +7140,12 @@ a.status-card {
);
padding: 0 15px;
opacity: 0;
pointer-events: none;
transition: opacity 0.1s ease;
&.active {
opacity: 1;
pointer-events: auto;
}
}