Giter Site home page Giter Site logo

e-kuerschner / useaudioplayer Goto Github PK

View Code? Open in Web Editor NEW
297.0 9.0 36.0 84.6 MB

React hooks for controlling audio on the web

License: MIT License

TypeScript 81.46% JavaScript 18.54%
react audio player controls hook react-hooks audio-player audio-player-library react-hook html5-audio

useaudioplayer's Introduction

react-use-audio-player

Typescript package exporting custom React hooks for controlling audio in the browser. Built on top of the amazing howler.js library.

The intent of this package is to provide an idiomatic way to create and manipulate sounds in a React application.

Version CircleCI npm bundle size Buy Me A Coffee donate button

Version 2.0 Upgrade/Migration Guide

Note that v2 is a major upgrade and thus contains breaking changes for your applications. Overall the migration to v2 will involve you taking a few refactoring steps:

  1. remove all uses of useAudioPosition and replace it with your own implementation (see the examples/recipes section below)
  2. replace all uses of useAudioPlayer with useGlobalAudioPlayer which is exported from the v2 package (alongside a net-new hook using the old name of useAudioPlayer - more on that below)
  3. check the docs for useGlobalAudioPlayer below since some API improvements have been made. Most notably, the hook is no longer called with any load arguments/options and instead returns an explicit load function that you must use.

Install

yarn add react-use-audio-player

Usage

To play a sound, import either useAudioPlayer or useGlobalAudioPlayer into a React component. Grab the load function from its return and get jamming!

import { useGlobalAudioPlayer } from 'react-use-audio-player';

function MyComponent() {
  const { load } = useGlobalAudioPlayer();

  // ... later in a callback, effect, etc.
  load('/mySound.wav', {
    autoplay: true
  });
}

Why Two Hooks?

useAudioPlayer and useGlobalAudioPlayer share a lot of similarities. In fact, they return the same AudioPlayer interface (see details below). Your use-case will determine which hook is the most appropriate for you to use.

useGlobalAudioPlayer has some unique functionality. It's purpose is to manage a single, global sound across your entire app. The inspiration for this came from a desire to easily build applications like SoundCloud or Spotify where no matter where you are in the app you can access and control the sound. When you are using this hook you can call it from anywhere in your component tree and it will synchronize with the same audio source as every other instance of useGlobalAudioPlayer.

For example, you could write a Playlist component where clicking a track loads that song and begins playback. Then, on a totally different branch in your component tree, write a PlaybackControls component which calls useGlobalAudioPlayer and uses its play and pause members to start and stop the same song previously loaded by Playlist.

To quickly determine if useGlobalAudioPlayer is right for you, ask yourself these two questions:

  1. Does your app only need to play a single sound at any given time?
  2. Do you want to be able to control this sound from anywhere in your component tree? If the answer is yes to both of these questions, then useGlobalAudioPlayer is the right choice for your application.

useAudioPlayer is the best choice for when you have a simple use-case. Each instance of the useAudioPlayer hook represents its own sound. This means that you can load and play multiple sounds from the same component. For example, you could add separate, unique sound effects for the success and error responses of a fetch request.

Note: Unlike useGlobalAudioPlayer, useAudioPlayer returns an additional method for cleaning up audio if you wish to stop playing and destroy the sound after some interaction (i.e. component unmount, user navigates to a different route, etc.). Without cleaning up, sounds may live on even after the components that created them unmount possibly leading to memory leaks.

useGlobalAudioPlayer and useAudioPlayer can be used simultaneously without one affecting the other.

AudioPlayer (interface)

This is the interface implemented by the returned object of both useAudioPlayer and useGlobalAudioPlayer. The interface defines all the state for a sound and a set of methods to manipulate the state/sound.

State

  • src: string (the src used to load the audio)
  • looping: boolean (is the audio looping)
  • isReady: boolean (is the sound loaded and ready to play)
  • paused: boolean (is the sound paused)
  • stopped: boolean (is the sound stopped i.e. not playing & position 0)
  • playing: boolean (is the sound playing)
  • duration: number (the length in seconds)
  • muted: boolean (is the sound muted)
  • rate: number (the playback rate)
  • volume: number (the volume level 0 - 1.0)
  • error: string | null (error message if any, after attempted load)

Methods

play () => void

Plays the loaded sound. You must invoke this to start playback if autoplay was set to false

pause () => void

Pauses the playing sound

togglePlayPause () => void

Toggles the play/pause state

stop () => void

Stops the playing sound and resets the position to 0.

setVolume (volume: number) => void

Sets the volume level of the loaded audio. Accepts a floating point number between 0 and 1.0 (muted to loudest)

mute (muteOnOff: boolean) => void

Mutes/unmutes the loaded sound

fade (from: number, to: number, duration: number) => void

Fades the sound's volume level from the value of the first argument to the value of the second, over a number of milliseconds as set by the final argument

setRate (speed: number) => void

