GithubHelp home page GithubHelp logo

joshwcomeau / redux-sounds Goto Github PK

View Code? Open in Web Editor NEW
129.0 5.0 17.0 10.1 MB

Middleware for playing audio / sound effects using Howler.js

License: MIT License

JavaScript 90.14% HTML 4.34% CSS 5.52%

redux-sounds's Introduction

Redux Sounds

build status npm version Coverage Status

Redux middleware that lets you easily trigger sound effects on actions. Makes it completely trivial to do so, by adding a meta property to any action:

export function danceMoves() {
  return {
    type: 'DANCE_MOVES',
    meta: {
      sound: {
        play: 'groovyMusic'
      }
    }
  };
}

Uses Howler.js under the hood, which uses Web Audio API when available, with a graceful fallback to HTML5 Audio.

Installation

Preferred: NPM

npm i -S redux-sounds

Also available: UMD

UMD builds are also available, for single-file usage or quick hacking in a JSbin. Simply add dist/redux-sounds.js or dist/redux-sounds.min.js to your file in a <script> tag. The middleware will be available under ReduxSounds.

Setup

soundsMiddleware works similarly to other Redux middleware, and can be pre-loaded with sound data.

Here's an example setup:

/* configure-store.js */

import { createStore, combineReducers, applyMiddleware } from 'redux';
import soundsMiddleware from 'redux-sounds';

import { gameReducer } from '../reducers/game-reducer';

// Our soundsData is an object. The keys are the names of our sounds.
const soundsData = {
  // If no additional configuration is necessary, we can just pass a string  as the path to our file.
  endTurn: 'https://s3.amazonaws.com/bucketName/end_turn.mp3',

  // Alternatively, we can pass a configuration object.
  // All valid howler.js options can be used here.
  winGame: {
    src: [
      'https://s3.amazonaws.com/bucketName/win_game.mp3',
      'https://s3.amazonaws.com/bucketName/win_game.wav'
    ],
    volume: 0.75
  },

  // Audio sprites are supported. They follow the Howler.js spec.
  // Each sprite has an array with two numbers:
  //   - the start time of the sound, in milliseconds
  //   - the duration of the sound, in milliseconds
  jumps: {
    src: ['https://s3.amazonaws.com/bucketName/jumps.mp3'],
    sprite: {
      lowJump: [0, 1000],
      longJump: [1000, 2500],
      antiGravityJump: [3500, 10000]
    }
  }
};

// Pre-load our middleware with our sounds data.
const loadedSoundsMiddleware = soundsMiddleware(soundsData);

// Use as you would any other middleware.
const store = createStore(gameReducer, applyMiddleware(loadedSoundsMiddleware));
// (Using the condensed createStore released in Redux v3.1.0)

Howler has much more advanced capabilities, including specifying callbacks to run when the sound has completed (or failed to complete), looping sounds, fading in/out, and much more. See their documentation for the complete list.

Usage

Once your store is created, dispatching actions that trigger sounds is simple.

Using the convention established in the rafScheduler Middleware example, a new meta property can be attached to actions. This meta property should have a sound key, and its value should be that of a registered sound.

Continuing from our example above, we have 5 possible sounds: endTurn, winGame, and 3 flavors of jumps.

The Howler methods other than play follow almost the same API as Howler:

Howler

sound.fade(1, 0, 1000, id);
sound.stop(id);

redux-sounds action

// fade
meta: {
  sound: {
    fade: ['endTurn', 1, 0, 1000];
  }
}

// stop
meta: {
  sound: {
    stop: 'endTurn';
  }
}

For sprites, separate the sound name from the sprite name with a period (.). A couple examples of the actions we can dispatch:

/* game-actions.js */

export function endTurn() {
  return {
    type: 'END_TURN',
    meta: {
      sound: {
        play: 'endTurn'
      }
    }
  };
}

export function lowJump() {
  return {
    type: 'LOW_JUMP',
    meta: {
      sound: {
        play: 'jumps.lowJump'
      }
    }
  };
}

export function lowJumpStop() {
  return {
    type: 'LOW_JUMP_STOP',
    meta: {
      sound: {
        stop: 'jumps.lowJump'
      }
    }
  };
}

