UNPKG

react-use-audio-player-guru

Version:

React hook for building custom audio playback controls

407 lines (350 loc) 11.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(React); var howler = require('howler'); function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var Actions; (function (Actions) { Actions[Actions["START_LOAD"] = 0] = "START_LOAD"; Actions[Actions["ON_LOAD"] = 1] = "ON_LOAD"; Actions[Actions["ON_PLAY"] = 2] = "ON_PLAY"; Actions[Actions["ON_END"] = 3] = "ON_END"; Actions[Actions["ON_PAUSE"] = 4] = "ON_PAUSE"; Actions[Actions["ON_STOP"] = 5] = "ON_STOP"; Actions[Actions["ON_PLAY_ERROR"] = 6] = "ON_PLAY_ERROR"; Actions[Actions["ON_LOAD_ERROR"] = 7] = "ON_LOAD_ERROR"; })(Actions || (Actions = {})); var initialState = { loading: true, playing: false, stopped: true, ended: false, error: null, duration: 0, ready: false }; function reducer(state, action) { switch (action.type) { case Actions.START_LOAD: return _extends({}, state, { loading: true, stopped: true, ready: false, error: null, duration: 0 }); case Actions.ON_LOAD: return _extends({}, state, { loading: false, duration: action.duration, ended: false, ready: true }); case Actions.ON_PLAY: return _extends({}, state, { playing: true, ended: false, stopped: false }); case Actions.ON_STOP: return _extends({}, state, { stopped: true, playing: false }); case Actions.ON_END: return _extends({}, state, { stopped: true, playing: false, ended: true }); case Actions.ON_PAUSE: return _extends({}, state, { playing: false }); case Actions.ON_PLAY_ERROR: return _extends({}, state, { playing: false, stopped: true, error: action.error }); case Actions.ON_LOAD_ERROR: return _extends({}, state, { playing: false, stopped: true, loading: false, error: action.error }); default: return state; } } var context = /*#__PURE__*/ React__default.createContext(null); function AudioPlayerProvider(_ref) { var children = _ref.children, value = _ref.value; var _useState = React.useState(null), player = _useState[0], setPlayer = _useState[1]; var _useReducer = React.useReducer(reducer, initialState), _useReducer$ = _useReducer[0], loading = _useReducer$.loading, error = _useReducer$.error, playing = _useReducer$.playing, stopped = _useReducer$.stopped, duration = _useReducer$.duration, ready = _useReducer$.ready, ended = _useReducer$.ended, dispatch = _useReducer[1]; var playerRef = React.useRef(); var prevPlayer = React.useRef(); var constructHowl = React.useCallback(function (audioProps) { return new howler.Howl(audioProps); }, []); var load = React.useCallback(function (_ref2) { var src = _ref2.src, _ref2$format = _ref2.format, format = _ref2$format === void 0 ? undefined : _ref2$format, _ref2$autoplay = _ref2.autoplay, autoplay = _ref2$autoplay === void 0 ? false : _ref2$autoplay, _ref2$html = _ref2.html5, html5 = _ref2$html === void 0 ? false : _ref2$html, _ref2$xhr = _ref2.xhr, xhr = _ref2$xhr === void 0 ? { method: "GET", headers: undefined, withCredentials: undefined } : _ref2$xhr; var wasPlaying = false; if (playerRef.current) { // don't do anything if we're asked to reload the same source // @ts-ignore the _src argument actually exists if (playerRef.current._src === src) return; // if the previous sound is still loading then destroy it as soon as it finishes if (loading) { prevPlayer.current = playerRef.current; prevPlayer.current.once("load", function () { var _prevPlayer$current; (_prevPlayer$current = prevPlayer.current) === null || _prevPlayer$current === void 0 ? void 0 : _prevPlayer$current.unload(); }); } wasPlaying = playerRef.current.playing(); if (wasPlaying) { playerRef.current.stop(); // remove event handlers from player that is about to be replaced playerRef.current.off(); playerRef.current = undefined; } } // signal that the loading process has begun dispatch({ type: Actions.START_LOAD }); // create a new player var howl = constructHowl({ src: src, format: format, autoplay: wasPlaying || autoplay, html5: html5, xhr: xhr }); // if this howl has already been loaded (cached) then fire the load action // @ts-ignore _state exists if (howl._state === "loaded") { dispatch({ type: Actions.ON_LOAD, duration: howl.duration() }); } howl.on("load", function () { return void dispatch({ type: Actions.ON_LOAD, duration: howl.duration() }); }); howl.on("play", function () { return void dispatch({ type: Actions.ON_PLAY }); }); howl.on("end", function () { return void dispatch({ type: Actions.ON_END }); }); howl.on("pause", function () { return void dispatch({ type: Actions.ON_PAUSE }); }); howl.on("stop", function () { return void dispatch({ type: Actions.ON_STOP }); }); howl.on("playerror", function (_id, error) { dispatch({ type: Actions.ON_PLAY_ERROR, error: new Error("[Play error] " + error) }); }); howl.on("loaderror", function (_id, error) { dispatch({ type: Actions.ON_LOAD_ERROR, error: new Error("[Load error] " + error) }); }); setPlayer(howl); playerRef.current = howl; }, [constructHowl, loading]); React.useEffect(function () { // unload the player on unmount return function () { if (playerRef.current) playerRef.current.unload(); }; }, []); var contextValue = React.useMemo(function () { return value ? value : { player: player, load: load, error: error, loading: loading, playing: playing, stopped: stopped, ready: ready, duration: duration, ended: ended }; }, [loading, error, playing, stopped, load, value, player, ready, duration, ended]); return React__default.createElement(context.Provider, { value: contextValue }, children); } var noop = function noop() {}; var useAudioPlayer = function useAudioPlayer(options) { var _useContext = React.useContext(context), player = _useContext.player, load = _useContext.load, rest = _objectWithoutPropertiesLoose(_useContext, ["player", "load"]); var _ref = options || {}, src = _ref.src, restOptions = _objectWithoutPropertiesLoose(_ref, ["src"]); React.useEffect(function () { // if useAudioPlayer is called without arguments // don't do anything: the user will have access // to the current context if (!src) return; load(_extends({ src: src }, restOptions)); }, [src, restOptions, load]); var togglePlayPause = React.useCallback(function () { if (!player) return; if (player.playing()) { player.pause(); } else { player.play(); } }, [player]); var seek = React.useCallback(function (position) { var isHowl = function isHowl(input) { if (input._src) return true; return false; }; if (!player) return; var result = player.seek(position); if (isHowl(result)) return; return result; }, [player]); return _extends({}, rest, { play: player ? player.play.bind(player) : noop, pause: player ? player.pause.bind(player) : noop, stop: player ? player.stop.bind(player) : noop, mute: player ? player.mute.bind(player) : noop, volume: player ? player.volume.bind(player) : noop, rate: player ? player.rate.bind(player) : noop, player: player, seek: seek, load: load, togglePlayPause: togglePlayPause }); }; var useAudioPosition = function useAudioPosition(config) { if (config === void 0) { config = {}; } var _config = config, _config$highRefreshRa = _config.highRefreshRate, highRefreshRate = _config$highRefreshRa === void 0 ? false : _config$highRefreshRa; var _useContext = React.useContext(context), player = _useContext.player, playing = _useContext.playing, stopped = _useContext.stopped, duration = _useContext.duration; var _useAudioPlayer = useAudioPlayer(), seek = _useAudioPlayer.seek; var _useState = React.useState(0), position = _useState[0], setPosition = _useState[1]; var animationFrameRef = React.useRef(); // sets position on player initialization and when the audio is stopped React.useEffect(function () { if (player) { setPosition(player.seek()); } }, [player, stopped]); // updates position on a one second loop for low refresh rate default setting React.useEffect(function () { var timeout; if (!highRefreshRate && player && playing) timeout = window.setInterval(function () { return setPosition(player.seek()); }, 1000); return function () { return clearTimeout(timeout); }; }, [highRefreshRate, player, playing]); // updates position on a 60fps loop for high refresh rate setting React.useLayoutEffect(function () { var animate = function animate() { setPosition(player === null || player === void 0 ? void 0 : player.seek()); animationFrameRef.current = requestAnimationFrame(animate); }; // kick off a new animation cycle when the player is defined and starts playing if (highRefreshRate && player && playing) { animationFrameRef.current = requestAnimationFrame(animate); } return function () { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, [highRefreshRate, player, playing]); return { position: position, duration: duration, seek: seek }; }; exports.AudioPlayerProvider = AudioPlayerProvider; exports.useAudioPlayer = useAudioPlayer; exports.useAudioPosition = useAudioPosition; //# sourceMappingURL=react-use-audio-player-guru.cjs.development.js.map