Sets the playback speed of the loaded sound. Accepts a floating point value between 0.5 and 2.0. Currently half speed is the slowest and double is the fastest supported rates

seek (position: number) => void

Sets the playback position of the loaded sound to the argument. The position argument is floating point number representing the time the audio should move to

loop (loopOnOff: boolean) => void

Sets or unsets whether the sound should loop once it ends

getPosition () => number

Returns the current position of the loaded sound as a floating point number

load (src: string, options?: AudioLoadOptions) => void

Downloads and loads a new sound. The first argument, src is a URI of the sound to be played. The second argument is a set of options applied to the sound once it loads. These options can be used to initialize certain pieces of state on the AudioPlayer interface or be used to set up lifecycle callbacks if needed.

AudioLoadOptions

  • loop?: boolean (sets the initial loop state once the sound loads)
  • autoplay?: boolean (sets if the sound will automatically begin playing after load)
  • initialVolume?: number (sets the initial volume level once the sound loads)
  • initialMute?: number (sets the initial mute state once the sound loads)
  • initialRate?: number (sets the initial playback rate once the sound loads)
  • format?: string (sets the format of the loaded sound - should be set if your URI does not contain an extension)
  • html5?: boolean (loads the sound in an HTML5 audio tag as opposed to using the Web Audio API)
  • onplay?: () => void (callback for when audio begins playing)
  • onstop?: () => void (callback for when audio is stopped)
  • onpause?: () => void (callback for when audio is paused)
  • onload?: () => void (callback for when audio finishes loading)
  • onend?: () => void (callback for when audio has reached its end)

Quick Recipes & Gotchas

For full, example applications see the runnable examples in the repo. Below are a few snippets to help with some of the trickier use-cases.

Recipe: Switching between sounds on a single audio player

Switching from one sound the next is a common use-case (i.e. a playlist queue). This can be done in a couple of different ways:

// the same solution will work with useGlobalAudioPlayer
const { load } = useAudioPlayer();

const nextTrack = () => {
  load(nextSong, { autoPlay: true });
};

return <button onClick={nextTrack}>Start next track</button>;

Alternatively, you can queue up the next song to play when the current sound ends. You can see a full, working example of this in the AutoPlayNextSong component in /examples.

const songs = [songA, songB];
const [songIndex, setSongIndex] = useState(0);

const { load } = useAudioPlayer();

useEffect(() => {
  load(songs[songIndex], {
    autoplay: true,
    onend: () => setSongIndex(songIndex + 1)
  });
}, [songIndex, load]);

Recipe: Syncing React state to live audio position

In previous 1.x versions of the library, a separate hook was exported from the package which tracked state representing the current seek time of a playing sound. While this was helpful it ultimately fell outside the scope of the hook as the v2 rewrite took shape. This is mainly due to the difficulty of supporting the feature for both forms of the hook useAudioPlayer/useGlobalAudioPlayer.

Luckily, even without a dedicated hook, it is trivial to implement the same functionality yourself. Below is one method for how you might write your own hook for tracking seek time.

function useAudioTime() {
    const frameRef = useRef<number>()
    const [pos, setPos] = useState(0)
    const { getPosition } = useGlobalAudioPlayer()
    
    useEffect(() => {
        const animate = () => {
            setPos(getPosition())
            frameRef.current = requestAnimationFrame(animate)
        }

        frameRef.current = window.requestAnimationFrame(animate)

        return () => {
            if (frameRef.current) {
                cancelAnimationFrame(frameRef.current)
            }
        }
    }, [getPosition])
    
    return pos;
}

Gotcha: Streaming audio

To stream or play large audio files, the audio player must be forced to use HTML5 as opposed to the Web Audio API which is Howler's default. This is because the Web Audio API must download the entirety of the sound before playing anything.

When streaming or working with large files make sure to use the html5 option of the #load function.

Also, if your sound src string does not contain an extension (like if you are fetching a stream from an API), be sure to set it with the format option of the #load function.

More information in this Howler thread

const { load } = useAudioPlayer();

load('https://stream.toohotradio.net/128', {
  autoplay: true,
  html5: true,
  format: 'mp3'
});

Examples

Eventually I would like to host & run the examples somewhere on the web, but for now to run them yourself locally, follow the following steps:

  1. git clone the repository
  2. cd useAudioPlayer/examples
  3. yarn install
  4. yarn start
  5. follow the local README for further assistance

Contributing

Please consider opening an Issue or Pull Request on the Github and I will do my best to respond to these in a timely manner.

Release

The most basic npm release strategy is being followed for now. A good explanation can be found here.

Steps

  1. commit work & tests
  2. yarn/npm version (preversion script will ensure code is tested and built)
  3. yarn/npm publish
  4. git push & git push --tags

useaudioplayer's People

Contributors

brianshano avatar cb-ekuersch avatar dbismut avatar dependabot[bot] avatar e-kuerschner avatar humbkr avatar jjhamshaw avatar theslantedroom avatar zmarkan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

