wix-style-react
Version:
wix-style-react
192 lines • 8.96 kB
JavaScript
import React, { memo, useCallback, useMemo, useState, useEffect, forwardRef, useRef, useImperativeHandle, } from 'react';
import PropTypes from 'prop-types';
import { st, classes, vars } from './AudioPlayer.st.css';
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';
/** AudioPlayer */
const AudioPlayer = memo(forwardRef(({ dataHook, className, src, format, preload = 'metadata', webAudioAPI = false, onLoad, onLoadError, onPlay, onPause, onEnd, onSeek, autoplay, }, ref) => {
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();
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,
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(PauseFilled, { "data-hook": dataHooks.audioPlayerPause })) : (React.createElement(PlayFilled, { "data-hook": dataHooks.audioPlayerPlay }));
}, [loadingState, playing]);
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';
AudioPlayer.propTypes = {
/** Applies a data-hook HTML attribute that can be used in the tests. */
dataHook: PropTypes.string,
/** Specifies a CSS class name to be appended to the component’s root element. */
className: PropTypes.string,
/**
* Specifies a link to the source of the track to be loaded for the sound (URL or base64 data URI).
* If a file has no extension, you will need to specify the extension using the format property.
*/
src: PropTypes.string.isRequired,
/**
* Specifies a file format in situations where extraction does not work (such as a SoundCloud stream).<br/>
* By default, AudioPlayer detects your file format from the extension.
*/
format: PropTypes.string,
/**
* Determines what to download when the component is rendered: full file, its metadata or nothing at all.
* When webAudioAPI = true you can only set it to either 'auto' or 'none'.
* When webAudioAPI = false you can set it to 'auto', 'metadata' or 'none'.
*/
preload: PropTypes.oneOf(['auto', 'metadata', 'none']),
/**
* Start playback automatically when audio is loaded.
*/
autoplay: PropTypes.bool,
/**
* Specifies whether to force web audio API. Use it for relatively small audio files only because you have to wait for the full file
* to be downloaded and decoded before playing. Web Audio API allows advanced capabilities as described in
* [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API).
*/
webAudioAPI: PropTypes.bool,
/**
* Defines a callback function which is called when audio is loaded.
*/
onLoad: PropTypes.func,
/**
* Defines a callback function which is called every time audio fails to load.
*/
onLoadError: PropTypes.func,
/**
* Defines a callback function which is called when audio is played.
*/
onPlay: PropTypes.func,
/**
* Defines a callback function which is called when audio is paused.
*/
onPause: PropTypes.func,
/**
Will be called when audio is ended.
*/
onEnd: PropTypes.func,
/**
* Defines a callback function which is called when audio is seeked explicitly (i.e. when user drags the slider).
*/
onSeek: PropTypes.func,
};
export default AudioPlayer;
//# sourceMappingURL=AudioPlayer.js.map