UNPKG

@wix/design-system

Version:

@wix/design-system

141 lines 6.82 kB
import React, { memo, useCallback, useMemo, useState, useEffect, forwardRef, useRef, useImperativeHandle, } from 'react'; import { st, classes, vars } from './AudioPlayer.st.css.js'; import Tooltip from '../Tooltip'; import IconButton from '../IconButton'; import Loader from '../Loader'; import Heading from '../Heading'; import { PlayFilled, PauseFilled } from '@wix/wix-ui-icons-common'; import { dataHooks } from './constants'; import { useAudioManager } from './AudioManager/AudioManager'; import { positionToSeconds, secondsToISO, secondsToPosition } from './utils'; import { useIcons } from '../WixDesignSystemIconThemeProvider'; /** AudioPlayer */ const AudioPlayer = memo(forwardRef(({ dataHook, className, src, format, preload = 'metadata', webAudioAPI = false, onLoad, onLoadError, onPlay, onPause, onEnd, onSeek, autoplay, volume, }, ref) => { const icons = useIcons('AudioPlayer', { PlayFilled, PauseFilled, }); const [isSliderLocked, setIsSliderLocked] = useState(true); const [hoverPosition, setHoverPosition] = useState(0); const [handleSizeInPercentage, setHandleSizeInPercentage] = useState(0); const [playing, setPlaying] = useState(false); const [showDuration, setShowDuration] = useState(true); const playPauseButtonRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => playPauseButtonRef.current && playPauseButtonRef.current.focus(), })); const _onDestroy = useCallback(() => { setPlaying(false); setShowDuration(true); }, [setPlaying, setShowDuration]); const _onEnd = useCallback(() => { setShowDuration(true); setPlaying(false); onEnd && onEnd(); }, [onEnd, setShowDuration, setPlaying]); const _onLoad = useCallback(() => { if (autoplay) { setShowDuration(false); setPlaying(true); } onLoad && onLoad(); }, [onLoad, autoplay]); const { loadingState, duration, seek, setSeek } = useAudioManager({ src, format, preload, webAudioAPI, volume, onLoad: _onLoad, onLoadError, onPlay, onSeek, onPause, playing, onDestroy: _onDestroy, onEnd: _onEnd, allowSeekLoop: isSliderLocked, }); const isLoaded = loadingState === 'loaded'; const _hoverISO = useMemo(() => { if (!isLoaded) { return secondsToISO(0, false, duration); } return secondsToISO(positionToSeconds(hoverPosition, duration), true, duration); }, [isLoaded, hoverPosition, duration]); // takes the current seek (in seconds) and converts it to slider position. const _seekPercentage = useMemo(() => { if (!isLoaded) { return 0; } return secondsToPosition(seek, duration); }, [isLoaded, seek, duration]); const _togglePlayPause = useCallback(() => { setShowDuration(false); if (playing) { setPlaying(false); } else { setPlaying(true); } }, [playing, setPlaying, setShowDuration]); const _playPauseButtonContent = useMemo(() => { if (loadingState === 'loading') { return (React.createElement("span", { "data-hook": dataHooks.audioPlayerLoad }, React.createElement(Loader, { size: "tiny" }))); } return playing ? (React.createElement(icons.PauseFilled, { "data-hook": dataHooks.audioPlayerPause })) : (React.createElement(icons.PlayFilled, { "data-hook": dataHooks.audioPlayerPlay })); }, [loadingState, playing, icons]); const _setSliderPositions = useCallback((x, width, clickX) => { const positionInPixels = ((clickX - x) / width) * 100; const position = Math.min(Math.max(positionInPixels, 0), 100); setHandleSizeInPercentage((12 / width) * 100); setHoverPosition(position); }, [setHoverPosition]); const _handleSliderMouseDown = useCallback(event => { const { clientX, currentTarget } = event; const { x, width } = currentTarget.getBoundingClientRect(); setIsSliderLocked(false); _setSliderPositions(x, width, clientX); }, [_setSliderPositions, setIsSliderLocked]); const _handleSliderMouseMove = useCallback(event => { const { clientX, currentTarget } = event; const { x, width } = currentTarget.getBoundingClientRect(); _setSliderPositions(x, width, clientX); }, [_setSliderPositions]); const _handleSliderMouseUp = useCallback(() => { setIsSliderLocked(true); }, [setIsSliderLocked]); useEffect(() => { window.addEventListener('mouseup', _handleSliderMouseUp); return () => window.removeEventListener('mouseup', _handleSliderMouseUp); }, [_handleSliderMouseUp]); // seek audio file to the slider location when dragged. useEffect(() => { if (!isSliderLocked) { setSeek(positionToSeconds(hoverPosition, duration)); setShowDuration(false); } }, [duration, hoverPosition, isSliderLocked, setSeek]); return (React.createElement("div", { className: st(classes.root, className), "data-hook": dataHook }, React.createElement(IconButton, { ref: playPauseButtonRef, size: "small", onClick: _togglePlayPause, dataHook: dataHooks.audioPlayerPlayPause, className: classes.playPauseButton }, _playPauseButtonContent), React.createElement("div", { "data-hook": dataHooks.audioPlayerSlider, className: classes.slider, style: { [vars['audio-player-position']]: `${_seekPercentage}%`, }, onMouseDown: _handleSliderMouseDown, onMouseMove: _handleSliderMouseMove }, React.createElement("div", { className: classes.track }), React.createElement("div", { className: classes.tooltip, style: { left: `${hoverPosition}%` } }, React.createElement(Tooltip, { content: `${_hoverISO}` }, React.createElement("div", { className: classes.tooltipTarget }))), React.createElement("div", { "data-hook": dataHooks.audioPlayerSliderHandle, className: st(classes.handle, { grow: isLoaded && (!isSliderLocked || Math.abs(_seekPercentage - hoverPosition) < handleSizeInPercentage), }), style: { left: `${_seekPercentage}%` } })), React.createElement(Heading, { size: "tiny", className: classes.timer, dataHook: dataHooks.audioTimeIndicator }, showDuration ? secondsToISO(duration, isLoaded, duration) : secondsToISO(seek, isLoaded, duration)))); })); AudioPlayer.displayName = 'AudioPlayer'; export default AudioPlayer; //# sourceMappingURL=AudioPlayer.js.map