useaudioplayer's Issues

Loading state is not reset to true when loading a new sound

The AudioPlayerProvider state defaults loading state to true. This is helpful when loading a sound for the first time, but for loading subsequent sounds the state is never set back to true, potentially leading to some unexpected state in client's code.

How to reproduce
Steps to reproduce the behavior:

  1. load a sound
  2. load another sound
  3. observe that while the second sound is loading (you can check the network tab in Chrome and even simulate a slower connection in order to catch this behavior more easily) the loading state returned by the useAudioPlayer hook is never set back to true

Expected behavior
The loading state is reset to true any time an audio is being loaded

Environment (please complete the following information):

  • Library version: 0.0.11

HTML5 network requests in Chrome stuck as pending after 6th song?

Hi! Thanks for creating this excellent library. I ran in to a bug, and I'm not quite sure if it's HowlerJS or this library's implementation of HowlerJS that's causing the following issue, so any guidance would be super helpful!

I discovered that Chrome is unable to resolve an HTML5 206 partial response network request after the 6th file has been loaded. So for example, if I have a queue of songs, I could click "play" to stream each track, but when I try and stream the seventh track, it hangs as "Pending" in the network tab. After some searching, I discovered that Chrome limits the number of active network requests the browser can poll, see this SO answer for more of an explanation: https://stackoverflow.com/a/29639535/6480913

This has left me puzzled because observing the network tab, we can see that all 206 partial requests have resolved, so why would this last file still hang? I figure this has to do with how Howler manages the state of the audio nodes generated programmatically? And for some reason Chrome thinks that the previous 6 audio files are still polling even though their corresponding network requests have resolved? I think I'm on the right track but I am stuck on how to move forward.

Here's a small code sandbox reproducing this problem. I added two play buttons, every time you click a play button, a unique ID is appended to the url to prevent the browser from caching the audio file.

Sandbox: https://codesandbox.io/s/hopeful-kare-ih0omh?file=/components/Music.tsx:0-996

Steps (Must be on Chrome / Brave):

  1. Open up the network tab (specifically the media network tab)
  2. Cycle between clicking play of the first button and second button 6 times
  3. On the 7th click, observe the network tab and see how the request never resolves (stuck at pending).

Here's a video showing the problem: https://streamable.com/qlqryy

Expected behavior
I expect Chrome to not prevent loading of more than 6 songs.

Environment (please complete the following information):

  • Browser/ browser version: Chrome 100.0.4896.127
  • Library version: 1.2.5
  • React version: 16.10.1

Thank you for any insight!!

Rapid src changes creates duplicate players

Describe the bug
When using a redux state store for the URL in the player, if you update the URL repeatedly quick enough, it creates duplicate versions of the player which can no longer be controlled. I see that this was reported here: https://github.com/E-Kuerschner/useAudioPlayer/issues/18, but using the latest version (0.0.15), I'm still facing the issue.

To Reproduce
I've created a small repo with my use-case for you, which can be viewed here: https://github.com/joshcawthorne/useAudioPlayerDuplicationBug. It can also be accessed via this URL: https://use-audio-player-duplcation-bug.netlify.com/.

To recreate, simply quickly press around on the different track links, if you do it quick enough for about 5-10 seconds, you'll start hearing duplicate versions of the player which are uncontrollable.

Expected behavior
One version of the player to only ever exist, no matter how many times the src is updated.

Environment (please complete the following information):

  • Browser/ browser version: Chrome 81
  • Library version: 0.0.15
  • React version: 16.13.1
  • Node version: 12.14.1

Thanks for all your work thus far - this is a really brilliant library!

Multiple build targets

In an effort to modernize, I want to publish multiple versions of the useAudioPlayer bundle. A pre-bundled and compiled version to be used with may status-quo setups (downstream build tool that ignore node_modules, assuming they are already suitable for their target environments) and another bundle that preserves module syntax and other modern javascript features.

Add percentage to useAudioPosition

It is common to calculate what percentage of the duration the current position is at. By adding this computed value to the output of the useAudioPosition hook, we may be saving some work for a large set of users.

Question: How would I go about making a persistent player....

Hi again,

Sorry if I'm blowing your issues up, but this is a really nice package, and I'm curious if it might be able to handle my use case.

I have made this app:

rykr.netlify.com

Which is a blog and music player in one, with a persistent player on the bottom of the screen at all times. I am trying to run all audio though it, but I currently need to define a set of files for the player to play at one time, like so:

