UNPKG

beautiful-react-hooks

Version:

A collection of beautiful (and hopefully useful) React hooks to speed-up your components and hooks development

200 lines (199 loc) 7.35 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { useCallback, useEffect, useRef } from 'react'; import noop from './shared/noop'; import isClient from './shared/isClient'; import useObjectState from './useObjectState'; import isDevelopment from './shared/isDevelopment'; import isAPISupported from './shared/isAPISupported'; import createHandlerSetter from './factory/createHandlerSetter'; import warnOnce from './shared/warnOnce'; /** * The default options for the useAudio hook */ const defaultOptions = { volume: 1, loop: false, muted: false, playbackRate: 1, autoPlay: false, preload: 'auto' }; /** * The default state for the useAudio hook */ const defaultState = Object.assign({ duration: 0, currentTime: 0, isPlaying: false, isSrcLoading: undefined }, defaultOptions); /** * The error event code to message mapper */ const errorEventCodeToMessageMapper = { 3: 'MEDIA_ERR_DECODE - error occurred when decoding', 4: 'MEDIA_ERR_SRC_NOT_SUPPORTED - audio not supported', 2: 'MEDIA_ERR_NETWORK - error occurred when downloading', 1: 'MEDIA_ERR_ABORTED - fetching process aborted by user', 0: 'UNKNOWN_ERROR - unknown error' }; /** * The hook not supported controls */ const hookNotSupportedControls = Object.freeze({ seek: noop, play: noop, mute: noop, pause: noop, unmute: noop, onError: noop, setVolume: noop }); /** * Checks if the ref element exists and calls the callback with it * @param ref * @param callback */ const checkIfRefElementExists = (ref, callback) => () => { const element = ref.current; if (!element) { return undefined; } return callback(element); }; /** * The useAudio hook wraps the Audio API and provides a set of controls to manage the audio */ export const useAudio = (src, options) => { const hookNotSupportedResponse = [ defaultState, hookNotSupportedControls, useRef(null) ]; if (!isClient) { if (!isDevelopment) { warnOnce('Please be aware that useAudio hook could not be available during SSR'); } return hookNotSupportedResponse; } if (!isAPISupported('Audio')) { warnOnce('The current device does not support the \'Audio\' API, you should avoid using useAudio hook'); return hookNotSupportedResponse; } const audioRef = useRef(new Audio(src)); const [onErrorRef, setOnErrorRef] = createHandlerSetter(); const [state, setState] = useObjectState(defaultState); const onError = (error) => { if (onErrorRef.current != null) { onErrorRef.current(error); } }; const play = useCallback(checkIfRefElementExists(audioRef, (element) => __awaiter(void 0, void 0, void 0, function* () { yield element .play() .then(() => { setState({ isPlaying: true }); }) .catch(onError); })), []); const pause = useCallback(checkIfRefElementExists(audioRef, (element) => { element.pause(); setState({ isPlaying: false }); }), []); const mute = useCallback(checkIfRefElementExists(audioRef, (element) => { // eslint-disable-next-line no-param-reassign element.muted = true; setState({ muted: true }); }), []); const unmute = useCallback(checkIfRefElementExists(audioRef, (element) => { // eslint-disable-next-line no-param-reassign element.muted = false; setState({ muted: false }); }), []); const seek = useCallback((time) => checkIfRefElementExists(audioRef, (element) => { const newTime = time >= 0 ? Math.min(time, element.duration) : Math.max(time, 0); // eslint-disable-next-line no-param-reassign element.currentTime = newTime; setState({ currentTime: newTime }); })(), []); const setVolume = useCallback((volume) => checkIfRefElementExists(audioRef, (element) => { const newVolume = volume >= 0 ? Math.min(volume, 1) : Math.max(volume, 0); // eslint-disable-next-line no-param-reassign element.volume = newVolume; setState({ volume: newVolume }); })(), []); const onLoadedData = checkIfRefElementExists(audioRef, (element) => { setState({ isSrcLoading: false, duration: element.duration, currentTime: element.currentTime }); }); const onTimeUpdate = checkIfRefElementExists(audioRef, (element) => { setState({ currentTime: element.currentTime }); }); const errorEventCallback = () => { var _a, _b, _c; const element = audioRef.current; const errorCode = (_a = element.error) === null || _a === void 0 ? void 0 : _a.code; const errorMessage = ((_c = (_b = element.error) === null || _b === void 0 ? void 0 : _b.message) !== null && _c !== void 0 ? _c : errorEventCodeToMessageMapper[errorCode !== null && errorCode !== void 0 ? errorCode : 0]) || 'UNKNOWN'; onError(new Error(errorMessage)); }; useEffect(() => { const element = audioRef.current; if (element) { const mergedOptions = Object.assign(Object.assign({}, defaultOptions), options); element.loop = mergedOptions.loop; element.muted = mergedOptions.muted; element.volume = mergedOptions.volume; element.preload = mergedOptions.preload; element.autoplay = mergedOptions.autoPlay; element.playbackRate = mergedOptions.playbackRate; setState(Object.assign(Object.assign({}, mergedOptions), { isSrcLoading: true })); element.addEventListener('loadeddata', onLoadedData); element.addEventListener('timeupdate', onTimeUpdate); element.addEventListener('error', errorEventCallback); } return () => { if (element) { element.removeEventListener('loadeddata', onLoadedData); element.removeEventListener('timeupdate', onTimeUpdate); element.removeEventListener('error', errorEventCallback); } pause(); }; }, []); useEffect(() => { if (state.isSrcLoading === false && state.autoPlay) { play(); } }, [state.isSrcLoading, state.autoPlay]); const controls = Object.freeze({ seek, play, mute, pause, unmute, setVolume, onError: setOnErrorRef }); return [state, controls, audioRef]; }; export default useAudio;