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
JavaScript
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;