React-native-sound: Play several sounds at simultaneously

Created on 8 Feb 2018  路  15Comments  路  Source: zmxv/react-native-sound

Is there any way to play several sounds simultaneously(Starting exactly at the same time)?
Or mixing the sounds into one mp3 file? Have been trying with some timers but the results are not perfect yet.

Most helpful comment

The problem was caused by this commit https://github.com/zmxv/react-native-sound/commit/42f329544d8f4d129635d70d12038848c6c7a8e4

It assigns the sounds a key based on the hash of a file name instead of an incremental value. This means you essentially can't create multiple instances of a sound with the same filename.

I don't know why they thought it was a good idea to use a hash function there, but I have my own branch https://github.com/alex-paterson/react-native-sound that undoes this commit and it fixed a bug that this introduced into my app. I'll make a PR

All 15 comments

Did you pre-load the sounds before you .play() them?

// Load the sound file 'whoosh.mp3' from the app bundle
// See notes below about preloading sounds within initialization code below.
var whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => {
  if (error) {
    console.log('failed to load the sound', error);
    return;
  }
  // loaded successfully
  console.log('duration in seconds: ' + whoosh.getDuration() + 'number of channels: ' + whoosh.getNumberOfChannels());
});

what does your code look like for playing the sounds?

Sorry for the bad code.. but I tried something like this! It used to work.... where I would create multiple sound instances but this stopped working after I upgraded react-native-sound! Is this a bug a or a feature? :D @baumant @adrianTyrevall

import * as React from "react";
import { Animated, PixelRatio, View } from "react-native";
import Piwik from "react-native-piwik";
import Sound from "react-native-sound";
import styled from "styled-components/native";

import * as dimensionHelper from "../util/dimensionHelper";
import { randomBlerpColor } from "../util/helpers";

const Container = styled.TouchableHighlight`
  border-radius: 200;
  align-self: center;
  margin: ${20};
`;

const AnimatedContainer = Animated.createAnimatedComponent(Container);

const StyledActivityIndicator = styled.ActivityIndicator`
  position: absolute;
  align-self: center;
  margin-top: ${PixelRatio.getPixelSizeForLayoutSize(20)};
`;

const Image = styled.Image`
  width: 100%;
  height: 100%;
`;

interface State {
  borderWidth: Animated.Value;
  color: string;
  height: Animated.Value;
  isLoading: boolean;
  isPlaying: boolean;
  width: Animated.Value;
}

interface Props {
  id?: string;
  title?: string;
  sound: {
    url: string;
  };
  color: string;
}

export default class SpamButton extends React.Component<Props, State> {
  public state: State;
  public soundOne: Sound;
  public soundTwo: Sound;
  public soundThree: Sound;

  public mounted: boolean;
  public currentCount: number;
  public maxCount = 3;

  constructor(props: Props) {
    super(props);
    this.state = {
      borderWidth: new Animated.Value(0),
      color: randomBlerpColor(),
      height: new Animated.Value(
        dimensionHelper.isTablet()
          ? dimensionHelper.TAB_SPAM_BUTTON_SIZE
          : dimensionHelper.PHONE_SPAM_BUTTON_SIZE
      ),
      isLoading: false,
      isPlaying: false,
      width: new Animated.Value(
        dimensionHelper.isTablet()
          ? dimensionHelper.TAB_SPAM_BUTTON_SIZE
          : dimensionHelper.PHONE_SPAM_BUTTON_SIZE
      )
    };
    this.mounted = false;
  }

  public componentWillMount() {
    Sound.setCategory("Playback", true);
  }

  public borderAnimationStart() {
    Animated.spring(this.state.borderWidth, {
      bounciness: 20,
      toValue: 12
    }).start();
  }

  public borderAnimationStop() {
    Animated.spring(this.state.borderWidth, {
      bounciness: 20,
      toValue: 0
    }).start();
  }

  // to repeat animations do this https://stackoverflow.com/questions/31578069/repeat-animation-with-new-animated-api
  public jumpAnimation() {
    Animated.sequence([
      Animated.timing(this.state.width, {
        duration: 45,
        toValue: dimensionHelper.isTablet()
          ? dimensionHelper.TAB_SPAM_BUTTON_SIZE + 25
          : dimensionHelper.PHONE_SPAM_BUTTON_SIZE + 12
      }),
      Animated.timing(this.state.width, {
        duration: 75,
        toValue: dimensionHelper.isTablet()
          ? dimensionHelper.TAB_SPAM_BUTTON_SIZE
          : dimensionHelper.PHONE_SPAM_BUTTON_SIZE
      })
    ]).start();
    Animated.sequence([
      Animated.timing(this.state.height, {
        duration: 45,
        toValue: dimensionHelper.isTablet()
          ? dimensionHelper.TAB_SPAM_BUTTON_SIZE + 25
          : dimensionHelper.PHONE_SPAM_BUTTON_SIZE + 12
      }),
      Animated.timing(this.state.height, {
        duration: 75,
        toValue: dimensionHelper.isTablet()
          ? dimensionHelper.TAB_SPAM_BUTTON_SIZE
          : dimensionHelper.PHONE_SPAM_BUTTON_SIZE
      })
    ]).start();
  }