const { tracks, assets } = useStaticQuery(graphql`
    query Tracks {
      tracks: allMdx(filter: { fileAbsolutePath: { regex: "/content/music/" } }
      ) {
        edges {
          node {
            fields {
              slug
            }
            frontmatter {
              name
              artist
              genre
              bpm
              # ADD BASE HERE
              artwork {
                base
                childImageSharp {
                  fluid(maxWidth: 1000) {
                    ...GatsbyImageSharpFluid
                  }
                }
              }
              alt
              description
              release(formatString: "MMMM Do, YYYY")
              audio {
                absolutePath
                base
              }
            }
          }
        }
      }

      # query all mp3 and jpg files from /content/music/
      assets: allFile(
        filter: {
          extension: { in: ["mp3", "jpg"] }
          absolutePath: { regex: "/content/music/" }
        }
      ) {
        edges {
          node {
            publicURL
            relativePath
          }
        }
      }
    }
  `)

  // This uses the map function to create an array of objects, where each object is a track and all it's information and assets. We need to use `useMemo` to avoid re-computation when the state changes as this information is all static.
  const trackList = useMemo(
    () =>
      tracks.edges.map(track => {
        const slug = track.node.fields.slug
        const { frontmatter } = track.node
        const {
          name,
          artist,
          genre,
          bpm,
          artwork,
          alt,
          description,
          audio,
        } = frontmatter

        return { slug, name, artist, genre, bpm, artwork, alt, description, audio }
      }),
    [tracks]
  )

...so I am using a graphql staticQuery in gatsby to generate an array of tracks, which I then pull in to the player and it works ok, like so:

 const [state, setState] = useState({
    audioPlayer: {},
    currentTrackIndex: null,
    isPlaying: false,
  })
  
  const [currentTime, setCurrentTime] = useState(state.audioPlayer.currentTime)
  
  useEffect(() => {
    setState({
      audioPlayer: new Audio(),
      currentTrackIndex: null,
      isPlaying: false,
    })
  }, [])
  
  const formattedTime = getTime(currentTime) || `0:00`
  const formattedDuration = getTime(state.audioPlayer.duration) || `0:00`

// etc, etc etc...

...but what I really want is to have pieces of audio on many different pages in the app, and all of the audio everywhere is connected to the audio context and can be played, and when it is played it is then added to the array, and this would include full songs, audio snippets, sound effects, examples, etc...all on different pages, but all being sent through one audio context.

I have no idea who to go about doing this, but was curious if you might have any ideas about how this might be possible to do in a react and/or gatsby app, or even just in general.

This is more a discussion, I guess, then any specific issue, just want to get your thoughts, if you have the time and interest to share them :-)

Cannot read property 'player' of null

If you have an HTML element inside a react three fiber drei Html component, it won't "wait" for the audio player component to regiter or mount and I get this error.

Cannot read property 'player' of null

Warning: useLayoutEffect does nothing on the server

I use useAudioPlayer in a Next.js project and the useAudioPosition triggers a lot of warning in the logs of the server:

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.

which makes reading the logs difficult.

One of the solution is to create a wrapper:

import { useEffect, useLayoutEffect } from 'react'

const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

export default useIsomorphicLayoutEffect

@E-Kuerschner if you're okay with this solution, I can implement it.

Player doesn't play radio stream

Describe the bug
Player doesn't play radio stream. Howler docs says that live stream can only be played through HTML5 Audio, but I can't trigger it.

sound =  new Howl({
  src: data.src,
  html5: true, // A live stream can only be played through HTML5 Audio.
  format: ['mp3', 'aac']
});

To Reproduce

  1. Set src key in useAudioPlayer to play radio stream, e.g.
    https://eu6.fastcast4u.com/proxy/wydra?mp=/1

Expected behavior
Player plays radio stream. I recommend add optional argument html5: true to play in <audio> tag.

Environment:

  • Browser/ browser version: Chrome 81
  • Library version: 0.0.17
  • React version: 16.12
  • Node version: 10.15.3

Access the fade method

How can I access the fade method to fade in and out tracks?
It's probably obvious but I can't find it...

I tried using the player function in useAudioPlayer but that doesn't seem to be the right approach

 const { togglePlayPause, ready, loading, playing, stop, play, player } = useAudioPlayer({
    src: file,
    format: "mp3",
    loop: true,
    autoplay: true,
    html5: isIos,
    // try here? nope
    onplay: () => {
      player.fade(0, 1, 5000);
    },
  });

  useEffect(() => {
    // trying here - nope
    isPlaying ? play() : player.fade(1, 0, 100)
  }, [isPlaying]);

ProgressBar 这个组件在哪

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

useAudioPosition to support seeking

First of all this project is amazing and has saved me a lot of hassle - thank you.

In the docs it suggests that the useAudioPosition could be used for a seek bar which would be really great. However, from what I understand, it only returns the read-only 'position' and 'duration' values so it can't actually be used for seeking.

It would be helpful if useAudioPosition returned a function that could set the current playback position

Multiple audio sources playing

Describe the bug
In short: multiple instances of return values from useAudioPlayer seems to be only partly in sync.

I have a component structure like this:

App.js
| PlayerPage
   | - List
   |    | - ListElement
   |
   | - PlayerControls

