useAudioPlayer
useAudioPlayer copied to clipboard
feat: expose underlying Howl instance and events to the client to allow for greater expandability
Prior to v2 the Howler instance was exposed. I needed access to the Howler api to get the audio context for recording.
This PR attempts to add back that ability by adding a getHowl()
method to the hooks.
I also wanted to expose useHowlEventSync
so that the events could be listened to outside the hooks.
- Add type
getHowl
as Howl toAudioPlayer
. - Add the method
getHowl
touseAudioPlayer
. - Return the
howl
instance inuseAudioPlayer().load()
. - Add the method
getHowl
touseGlobalAudioPlayer
. - Return the
howl
instance inuseGlobalAudioPlayer().load()
. - Export
useHowlEventSync
from the package to allow external usage of the events.
Resolves the previously closed issues: #50 (resolved by this PR ).
hey @kilokeith thanks for the PR! Let's get some of these changes in!
First off, I'm curious what your use case is and how you would be using the event sync hook. It's really too technical to be used by devs IMO. Perhaps there is a way we can extend existing API to make it easier to set up your own event listening? But I'll need to know more about your use case before I can offer any ideas
Regarding getHowl
, I'm totally game to add that new method as an escape hatch, but I don't think we should have load
return the Howl. I think this bc the lib provides a pretty clean abstraction over Howler and returning the Howl here begins to leak implementation details. One other thing to note is that the hooks do some lifecycle management over the Howl object(s). For example the destroy
method may be called messing with the reference returned out of getHowl
For sure, I was wondering if something like destroy
would cause an issue with the howler instance. Exposing the instance ends up being one of the more important features of another library use-sound
, so I thought the load
could work similarly.
const [play, { sound }] = useSound('/win-theme.mp3');
I didn't really have any plans useHowlEventSync
myself. I thought it'd be nice to have an event system that, for instance, you could have an event listener for plays and fire analytics events that way on a more global level rather than be bound to a react render context. There's certainly bigger features I would rather develop rather than that.
Turns out I didn't need any of these added features by the time I finished my project. I wanted to share the audio context with a media recorder so that I could record the screen and get audio without accessing mic permissions or anything extra. But that doesn't come form the howler instances, it's part of the Howler window global.
What I could have used most of all was:
- Ability to play multiple sounds at once from a single
load
. Such as having a timeline like theatre.js where a keyframe has multiple sounds to trigger at a time, but without knowing what need to be played ahead of time it makes it hard to create multiple instances in a hook. Maybe a wrapper aroundload
that creates a group with a single control would work. - Alternatively, a vanilla js version that doesn't need to be in a react hook so I can use a regular function and loops. I know that's just Howler, but I do like the conveniences this package provides. Thinking something like the zustand approach to wrap a vanilla store in a react
use
hook selector. - Similarly, sound banks. Groups of sounds, but allowing either iterating through a list each time
play
is called, or picks a random sound. That would add variation in something like a game. I made a small method that does this and it's nice to have around. - A robust sound manager that caches by a name or compound id, like TanStack Query does query keys. Something that let's me declare a sound once and have it cached throughout the app, but in a way that needs no extra work or knowing what's already loaded. Having nice wrappers around the sounds that take care of preloading, caching, global state control, etc would help greatly in for projects using R3F and similar that have scenes and triggers, and timers. I've thought that maybe I need to make my own library that's a combo of Howler and swr or TS Query if that's what I need. But I've found this package to be pretty great and want to build on top of it.
hey @kilokeith i love those ideas. if you have a moment can you elaborate on this point?
Ability to play multiple sounds at once from a single load. Such as having a timeline like theatre.js where a keyframe has multiple sounds to trigger at a time, but without knowing what need to be played ahead of time it makes it hard to create multiple instances in a hook. Maybe a wrapper around load that creates a group with a single control would work.
@E-Kuerschner I may be able to chime in here. Are you familiar with Ableton Live or any DAW for music production? I believe Keith is stating it'd be nice to be able to display a list of audio files (like drums / vocalist / etc) and load all of them at once so that you could click "play" and have all audio files play in sync at once. I believe he also says it would be difficult to create multiple instances of hooks since those audio files would be dynamic and would need to be loaded from storage thus not knowing how many instances of the hook to create. @kilokeith correct me if I'm on the wrong path though.
I'm coming back to this package after successfully using this in production with this very exact request (made a PR like a year ago if you remember haha)
My search for this feature started from the Howler library where I found an open issue so maybe this helps give you an understanding of what I'd like to have achieved as well https://github.com/goldfire/howler.js/issues/672
Thanks for actively maintaining this library!
Gotcha - I'm tracking! @uncvrd do you think you would mind opening up an Issue/Feature request so that we can discuss and track this there? I'm going to close out this pull request shortly. If you could just list out the high level requirements you think the feature should follow that would be awesome.
As for an API, I was considering introducing an abstraction for an audio bus which the programmer can create and programmatically add sounds too; maybe something similar to this:
const sound1 = useAudioPlayer()
sound1.load()
const sound2 = useAudioPlayer()
const bus = useAudioBus()
// an AudioBus could implement a common interface with an individual sound
bus.load()
bus.play()
bus.pause()
@E-Kuerschner is there any chance this might be merged/released? I'm also looking to get access to the underlying Howl instance to be able to get the duration