  public incrementCount = () => {
    return this.currentCount < this.maxCount - 1
      ? (this.currentCount = this.currentCount + 1)
      : (this.currentCount = 0);
  };

  public finishedPlaying = (success) => {
    if (this.mounted) {
      this.setState({ isPlaying: false });
      // if (success) {
      //   console.log('successfully finished playing');
      // } else {
      //   console.log('playback failed due to audio decoding errors');
      //   // reset the player to its uninitialized state (android only)
      //   // this is the only option to recover after an error occured and use the player again
      //   whoosh.reset();
      // }
    }
  };

  public finishedLoading = (soundIndex: number) => {
    if (this.mounted) {
      // Error object does get passed need to make it work with flow though
      // Stops load and plays the sound that was loaded from the new player
      this.setState({ isPlaying: true, isLoading: false });
      switch (soundIndex) {
        case 0:
          this.soundOne.play(this.finishedPlaying);
          break;
        case 1:
          this.soundTwo.play(this.finishedPlaying);
          break;
        case 2:
          this.soundThree.play(this.finishedPlaying);
          break;
      }
    }
  };

  public handlePress = () => {
    this.jumpAnimation();
    const currentIndex = this.currentCount;
    switch (currentIndex) {
      case 0:
        if (this.soundOne) {
          this.soundOne.stop(() => {
            // Note: If you want to play a sound after stopping and rewinding it,
            // it is important to call play() in a callback.
            this.soundOne.play(this.finishedPlaying);
          });
          this.setState({ isPlaying: true });
        } else {
          this.borderAnimationStart();
          this.setState({ isLoading: true });
          this.soundOne = new Sound(this.props.sound.url, "", () => {
            this.finishedLoading(currentIndex);
          });
        }
        break;
      case 1:
        if (this.soundTwo) {
          this.soundTwo.stop(() => {
            // Note: If you want to play a sound after stopping and rewinding it,
            // it is important to call play() in a callback.
            this.setState({ isPlaying: true });
            this.soundTwo.play(this.finishedPlaying);
          });
        } else {
          this.borderAnimationStart();
          this.setState({ isLoading: true });
          this.soundTwo = new Sound(this.props.sound.url, "", () => {
            this.finishedLoading(currentIndex);
          });
        }
        break;
      case 2:
        if (this.soundThree) {
          this.soundThree.stop(() => {
            // Note: If you want to play a sound after stopping and rewinding it,
            // it is important to call play() in a callback.
            this.setState({ isPlaying: true });
            this.soundThree.play(this.finishedPlaying);
          });
        } else {
          this.borderAnimationStart();
          this.setState({ isLoading: true });
          this.soundThree = new Sound(this.props.sound.url, "", () => {
            this.finishedLoading(currentIndex);
          });
        }
        break;
      default:
        if (this.soundOne) {
          this.soundOne.stop(() => {
            // Note: If you want to play a sound after stopping and rewinding it,
            // it is important to call play() in a callback.
            this.setState({ isPlaying: true });
            this.soundOne.play(this.finishedPlaying);
          });
        } else {
          this.borderAnimationStart();
          this.setState({ isLoading: true });
          this.soundOne = new Sound(this.props.sound.url, "", () => {
            this.finishedLoading(currentIndex);
          });
        }
        break;
    }
    this.incrementCount();
    Piwik.trackEvent("Interaction", "Spam Button Play", this.props.title || "");
  };

  public componentDidMount() {
    this.mounted = true;
    this.currentCount = 0;
  }

  public componentWillUnmount() {
    this.mounted = false;
    if (this.soundOne) {
      this.soundOne.release();
      this.soundOne = null;
    }

    if (this.soundTwo) {
      this.soundTwo.release();
      this.soundTwo = null;
    }

    if (this.soundThree) {
      this.soundThree.release();
      this.soundThree = null;
    }
  }

  public render() {
    return (
      <AnimatedContainer
        onPress={this.handlePress}
        style={{
          borderColor: this.state.color,
          borderWidth: this.state.borderWidth,
          height: this.state.height,
          width: this.state.width
        }}
      >
        <View>
          <Image source={require("../img/Blerp-Spam-Icon.png")} />
          {this.state.isLoading && (
            <StyledActivityIndicator color={this.props.color} size="large" />
          )}
        </View>
      </AnimatedContainer>
    );
  }
}

so the code above works for multiple playback on 0.10.3 i haven't had time to look into it but i believe this stopped working after that!

The problem was caused by this commit https://github.com/zmxv/react-native-sound/commit/42f329544d8f4d129635d70d12038848c6c7a8e4

It assigns the sounds a key based on the hash of a file name instead of an incremental value. This means you essentially can't create multiple instances of a sound with the same filename.

I don't know why they thought it was a good idea to use a hash function there, but I have my own branch https://github.com/alex-paterson/react-native-sound that undoes this commit and it fixed a bug that this introduced into my app. I'll make a PR

oh great explanation! Thanks @alex-paterson !!

