UNPKG

rehowl

Version:

Opinionated React wrapper for Howler.js

292 lines (291 loc) 10 kB
import { useEffect, useRef, useState } from 'react'; /** * Plays and controls sounds from a Howl. * * The Howl instance, provided by the `howl` prop, can come from `useHowl`, * `<Rehowl />`, or provided from your own use of howler.js * * You can render **multiple `<Play />` components** for a single * Howl instance in order to play multiple sounds or sprites at once. * * Event handlers fire only for the `<Play />` that they correspond to, * and not for every sound playing off of the Howl instance like in howler.js */ export function Play(props) { var howl = props.howl, pause = props.pause, sprite = props.sprite, mute = props.mute, _a = props.volume, volume = _a === void 0 ? 1 : _a, seek = props.seek, fade = props.fade, stop = props.stop, rate = props.rate, loop = props.loop, children = props.children; var shouldPlay = !pause && !stop; // Don't attempt to play the Howl until both pause and stop are false. var _b = useState(shouldPlay), initialized = _b[0], setInitialized = _b[1]; var _c = useState(null), playId = _c[0], setPlayId = _c[1]; var _d = useState(true), playing = _d[0], setPlaying = _d[1]; var _e = useState(false), stopped = _e[0], setStopped = _e[1]; var _f = useState(false), unlocked = _f[0], setUnlocked = _f[1]; var _g = useState(false), seeking = _g[0], setSeeking = _g[1]; // We use refs for the callbacks so that they can be dynamic. var onPlay = useRef(null); var onPlayError = useRef(null); var onEnd = useRef(null); var onPause = useRef(null); var onStop = useRef(null); var onMute = useRef(null); var onVolume = useRef(null); var onSeek = useRef(null); var onFade = useRef(null); var onRate = useRef(null); useEffect(function () { onPlay.current = props.onPlay || null; }, [props.onPlay]); useEffect(function () { onPlayError.current = props.onPlayError || null; }, [props.onPlayError]); useEffect(function () { onEnd.current = props.onEnd || null; }, [props.onEnd]); useEffect(function () { onPause.current = props.onPause || null; }, [props.onPause]); useEffect(function () { onStop.current = props.onStop || null; }, [props.onStop]); useEffect(function () { onMute.current = props.onMute || null; }, [props.onMute]); useEffect(function () { onVolume.current = props.onVolume || null; }, [props.onVolume]); useEffect(function () { onSeek.current = props.onSeek || null; }, [props.onSeek]); useEffect(function () { onFade.current = props.onFade || null; }, [props.onFade]); useEffect(function () { onRate.current = props.onRate || null; }, [props.onRate]); // Only load the rest of the component once the user wants to play. useEffect(function () { if (initialized) return; if (!shouldPlay) return; setInitialized(true); }, [initialized, shouldPlay]); useEffect(function () { if (!howl || !shouldPlay || !initialized) return; // Play the sound and get its ID. var currentPlayId = howl.play(sprite); setPlayId(currentPlayId); // Initialize with the right settings. howl.volume(volume, currentPlayId); if (mute !== undefined) { howl.mute(mute, currentPlayId); } if (rate !== undefined) { howl.rate(rate, currentPlayId); } // Update children on initial play. howl.once('play', function (id) { if (id !== currentPlayId) return; setUnlocked(true); }); // Set up event listeners, filtered to this play ID. howl.on('play', function (id) { if (id !== currentPlayId) return; if (onPlay.current) { onPlay.current(); } }); howl.on('playerror', function (id) { if (id !== currentPlayId) return; if (onPlayError.current) { onPlayError.current(); } }); howl.on('pause', function (id) { if (id !== currentPlayId) return; if (onPause.current) { onPause.current(); } }); howl.on('end', function (id) { if (id !== currentPlayId) return; if (onEnd.current) { onEnd.current(); } }); howl.on('stop', function (id) { if (id !== currentPlayId) return; if (onStop.current) { onStop.current(); } }); howl.on('mute', function (id) { if (id !== currentPlayId) return; if (onMute.current) { onMute.current(); } }); howl.on('volume', function (id) { if (id !== currentPlayId) return; if (onVolume.current) { onVolume.current(); } }); howl.on('rate', function (id) { if (id !== currentPlayId) return; if (onRate.current) { onRate.current(); } }); howl.on('seek', function (id) { if (id !== currentPlayId) return; setSeeking(false); if (onSeek.current) { onSeek.current(); } }); howl.on('fade', function (id) { if (id !== currentPlayId) return; if (onFade.current) { onFade.current(); } }); return function () { howl.stop(currentPlayId); setInitialized(false); setUnlocked(false); howl.off('play', undefined, currentPlayId); howl.off('playerror', undefined, currentPlayId); howl.off('pause', undefined, currentPlayId); howl.off('end', undefined, currentPlayId); howl.off('stop', undefined, currentPlayId); howl.off('mute', undefined, currentPlayId); howl.off('volume', undefined, currentPlayId); howl.off('rate', undefined, currentPlayId); howl.off('seek', undefined, currentPlayId); howl.off('fade', undefined, currentPlayId); setPlayId(null); }; }, [initialized, howl, sprite]); useEffect(function () { /** * Use playing in state because queued events (like in the above useEffect) * will not apply immediately, so it's possible for us to attempt playing * twice when the sound is initialized. This causes some issues with Howler. */ if (!howl || !playId || !unlocked) return; if (stop) { if (!stopped) { howl.stop(playId); setStopped(true); setPlaying(false); } return; } if (playing && pause) { howl.pause(playId); setStopped(false); setPlaying(false); } else if (!playing && !pause) { howl.play(playId); setStopped(false); setPlaying(true); } }, [howl, playId, stopped, unlocked, playing, pause, stop]); useEffect(function () { if (!howl || !playId || !unlocked) return; if (mute === undefined) return; howl.mute(mute, playId); }, [howl, playId, unlocked, mute]); useEffect(function () { if (!howl || !playId || !unlocked) return; if (volume === undefined) return; howl.volume(volume, playId); }, [howl, playId, unlocked, volume]); useEffect(function () { if (!howl || !playId || !unlocked) return; if (seek === undefined) return; setSeeking(true); howl.seek(seek, playId); }, [howl, playId, unlocked, seek]); useEffect(function () { if (!howl || !playId || !unlocked) return; if (!fade) return; var from = fade[0], to = fade[1], duration = fade[2]; howl.fade(from, to, duration, playId); }, [howl, playId, unlocked, JSON.stringify(fade)]); useEffect(function () { if (!howl || !playId || !unlocked) return; if (rate === undefined) return; howl.rate(rate, playId); }, [howl, playId, unlocked, rate]); useEffect(function () { if (!howl || !playId || !unlocked) return; if (loop === undefined) return; howl.loop(loop, playId); }, [howl, playId, unlocked, loop]); if (!children || !howl) return null; return children({ duration: function () { if (!howl || !playId) return 0; if (sprite) return howl.duration(playId); return howl.duration(); }, playing: function () { if (!howl || !playId) return false; return howl.playing(playId); }, seek: function () { if (!howl || !playId) return 0; // Get seek if (seeking && seek !== undefined) return seek; var position = howl.seek(playId); if (typeof position !== 'number') { return 0; } return position; }, volume: function () { var propsVolume = volume === undefined ? 1 : volume; if (!howl || !playId) return propsVolume; var currentVolume = howl.volume(playId); if (typeof currentVolume !== 'number') { return 0; } return currentVolume; }, }); }