in App.js I wrap the PlayerPage inside an
Then I call useAudioPlayer to set source and some other options in List.
I also call useAudioPlayer with no arguments in PlayerControls, and it seems to pick up the playing state fine, but when I use play() or pause() from there it does not affect the playing track!

This behaviour seems very weird to me, but hopefully there is just something I haven't catched.

To Reproduce
Steps to reproduce the behavior:

  1. Go to the sandbox below
  2. Click a green element in the playlist to select it
  3. Click again, if required, to start playing, and again a couple times to pause/play
  4. Observe the Play and Pause buttons states and console output
  5. Let track keep playing
  6. Pause track from Pause button
  7. Observe that music still plays, and id is null in console
  8. Click Play button
  9. Observe that the sound is being played twice, simultaneously, and new id in console
  10. Click Play/Pause multiple times, and hear that a new track starts playing for each Play click
  11. Finally pause music by clicking on the green play list element.
  12. Observe that all audio ids are paused and enjoy the silence

Codesandbox: https://codesandbox.io/s/useaudioplayer-multiplay-4uz28j?file=/src/List.js

Expected behavior
The playing state should be the same for both instances of call to useAudioPlayer.
Calling pause() in one instance should pause the sound playback initiated from the other and vice versa.

Environment (please complete the following information):

  • Browser/ browser version: Brave v1.49.120 / Firefox 110
  • Library version: 1.2.6
  • React version: 18.2.0
  • Node version:

Integration testing

The Problem

The library should be tested on some level that ensures it integrates with React in a browser correctly

Solution

An automated unit or integration test suite. It may be a challenge to write tests when dealing with several layer of abstraction around an already difficult side-effect to test, that being audio playback. Tools like Cypress may come in handy if a browser-like environment is necessary.

Export AudioPosition interface from useAudioPosition

Is your feature request related to a problem? Please describe.
Instead of having to a create a duplicate of the AudioPosition interface inside react-use-audio-player/dist/useAudioPosition, I would like to be able to import the interface from react-use-audio-player (similar to the AudioPlayerControls type).

Describe the solution you'd like
Export the interface AudioPosition from react-use-audio-player/dist/useAudioPosition, so it is accessible from react-use-audio-player.

Audio playing multiple streams over itself

I've built a small playlist-like setting similar to the example.

Here's my click method on a list of tracks

  const handleClick = track => {
    setCurrent(track);
    load({ src: track.url, autoplay: !playing });
  };

Clicking around on the tracks mostly works - but then one will get "stuck" and you have to refresh to get it to stop.

I'm wrapping the entire thing in a single AudioPlayerProvider so not dealing with multiple contexts or anything.

I tried adding a stop() in there before load() but same problem.

Add completed callback

Describe the solution you'd like
I'd like a callback when a track has naturally finished playing (not user stopped).

In my case I'm using it to autoplay the next track in playlist.

Describe alternatives you've considered
I've tried testing percentage duration, but it resets to zero before hitting 1.0

Additional context
This is how I'm doing it now:

  const { position, duration } = useAudioPosition({ highRefreshRate: true });
  const [current, setCurrent] = useState(tracks[0]);

  useEffect(() => {
    const percent = position / duration || 0;
    if (percent >= 0.99) {
      const currentIndex = tracks.findIndex((t) => t.id === current.id);
      if (currentIndex + 1 < tracks.length) {
        setCurrent(tracks[currentIndex + 1]);
      }
    }
  }, [position, duration]);

Ease difficulty to handle end events

The current API does not make it easy to react to the event in which the playing audio ends. An improved API would allow a developer to register a callback which is called whenever the current audio ends.

expose howl instance via the API

Is your feature request related to a problem? Please describe.

I was trying to set the playback rate but there's no method to do this via the useAudioPosition / useAudioPlayer hooks. Howler.js exposes this method via .rate() method

Describe the solution you'd like

expose the howler player instance in the useAudioPlayer hook so it's easy to access all other methods i.e

const { player } = useAudioPlayer();
function onChangeRate(rate) {
   player.rate(rate);
}

alternatively, you can just add rate function:

const { rate } = useAudioPosition();
function onChangeRate(rate) {
   rate(rate);
}

Describe alternatives you've considered
I had to get the howler instance via the global Howler._howls object:

function onChangeRate(rate) {
   const Howler = window.Howler;
   const howl = Howler._howls.find((h) => h._src === my_src);
   howl.rate(rate);
}

Additional context
Add any other context or screenshots about the feature request here.

Howler.js 2.2.0 doesn't play WAV files in Safari 14

Describe the bug
Howler.js 2.2.0 doesn't play WAV files in Safari 14

See here: goldfire/howler.js#1414

Environment (please complete the following information):

  • Browser/ browser version: Safari 14
  • Library version: "react-use-audio-player": "^1.2.2",
  • React version: -
  • Node version: -

I've created a PR which bumps the howler.js version to 2.2.1 - which includes a fix for this bug

Ability to start playback at a given position

Is your feature request related to a problem? Please describe.
When the user resumes listening in a new session, it would be nice to let them resume where they left off. Storing that value should be up to me as a developer, but starting the playback at a given location would be nice.

Describe the solution you'd like
play() could take an argument about where to start, or seek could be leveraged as soon as the audio is loaded.

audio keeps playing when pressing the "pause" button after using "seek"

Hey, thank you for this package!

Describe the bug
I've got a funky bug in IE11. The Play/Pause behaviour is broken as soon as a user changes the position of the audio. The button state is updated correctly, but the audio file keeps on playing. I did a quick screenrecording but github doesn't allow video files so I've zipped it ...

play-pause.mp4.zip

To Reproduce
Steps to reproduce the behavior:

  1. Spin up the test server in useAudioPlayer/examples
  2. Click on 'Play'
  3. Change the position of the audio by clicking on the progress bar
  4. Hit Pause
  5. The audio keeps on playing (there's no error in the console)

Expected behavior
The audio should be paused

Environment (please complete the following information):

  • Browser/ browser version: 11.15.16299.0
  • Library version: v0.0.15

Duration is 0 until audio is playing

Describe the bug
It looks like duration is zero until the audion starts playing.

To Reproduce
Steps to reproduce the behavior:

  1. Load file
  2. Log duration

Expected behavior
Duration should be provided as soon as the file is loaded.

While I understand why you would want avoid rerendering if position and duration aren't used, I fail to see why duration couldnt be a part of the useAudioPlayer hook.

How to play multiple piano samples

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Hi there, in Howler Im able to play simultaneously multiple piano samples for piano app, though when using react hooks, sound will start to get very distorted and then stop playing, so im looking for replacement, basically need ability to load multiple files and to fade out sounds, neither of which I was able to figure yet out, is there any example? Example in this library with useState will stop playing first file if second is started so need different solution.

This is code im using in Howler.js that is working, but couldn't figure out how to do similar with this library

  var m21 = new Howl({ src: ['assets/PianoWav/21.wav'] });
  var m22 = new Howl({ src: ['assets/PianoWav/22.wav'] });
  var m23 = new Howl({ src: ['assets/PianoWav/23.wav'] });

  let midiToHowl = {
    21: m21,
    22: m22,
    23: m23,
}

  function OsmdNoteOn(n, dur) {
    let noteName;
    noteName = midiToHowl[n];
    noteName.play();
    setTimeout(() => noteName.fade(1, 0, 2000), dur);
  }

Playback events are not triggered by media session API

Describe the bug
Chrome and other browsers now ship with a feature to control media directly from the browser using the media session API:
https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API
https://techcrunch.com/2020/01/16/chrome-gets-global-media-controls

Problem: when using the Chrome media controls, the playback events like play, pause, ... are not triggered, ie isPlaying is not updated, resulting in an out of sync UI.

To Reproduce
Steps to reproduce the behavior:

  1. Using Chrome, start playing an audio file via an html button
  2. Use the browser media controls to pause the playback
  3. The audio is paused but the isPlaying variable is not updated

Expected behavior
The useAudioPlayer() state reflects the state of the audio playback.

Environment (please complete the following information):

  • Browser/ browser version: Chrome
  • Library version: 0.0.18
  • React version: 16.3.1
  • Node version: 10.15.0

Related issue: goldfire/howler.js#1262

我想知道倍数 播放哪里可以设置

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Audio does not seem to be playing on the iPhone speaker...

ok, here's an actual issue, I think. I set up your package on a page in an app I'm building to test it out:

https://rykr.netlify.com/test/

...and it seems to work fine everywhere I have tested it except for my iPhone, in which I hit the play button after the audio has loaded, but no sound plays through the phone speaker. If I connect it to another speaker through a cable or bluetooth it seems to work, just not the phone speaker itself.

Kind of an odd issue, I know, but wondering if you have ever heard of this kind of thing at all. I am aware that the web audio API has historically had a few issue in safari and webkit, but I don't know why it's not working through the speaker only...quite odd...

This is how I set up your package on that page in the app, just basically copying the example:

import React from 'react'
import styled from 'styled-components'
import { useAudioPlayer } from 'react-use-audio-player'
import { AudioPlayerProvider } from 'react-use-audio-player'

import baktun from '../content/music/baktun/baktun.mp3'

const Box = styled.div`
  grid-column: 1 / 8;
  align-self: center;
  justify-self: center;
  border: 1px solid white;
  width: 16rem;
  height: 20rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: 32px;
`

const Button = styled.button`
  margin: 1rem;
  padding: 0.75rem 2rem;
  background: #006964;
  border: 1px solid #00fff3;
  border-radius: 16px;
  color: white;
  font-size: 32px;
`

const Name = styled.h1`
  margin: 1rem;
  padding: 0;
  font-family: 'Odachi', sans-serif;
  font-size: 32px;
`  

const AudioPlayer = ({ file }) => {
  const { play, pause, ready, loading, playing } = useAudioPlayer({
    src: file,
    format: "mp3",
    autoplay: false
  })

  const togglePlay = () => {
    if (playing) {
      pause()
    } else {
      play()
    }
  }

  if (!ready && !loading) return <div>No audio to play</div>
  if (loading) return <div>Loading audio</div>

  return (
    <div>
      <Button onClick={togglePlay}>{playing ? "Pause" : "Play"}</Button>
    </div>
  )
}

const Test = () => {
  return (
    <AudioPlayerProvider>
      <Box>
        <Name>b'ak'tun</Name>
        <AudioPlayer file={baktun} />
      </Box>
    </AudioPlayerProvider>
  )
}

export default Test

Maximum update depth exceeded when using React-Router

Using the Provider and audio hooks under React-Router produces a Maximum update depth exceeded error loop

const routes = {
  '/': () => <Provider station='clouds' />,
  '/:station': ({ station }) => <Provider station={station} />
}

function Provider ({ station }) {
  return (
    <AudioPlayerProvider>
      <Radio station={station} />
    </AudioPlayerProvider>
  )
}

function Radio (props) {
  const [song, setSong] = useState({
    album: 'Press the Play button to start the radio',
    title: placeholders[Math.floor(Math.random() * placeholders.length)]
  })

  const { togglePlayPause, ready, loading, playing, volume, mute } = useAudioPlayer({
    src: `https://play.squid-radio.net/${props.station}?cache_ts=${new Date().getTime()}`,
    onend: () => console.log('sound has ended!')
  })

  return (
    <div />
)
}

const App = () => {
  const routeResult = useRoutes(routes)
  return routeResult || (
    <script>{(
      window.location.href = '/404.html'
    )}
    </script>
  )
}

Environment (please complete the following information):

  • Browser/ browser version: Firefox 81.0.2 64-bit
  • Library version: 1.2.2
  • React version: 16.13.1
  • Node version: 14.x

Loop audio

When I pass loop prop it works but the thing is I need to update the loop prop while the audio is playing.

When I do that I doest update unless I re-render

Seeking on streamed audio

Describe the bug
There may be an issue when attempting to seek on live, streamed audio.

To Reproduce
Steps to reproduce the behavior:

  1. run examples (yarn start in ./exampes)
  2. go to Basic Example and choose the live audio from the sound source drop down
  3. hit play
  4. click forward in the time bar

There will be an error. The seek function is being passed the value of Infinity.

Expected behavior
Im not sure what the expected behavior should be. Maybe seeking should be disabled while streaming if we can detect that case.

Environment (please complete the following information):

  • Browser/ browser version:
  • Library version:
  • React version:
  • Node version:

useAudioPosition() position is not updated by calling seek() when the audio is not playing

Describe the bug
useAudioPosition() position is not updated by calling seek() when the audio is not playing, it's only updated when you start the playback again.

To Reproduce
Steps to reproduce the behavior:

  1. Create an audio player with a clickable progress bar calling seek() on click
  2. Play an audio file and click the progress bar to seek to a new position: the "position" variable is updated across the components using useAudioPosition()
  3. Pause the playback
  4. Click the progress bar to seek to a new position: the "position" variable is not updated
  5. Start the playback again: the position variable is updated.

Expected behavior
The "position" variable instantly updates when seek() is called, independently of the playback status.

Environment (please complete the following information):

  • Browser/ browser version: Chrome
  • Library version: 0.0.18
  • React version: 16.13.1
  • Node version: 10.15.0

Combined play/pause action

It would be convenient to have a method to toggle play/pause state since this is a common action.

The method name could be togglePlay or togglePlayPause to be used like so:

const PlayButton = () => {
  const { togglePlayPause, playing } = useAudioPlayer(...)
  const buttonText = playing ? "Pause" : "Play"
  return <button onClick={togglePlayPause}>{buttonText}</button>
}

Help: Dunno what I'm doing here...

dunno how to make a seekable progress bar from your example. Here's what I'm trying to do:

const ProgressSlider = () => {

  const { percentComplete, duration, position, seek } = useAudioPosition()

  const goToPosition = React.useCallback((percentage) => {
    seek(duration * percentage)
  }, [duration, seek])

  return (
    <input
      max={duration}
      min="0"
      step="1"
      type="range"
      value={position}
      onClick={goToPosition}
    />
  )
}

...but I don't know how to make the click handler work right...where does percentage come from? Is there a simpler way? Just trying to use a range input for now...

Getting this error when I click on the Progress Bar to move the audio around:

Screen Shot 2020-09-18 at 6 58 16 PM

...HALP??? XD

Continuous integration

Setup infrastructure for tests to run on every commit or merge. A test passing badge on the README would also be a nice touch

Move position state to Provider

The useAudioPosition hook stores its own position state and encapsulates its own seek function. This doesn't allow a developer to use multiple instances of the hook and maintain synchronized stated when seek is invoked from one of them.

An example use case would be this: A developer has a seek bar component and a separate time component. When seek is invoked from within the seek bar component the position in both the seek bar and time components should be the same.

A separate React context for holding position state may be necessary so that consumers of the other player state (duration, isPlaying, etc.) do not rerender constantly as the position changes every second.

Expose Howler's volume control

It would be really good to be able to let the user control the volume with a range slider as opposed to a binary on/off control.

I know Howler lets you control volume on a 0 - 1 sliding scale so it'd be really helpful if you could expose this control.

The mute/unmute option works sufficiently for now but a range option would be a massive plus.

Thanks

How to automatically play when switching audio

I try to use the onend call back function to change the audio src, but it didn't automatically play, even I use the player.paly() to play the audio, but it's no use. So could you give me a solution to solve this problem.

Rewrite without React context

Currently the library makes use of React context to maintain the state of the audio instance. This requires the portion of the app using this state to be wrapped in a context Provider. Since a howler is stored globally on the window after it is created would it be possible to axe the Provider and derive all state from the globally stored howl? This would improve the usability of this library as it is frustrating to have to wrap elements in Providers.

Perhaps removing the Provider may introduce the necessity of more hooks to recall previously created hooks. This may not be a bad thing and should be judged at the time if/when it comes up.

position is sometimes Howl

The position returned from useAudioPosition is occasionally the Howl object.
My understanding is that it should always be a number.

Steps to reproduce the behavior:
This seems to happen only when the track has yet to start or when the position is less than a second

  • Browser/ browser version:
    Firefox, Safari, Chrome

Thanks

Case insensitive formats

Some radio station APIs return formats (.mp3, aac, ...) in upper case. Because of this, the player get stuck on infinite loading with no apparent reason. Takes a while to figure out the problem

Steps to reproduce:

useAudioPlayer({
    src: "https://stream.toohotradio.net/128",
    html5: true,
    format: ["MP3"],
  });

Use howler typings for input arguments

Is your feature request related to a problem? Please describe.
Using the official typings of howler where appropriate makes it a consistent interface for all consumers.

Describe the solution you'd like
leverage the howler typings for the input (and possibly internal) arguments/properties.

Describe alternatives you've considered
@ts-ignore or override the typings from the consumer code

Opt-in smoother animations for useAudioPosition

1Hz state updates are sometimes not desirable since they can lead to jagged animations and poor progress resolution for shorter audio files. However 60fps (requestAnimationFrame) updates can cause many re-renders which may also not be desirable for some users.

Describe the solution you'd like
A way to opt-in to more frequent state updates in the useAudioPosition hook

Describe alternatives you've considered
N/A

Additional context
N/A

Support for multiple audio sources

The Problem

Currently the library only supports one audio source being loaded at a time. If a user were to play a different sound the current sound object (the one currently playing) would need to be destroyed and a new instance created and loaded. This poses a problem for common interfaces like Spotify or any music library. A user will typically want to play their song the moment they click on a sound. If that sound has to initialize and load at the time they click it there could be some frustrating delay.

Possible Solution

There should be a way to register an audio source with the library and interact with it at a later time. For our audio library example, all the songs on screen could be loaded, then when the user clicks a song it may begin playing immediately.

Considerations

This library was created initially with the intention of only handling a single audio source out of simplicity. Is it the case that many applications have use cases for many audio sources within a single AudioContext?

WAV files do not load on iPad iOS 15.2

Describe the bug
My react code is able to play MP3s and Wavs on a desktop, but not on Mobile safari on iOS 15.2 (iPad Air 4th Gen). The WAV files do not play. Adding html5:true to the howler config will fix things, however that seems like a patch and not a solution.

To Reproduce
Code Example:

case 'playSound': {
          const logSoundName = payload.data.url.split('/')
          console.log(`🍎 attempting to play sound: ${logSoundName[logSoundName.length-1]} 🍎`);
          const sound = new Howl({
            src: [payload.data.url],
            volume: 0.2,
            onload: (soundId: number) => {
              console.log(`🍎 onLoad - attempting to play sound# ${soundId} 🍎`);
              sound.play();
            },
            onplay:(soundId: number) => {
              console.log(`🍎 onPlay sound# ${soundId} 🍎`);
            },
          });

          sound.once('loaderror', (e) => console.log(`load error: ${e}`))
          sound.once('playerror', (e) => console.log(`playerror: ${e}`))

          break;
        }

Expected behavior

  • Inspecting the Safari console (iPad simulator) I can see the following logs for playing a WAV (first log) and MP3 (final 3 logs). As you can see the logs are not firing for the sound loading, but also they are not firing for any error.
  • The playerror or loaderror callbacks never happen
  • The play callback is also never called. The sound is neither playing nor failing with error.

Environment (please complete the following information):

  • Browser/ browser version: Safari iOS 15.2
  • Library version: 2.2.0
  • React version: 17.0.2
  • Node version: 14

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.