any light on getting this merged?

@aaronkchsu , @alex-paterson , @baumant , @mojombo
hi
how to loop audio without gap between replay...

my code

import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View,
Image,
TouchableOpacity
} from 'react-native';

import Sound from "react-native-sound";

let whoosh1 =null;
let whoosh2 =null;
export default class SoundList extends Component{
constructor(props) {
super(props);

    this.state = {
        language: '',
    };
}

play(){

    whoosh1= new Sound('rain.mp3', Sound.MAIN_BUNDLE);
    whoosh2= new Sound('rain1.mp3', Sound.MAIN_BUNDLE, (error) => {
        this.playM2();
    });
}

playM1()
{
    whoosh1.play();

    let timeOutTimeInMiliSec = (whoosh1.getDuration() -1) * 1000;
    console.log(timeOutTimeInMiliSec)
    setTimeout(() => {
        console.log('start playM2')
        whoosh1.stop();
        this.playM2()
    }, timeOutTimeInMiliSec);
}
playM2()
{
    whoosh2.play();

    let timeOutTimeInMiliSec = (whoosh2.getDuration() -1) * 1000;
    console.log(timeOutTimeInMiliSec)
    setTimeout(() => {
        console.log('start playM1')
        whoosh2.stop();
        this.playM1()
    }, timeOutTimeInMiliSec);
}

render() {
    this.play()
    return (
        <View >
                <Text>Hiiiii</Text>
        </View>
    );
}

}

@sam9010 you seem to be doing it right by using multiple sounds however you are going to need https://github.com/zmxv/react-native-sound/pull/439 in order to fully utilize the two sound instances

@alex-paterson
I have tried to copy the contents of your sound.js instead of the contents of the current sound.js but it seems it doesn't solve the issue for me. is there another file I should copy?

@aaronkchsu , @alex-paterson , @baumant , @mojombo
hi
how to loop audio without gap between replay...

my code

import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View,
Image,
TouchableOpacity
} from 'react-native';

import Sound from "react-native-sound";

let whoosh1 =null;
let whoosh2 =null;
export default class SoundList extends Component{
constructor(props) {
super(props);

    this.state = {
        language: '',
    };
}

play(){

    whoosh1= new Sound('rain.mp3', Sound.MAIN_BUNDLE);
    whoosh2= new Sound('rain1.mp3', Sound.MAIN_BUNDLE, (error) => {
        this.playM2();
    });
}

playM1()
{
    whoosh1.play();

    let timeOutTimeInMiliSec = (whoosh1.getDuration() -1) * 1000;
    console.log(timeOutTimeInMiliSec)
    setTimeout(() => {
        console.log('start playM2')
        whoosh1.stop();
        this.playM2()
    }, timeOutTimeInMiliSec);
}
playM2()
{
    whoosh2.play();

    let timeOutTimeInMiliSec = (whoosh2.getDuration() -1) * 1000;
    console.log(timeOutTimeInMiliSec)
    setTimeout(() => {
        console.log('start playM1')
        whoosh2.stop();
        this.playM1()
    }, timeOutTimeInMiliSec);
}

render() {
    this.play()
    return (
        <View >
                <Text>Hiiiii</Text>
        </View>
    );
}

}

Can you make it dynamic ? let say I have 3-4 mp3 then ?

Really looking forward for this PR. I am using an sound sprite for my app and cannot play multiple instances at the same time.

Nevertheless thanks for all the work done in this library ;-)

Hi guys... I found a workaround...
You can trick that hash function so you can load multiple instances of the same audio by adding a string like a query param to the file name... 馃槂

let soundCounter = 0;

function loadSound(name){
    soundCounter++;
    return new Sound(name+'?'+soundCounter, Sound.MAIN_BUNDLE, (error) => {
      if (error) {
        console.log('failed to load the sound', sounds[name], error);
        return;
      }
      console.log('Sound loaded!',name);
    });
}

// Then
let sound1 = loadSound('whoosh.mp3');
let sound2 = loadSound('whoosh.mp3');
let sound3 = loadSound('whoosh.mp3');
let sound4 = loadSound('whoosh.mp3');

And you can play then all together without issues 馃憤

For anyone else following this: this issue was fixed in #439, which has been merged but not yet released to npm.

Please Help

I want to play multiple audio files in my React Native application. Currently, it is one audio at a time and i also want single audio to be played at a time. What I want is if one audio is playing and suddenly the user tapped on the second audio button the first one will get paused and second should play. And when the user tapped the first again the paused audio will start again from where it is paused.

@adrian621 @sangeeth9819
Please refer this stackoverflow question. It works for me!
https://stackoverflow.com/questions/61708473/how-to-run-multiple-tracks-at-a-time-simultaneously-in-react-native

Was this page helpful?
0 / 5 - 0 ratings

Related issues

VladimirJarabica picture VladimirJarabica  路  3Comments

jacargentina picture jacargentina  路  4Comments

gastonmorixe picture gastonmorixe  路  4Comments

zhenizhui picture zhenizhui  路  3Comments

watadarkstar picture watadarkstar  路  3Comments