UNPKG

wix-style-react

Version:
299 lines (256 loc) • 10.2 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; 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 from 'wix-ui-icons-common/PlayFilled'; import PauseFilled from 'wix-ui-icons-common/PauseFilled'; import { dataHooks } from './constants'; import { useAudioManager } from './AudioManager/AudioManager'; import { positionToSeconds, secondsToISO, secondsToPosition } from './utils'; /** AudioPlayer */ var AudioPlayer = /*#__PURE__*/memo( /*#__PURE__*/forwardRef(function (_ref, ref) { var dataHook = _ref.dataHook, className = _ref.className, src = _ref.src, format = _ref.format, preload = _ref.preload, webAudioAPI = _ref.webAudioAPI, onLoad = _ref.onLoad, onLoadError = _ref.onLoadError, onPlay = _ref.onPlay, onPause = _ref.onPause, onEnd = _ref.onEnd, onSeek = _ref.onSeek; var _useState = useState(true), _useState2 = _slicedToArray(_useState, 2), isSliderLocked = _useState2[0], setIsSliderLocked = _useState2[1]; var _useState3 = useState(0), _useState4 = _slicedToArray(_useState3, 2), hoverPosition = _useState4[0], setHoverPosition = _useState4[1]; var _useState5 = useState(0), _useState6 = _slicedToArray(_useState5, 2), handleSizeInPercentage = _useState6[0], setHandleSizeInPercentage = _useState6[1]; var _useState7 = useState(false), _useState8 = _slicedToArray(_useState7, 2), playing = _useState8[0], setPlaying = _useState8[1]; var _useState9 = useState(true), _useState10 = _slicedToArray(_useState9, 2), showDuration = _useState10[0], setShowDuration = _useState10[1]; var playPauseButtonRef = useRef(); useImperativeHandle(ref, function () { return { focus: function focus() { var _playPauseButtonRef$c; return playPauseButtonRef === null || playPauseButtonRef === void 0 ? void 0 : (_playPauseButtonRef$c = playPauseButtonRef.current) === null || _playPauseButtonRef$c === void 0 ? void 0 : _playPauseButtonRef$c.focus(); } }; }); var _onDestroy = useCallback(function () { setPlaying(false); setShowDuration(true); }, [setPlaying, setShowDuration]); var _onEnd = useCallback(function () { setShowDuration(true); setPlaying(false); onEnd && onEnd(); }, [onEnd, setShowDuration, setPlaying]); var _useAudioManager = useAudioManager({ src: src, format: format, preload: preload, webAudioAPI: webAudioAPI, onLoad: onLoad, onLoadError: onLoadError, onPlay: onPlay, onSeek: onSeek, onPause: onPause, playing: playing, onDestroy: _onDestroy, onEnd: _onEnd, allowSeekLoop: isSliderLocked }), loadingState = _useAudioManager.loadingState, duration = _useAudioManager.duration, seek = _useAudioManager.seek, setSeek = _useAudioManager.setSeek; var isLoaded = loadingState === 'loaded'; var _hoverISO = useMemo(function () { 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. var _seekPercentage = useMemo(function () { if (!isLoaded) { return 0; } return secondsToPosition(seek, duration); }, [isLoaded, seek, duration]); var _togglePlayPause = useCallback(function () { setShowDuration(false); if (playing) { setPlaying(false); } else { setPlaying(true); } }, [playing, setPlaying, setShowDuration]); var _playPauseButtonContent = useMemo(function () { if (loadingState === 'loading') { return /*#__PURE__*/React.createElement("span", { "data-hook": dataHooks.audioPlayerLoad }, /*#__PURE__*/React.createElement(Loader, { size: "tiny" })); } return playing ? /*#__PURE__*/React.createElement(PauseFilled, { "data-hook": dataHooks.audioPlayerPause }) : /*#__PURE__*/React.createElement(PlayFilled, { "data-hook": dataHooks.audioPlayerPlay }); }, [loadingState, playing]); var _setSliderPositions = useCallback(function (x, width, clickX) { var positionInPixels = (clickX - x) / width * 100; var position = Math.min(Math.max(positionInPixels, 0), 100); setHandleSizeInPercentage(12 / width * 100); setHoverPosition(position); }, [setHoverPosition]); var _handleSliderMouseDown = useCallback(function (event) { var clientX = event.clientX, currentTarget = event.currentTarget; var _currentTarget$getBou = currentTarget.getBoundingClientRect(), x = _currentTarget$getBou.x, width = _currentTarget$getBou.width; setIsSliderLocked(false); _setSliderPositions(x, width, clientX); }, [_setSliderPositions, setIsSliderLocked]); var _handleSliderMouseMove = useCallback(function (event) { var clientX = event.clientX, currentTarget = event.currentTarget; var _currentTarget$getBou2 = currentTarget.getBoundingClientRect(), x = _currentTarget$getBou2.x, width = _currentTarget$getBou2.width; _setSliderPositions(x, width, clientX); }, [_setSliderPositions]); var _handleSliderMouseUp = useCallback(function () { setIsSliderLocked(true); }, [setIsSliderLocked]); useEffect(function () { window.addEventListener('mouseup', _handleSliderMouseUp); return function () { return window.removeEventListener('mouseup', _handleSliderMouseUp); }; }, [_handleSliderMouseUp]); // seek audio file to the slider location when dragged. useEffect(function () { if (!isSliderLocked) { setSeek(positionToSeconds(hoverPosition, duration)); setShowDuration(false); } }, [duration, hoverPosition, isSliderLocked, setSeek]); return /*#__PURE__*/React.createElement("div", { className: st(classes.root, className), "data-hook": dataHook }, /*#__PURE__*/React.createElement(IconButton, { ref: playPauseButtonRef, size: "small", onClick: _togglePlayPause, dataHook: dataHooks.audioPlayerPlayPause, className: classes.playPauseButton }, _playPauseButtonContent), /*#__PURE__*/React.createElement("div", { "data-hook": dataHooks.audioPlayerSlider, className: classes.slider, style: _defineProperty({}, vars['audio-player-position'], "".concat(_seekPercentage, "%")), onMouseDown: _handleSliderMouseDown, onMouseMove: _handleSliderMouseMove }, /*#__PURE__*/React.createElement("div", { className: classes.track }), /*#__PURE__*/React.createElement("div", { className: classes.tooltip, style: { left: "".concat(hoverPosition, "%") } }, /*#__PURE__*/React.createElement(Tooltip, { content: "".concat(_hoverISO) }, /*#__PURE__*/React.createElement("div", { className: classes.tooltipTarget }))), /*#__PURE__*/React.createElement("div", { "data-hook": dataHooks.audioPlayerSliderHandle, className: st(classes.handle, { grow: isLoaded && (!isSliderLocked || Math.abs(_seekPercentage - hoverPosition) < handleSizeInPercentage) }), style: { left: "".concat(_seekPercentage, "%") } })), /*#__PURE__*/React.createElement(Heading, { appearance: "H5", 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']), /** * 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 }; AudioPlayer.defaultProps = { preload: 'metadata', webAudioAPI: false }; export default AudioPlayer;