export function lowJumpFade() {
  return {
    type: 'LOW_JUMP_STOP',
    meta: {
      sound: {
        fade: ['jumps.lowJump', 0, 1, 2]
      }
    }
  };
}

Note: It is worth noting that it is unlikely that you'll need to create new actions for your sound effects; You'll probably want to just add meta properties to pre-existing actions, so that they play a sound in addition to whatever else they do (change the reducer state, trigger other middleware, etc).
Also Note: When a sound is playing multiple times at once, Howler methods (stop, fade, pause, etc.) will apply to all playing instances

Adding more sounds via actions

/* add-sounds-actions.js */
const coinSoundsData = {
  heavyCoin: 'https://s3.amazonaws.com/bucketName/gold_coin.mp3',
  lightCoin: {
    src: 'https://s3.amazonaws.com/bucketName/gold_coin.mp3', // just lower volume
    volume: 0.75
  },
  randomCoins: {
    src: ['https://s3.amazonaws.com/bucketName/coin_collection.mp3'],
    sprite: {
      one: [0, 1000],
      two: [1000, 2500],
      three: [3500, 10000]
    }
  }
};

export function addCoinSounds() {
  return {
    type: 'ADD_COIN_SOUNDS',
    meta: {
      sound: {
        add: coinSoundsData
      }
    }
  };
}

Callbacks

The Howler callbacks onplay, onstop, and onend are currently supported

const winPopup = {
  type: 'SHOW_WIN_POPUP',
  payload: 'You won'
};

const soundsData = {
  randomCoins: {
    src: ['https://s3.amazonaws.com/bucketName/coin_collection.mp3'],
    sprite: {
      one: [0, 1000],
      two: [1000, 2500],
      three: [3500, 10000]
    },
    onend: (id, dispatch) => dispatch(winPopup)
  }
};

Playlist

It is preferable to have your playlist merged as one continuous sounds. If that's not possible, you can dispatch a "playlist" action like the example below.

/* playlist-action.js */
export function playlistSounds() {
  return {
    type: 'PLAYLIST_SOUNDS',
    meta: {
      sound: {
        list: [
          { play: 'jumps.lowJump' },
          { play: 'jumps.longJump' },
          { play: 'endTurn' }
        ]
      }
    }
  };
}

Troubleshooting

Unregistered sound

When you dispatch an action with a meta.sound property, redux-sounds looks for a registered sound under that name. If it cannot find one, you'll get a console warning:

The sound 'foo' was requested, but redux-sounds doesn't have anything registered under that name.

To understand why this is happening, let's examine the link between registering a sound when the store is created, and triggering a sound when an action is dispatched.

When you create your store, you pass in an object like so:

const soundsData = {
  foo: 'path/to/foo.mp3'
};

The keys in that object must correspond to the value specified when you dispatch your action:

dispatch({
  type: 'ACTION_NAME',
  meta: {
    sound: { play: 'foo' }
  }
});

Make sure these two values are the same!

Invalid sprite

When your meta.sound value has a period in it (eg. foo.bar), redux-sounds breaks it apart so that the first half is the sound name, and the second half is the sprite name.

If redux-sounds has a sound registered under 'foo', but that sound has no specified sprite for 'bar', you get a console warning that resembles:

The sound 'foo' was found, but it does not have a sprite specified for 'bar'.

It is possible that there is a typo, either in the meta.sound property or the sprite data passed in on initialization.

A missingSoundData error throws when I try loading the page.

Unlike other middleware, you cannot simply pass it to Redux as-is:

import soundsMiddleware from 'redux-sounds';

// DON'T do this:
const store = createStore(rootReducer, applyMiddleware(soundsMiddleware));

The reason for this is that before the store can be registered, you need to pass it data about which sounds it needs to handle.

You must first invoke soundsMiddleware with a soundsData object:

import soundsMiddleware from 'redux-sounds';
import { soundsData } from '../data/sounds';

// Important step:
const loadedSoundsMiddleware = soundsMiddleware(soundsData);

const store = createStore(rootReducer, applyMiddleware(loadedSoundsMiddleware));

Tests

To run: npm run test

Using Mocha for test-running, Chai Expect for assertions, and Istanbul for test coverage.

While test coverage is high, because the tests run in Node, and Node has no ability to play sounds, the actual output is not tested.

