Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose underlying Howl instance and events to the client to allow for greater expandability #118

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kilokeith
Copy link

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 to AudioPlayer.
  • Add the method getHowl to useAudioPlayer.
  • Return the howl instance in useAudioPlayer().load().
  • Add the method getHowl to useGlobalAudioPlayer.
  • Return the howl instance in useGlobalAudioPlayer().load().
  • Export useHowlEventSync from the package to allow external usage of the events.

Resolves the previously closed issues: #50 (resolved by this PR ).

…ow for greater expandability.

Add type `getHowl` as Howl to `AudioPlayer`. Add the method `getHowl` to `useAudioPlayer`. Return the `howl` instance in `useAudioPlayer().load()`. Add the method `getHowl` to `useGlobalAudioPlayer`. Return the `howl` instance in `useGlobalAudioPlayer().load()`. Export `useHowlEventSync` from the package to allow external usage of the events.

Resolves the previously closed issues: E-Kuerschner#50, E-Kuerschner#48
@E-Kuerschner
Copy link
Owner

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

@kilokeith
Copy link
Author

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 around load 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.

@E-Kuerschner
Copy link
Owner

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.

@uncvrd
Copy link
Contributor

uncvrd commented Aug 13, 2023

@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 goldfire/howler.js#672

Thanks for actively maintaining this library!

@E-Kuerschner
Copy link
Owner

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()

@mrdziuban
Copy link

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants