vueuse / sound

πŸ”Š A Vue composable for playing sound effects
https://sound.vueuse.org
MIT License
483 stars 26 forks source link
audio composition-api hooks sound vue vue-composable vue3

πŸ”Š @vueuse/sound

npm npm Netlify Status

If you want to take a quick look at the composable in effect, you should visit the 🌍 demo.

This package is a Vue version of the useSound React hook by joshwcomeau.

Installation

Package can be added using yarn:

yarn add @vueuse/sound

Or, use NPM:

npm install @vueuse/sound

Examples

Play a sound on click

This is the most basic example of how fast you can implement sounds in your app using @vueuse/sound.

<template>
  <button @click="play">Play a sound</button>
</template>

<script>
import { useSound } from '@vueuse/sound'
import buttonSfx from '../assets/sounds/button.mp3'

export default {
  setup() {
    const { play } = useSound(buttonSfx)

    return {
      play,
    }
  },
}
</script>

Playing on hover

This example is shown in the demo.

Increase pitch on every click

This example is shown in the demo.

Usage Notes

No sounds immediately after load

For the user's sake, browsers don't allow websites to produce sound until the user has interacted with them (eg. by clicking on something). No sound will be produced until the user clicks, taps, or triggers something.

useSound takes advantage of this: because we know that sounds won't be needed immediately on-load, we can lazy-load a third-party dependency.

useSound will add about 1kb gzip to your bundle, and will asynchronously fetch an additional package after load, which clocks in around 9kb gzip.

If the user does happen to click with something that makes noise before this dependency has been loaded and fetched, it will be a no-op (everything will still work, but no sound effect will play). In my experience this is exceedingly rare.

Reactive configuration

Consider the following snippet of code:

const playbackRate = ref(0.75)

const { play } = useSound('/path/to/sound', { playbackRate })

playbackRate doesn't just serve as an initial value for the sound effect. If playbackRate changes, the sound will immediately begin playing at a new rate. This is true for all options passed to the useSound composable.

API Documentation

The useSound composable takes two arguments:

It produces an array with two values:

When calling the function to play the sound, you can pass it a set of options (PlayOptions).

Let's go through each of these in turn.

ComposableOptions

When calling useSound, you can pass it a variety of options:

Name Value
volume number
playbackRate number
interrupt boolean
soundEnabled boolean
sprite SpriteMap
[delegated] β€”

[delegated] refers to the fact that any additional argument you pass in ComposableOptions will be forwarded to the Howl constructor. See "Escape hatches" below for more information.

The play function

When calling the composable, you get back a play function as the first item in the tuple:

const { play } = useSound('/meow.mp3')
//      ^ What we're talking about

You can call this function without any arguments when you want to trigger the sound. You can also call it with a PlayOptions object:

Name Value
id string
forceSoundEnabled boolean
playbackRate number

ExposedData

The composable produces a tuple with 2 options, the play function and an ExposedData object:

const [play, exposedData] = useSound('/meow.mp3')
//                ^ What we're talking about
Name Value
stop function ((id?: string) => void)
pause function ((id?: string) => void)
isPlaying boolean
duration number (or null)
sound Howl (or null)

Advanced

Sprites

An audio sprite is a single audio file that holds multiple samples. Instead of loading many individual sounds, you can load a single file and slice it up into multiple sections which can be triggered independently.

There can be a performance benefit to this, since it's less parallel network requests, but it can also be worth doing this if a single component needs multiple samples. See the Drum Machine component for an example.

For sprites, we'll need to define a SpriteMap. It looks like this:

const spriteMap = {
  laser: [0, 300],
  explosion: [1000, 300],
  meow: [2000, 75],
}

SpriteMap is an object. The keys are the ids for individual sounds. The value is a tuple (array of fixed length) with 2 items:

This visualization might make it clearer:

Waveform visualization showing how each sprite occupies a chunk of time, and is labeled by its start time and duration

We can pass our SpriteMap as one of our ComposableOptions:

const { play } = useSound('/path/to/sprite.mp3', {
  sprite: {
    laser: [0, 300],
    explosion: [1000, 300],
    meow: [2000, 75],
  },
})

To play a specific sprite, we'll pass its id when calling the play function:

<button
  @click="play({id: 'laser'})"
>

Escape hatches

Howler is a very powerful library, and we've only exposed a tiny slice of what it can do in useSound. We expose two escape hatches to give you more control.

First, any unrecognized option you pass to ComposableOptions will be delegated to Howl. You can see the full list of options in the Howler docs. Here's an example of how we can use onend to fire a function when our sound stops playing:

const { play } = useSound('/thing.mp3', {
  onend: () => {
    console.info('Sound ended!')
  },
})

If you need more control, you should be able to use the sound object directly, which is an instance of Howler.

For example: Howler exposes a fade method, which lets you fade a sound in or out. You can call this method directly on the sound object:

<template>
    <button
      @click={sound.fade(0, 1, 1000)}
    >
      Click to win
    </button>
</template>

<script>
import { useSound } from '@vueuse/sound'

export default {
    setup() {
        const { play, sound } = useSound('/win-theme.mp3')

        return {
            sound
        }
    }
}
</script>

Vite

If you are using Vite, you should add the following to your defineConfig options in vite.config.js:

optimizeDeps: {
  exclude: ['vue-demi']
}

Nuxt

If you use Nuxt 2, you must have @nuxt/bridge setup in your project.

Once you installed it, add @vueuse/sound/nuxt dependency to your project.

Add @vueuse/sound/nuxt to the modules section of your nuxt.config:

defineNuxtConfig({
  modules: ['@vueuse/sound/nuxt']
})

Configure your sounds πŸ₯:

defineNuxtConfig({
  sound: {
    sounds: {
      back: {
        src: "/back.wav",
        options: {
          volume: 0.25
        }
      }
    }
  }
})

You can also automatically scan an generate typings for your sounds in public/sounds directory by using scan feature:

defineNuxtConfig({
  sound: {
    sounds: {
      scan: true
    }
  }
})

Then move your sounds into public/sounds directory, and get autocompletion on useSound({url}).

Credits

All the credit behind this idea goes to Josh W. Comeau.

The documentation of this package has only been updated for Vue Composition API instead of React Hooks.

If you like this package, consider following me on GitHub and on Twitter.