Because I've delegated these duties to Howler, though, I don't feel too bad about that. I check to make sure the right info is passed to Howler at the right time; Howler's tests can take it from there.

Planned functionality

Got ideas for must-have functionality? Create an issue and let's discuss =)

Contributions

Contributors welcome! Please discuss additional features with me before implementing them, and please supply tests along with any bug fixes.

License

MIT

redux-sounds's People

Contributors

dependabot[bot] avatar gekorm avatar joshwcomeau avatar larrybotha avatar ludgerey 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

redux-sounds's Issues

Compiled lib in NPM is not the latest version, sprites not working

First off, I want to say that I love this middleware and I think you did an excellent job with the implementation and making it super easy to drop into any redux application. It seems as though when you install the v1.1 package via NPM, the main file from your package.json (lib/index.js) is actually an older version of the package. If you look at the lib/index.js file in node_modules, on line 35 has the following:

sounds[soundName].play();

This is identical to the way you called play() in v1.0.1 and is different than the current footprint of this file in v1.1:

const [ soundName, spriteName ] = action.meta.sound.split('.');

howlerIntegration.play(soundName, spriteName);

Could you please update the way that you publish your npm package so that it builds on pre-publish so that I can use the sprites? Thank you!

Package doesn't have associated typings

On import, encounter the following:

Could not find a declaration file for module 'redux-sounds'. Try `npm install @types/redux-sounds` if it exists or add a new declaration (.d.ts) file containing `declare module 'redux-sounds'

Indicate package doesn't have associated typings.

Can't play non-sprite sounds

I cannot get this library to actually play sounds in the project I'm working on so I created a minimal reproduction repo just trying to get the library working at all.

The mp3 files are loaded (can see via network tab), but do not play when action fires.

There is a warning in the Chrome inspector:

The Web Audio autoplay policy will be re-enabled in Chrome 70 (October 2018). Please check that your website is compatible with it. https://goo.gl/7K7WLu

But that is the only hint I have.

Is there a way to choose a random sound?

I want to do something like this:

scan: { src: [ 'sound1.mp3', 'sound2.mp3', 'sound3.mp3' ] }

but this will not work because it will always just succeed on sound1 and not ever choose the other two. Is there any way to get it to choose one at random? Other than exporting the array and manually choosing a random action string/key every time. Thanks.

Add mute functionality

In our app really miss this option. I'm planning to help with this one and probably create a pull request soon, but I would like to discuss it first.

The way I see it is to have ActionCreators which are distributed together with redux-sounds. An example of it can be ActionCreators.mute(). Later on, we can extend it with something like ActionTypes.stop(id) and ActionCreators.setVolume(). An example of it can look like this:

import { ActionCreators } from 'redux-undo';

store.dispatch(ActionCreators.mute())

We can also make it possible to override action types to allow usage of custom action creators. Something like this:

const loadedSoundsMiddleware = soundsMiddleware(soundsData, {
  muteType: 'MyApp/MUTE'
  // ... etc
});

This approach is successfully used in redux-undo. What do you think?

Add new sounds after middleware created?

Currently the docs talk about needing to have all the sounds added to the middleware at the beginning, but I was wondering if there is/could be a way to add/configure new sounds 'on the fly' using actions/etc?

preload:true is required?

im config sourceData has properties preload :false and after dispatch action has meta call load function :{sound: {load: actionName}}. that's right?

Sounds relative path

Hello
I am trying to use my sounds from a relative path.

My rootReducer is in the root
My sounds are in ./sounds
My soundsData is imported to rootReducer from ./constants/soundsData and looks like this:

export const soundsData = {
randomSound: '../sounds/randomSound.mp3',
}

But this way with URL works:

export const soundsData = {
randomSound: 'www.randomsounds.com/randomSound.mp3
};

Is there a way to play sounds using a relative path?

How can I load more soundData after declare midleware loadingSoundData?

now, I must be loaded all soundFile when run every page in site, how can i load sound base on each module
ex: in Cart, im need sound (addCart(ting ting),removeCart(tick tick))
in Order, im need sound (commentOrder('bla bla') , changeQuantity('bla bla....'))
so i load all file once time and per every page on root reducer

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.