2

I have a Voice URL I got it from Database and want to play it using react-native-sound,

So I make a reusable component that renders a play icon and duration "without animation right now"

UI

so after the sound play, I change play icon first then run setInterval to increase count every second +1

but it's too slow at first to change icon and the real duration in seconds: 11 but the timer stops in 9

So how can I make it faster...

Code

this.state = {
      playingNow: false,
      spinValue: new Animated.Value(0),
      timer: 0,
    };

//   get URL sound from DB then play it :)
  play = async () => {
    const {VoiceURL} = this.props;
    console.log('Play Func Object:', VoiceURL);
    const sound = new Sound(VoiceURL, '', error => {
      if (error) {
        console.log('failed to load the sound', error);
        return;
      } else {
        // Animated.timing(this.state.spinValue, {
        //   toValue: 1,
        //   duration: sound. _duration * 1100 || 1000,
        //   easing: Easing.linear,
        //   useNativeDriver: true,
        // }).start(() => {
        //   this.state.spinValue.setValue(0);
        // });
        this.interval = setInterval(
          () =>
            this.setState({
              playingNow: true,
              timer: this.state.timer + 1,
            }),
          1000,
        );
        sound.play(success => {
          if (success) {
            //   That's mean sound is finish
            console.log('success?', success);
            this.setState(
              {playingNow: false, timer: 0},
              () => sound.stop(),
              clearInterval(this.interval),
            );
            console.log('successfully finished playing');
          } else {
            console.log('playback failed due to audio decoding errors');
          }
        });
        console.log(
          'duration in seconds: ' +
            sound.getDuration() +
            'number of channels: ' +
            sound.getNumberOfChannels(),
        );
        sound.getCurrentTime(seconds => console.log('at: ' + seconds));
      }
      sound.setNumberOfLoops(0);
    });
    console.log('in_play??', sound);
    return sound;
  };

That's render a UI

  _renderPlayButton = onPress => {
    const {timer} = this.state;
    let icon = this.state.playingNow ? 'pause' : 'play-arrow';
    // const spin = this.state.spinValue.interpolate({
    //   inputRange: [0, 1],
    //   outputRange: [0, -120],
    // });
    return (
      <View
        style={{
          opacity: this.props.opacity,
          alignItems: 'center',
          flexDirection: 'row',
        }}>
        <TouchableOpacity
          style={{backgroundColor: '#1E558E', borderRadius: 100}}
          onPress={onPress}>
          <Icon name={icon} color="#fff" size={25} style={{padding: 2}} />
        </TouchableOpacity>
        <View
          style={{flexDirection: 'row', marginLeft: 5, alignItems: 'center'}}>
          {/* <Animated.View
            style={{
              backgroundColor: '#f00',
              borderRadius: 10,
              width: 15,
              height: 15,
              zIndex: 10,
              transform: [{translateX: spin}],
            }}
          /> */}
          <View
            style={{
              width: 120,
              backgroundColor: '#a7a7a7',
              height: 2,
            }}
          />
          <Text style={{color: '#777', marginBottom: 5, marginLeft: 10}}>
            00:
            {('0' + (timer || 0)).slice(-2)}
            {console.log('timer-state:', timer)}
          </Text>
        </View>
      </View>
    );
  };

JSX

      <View>
        {this._renderPlayButton(() => {
          this.play();
        })}
      </View>
Oliver D
  • 2,579
  • 6
  • 37
  • 80
  • 1
    Don't know if this is the cause of the problem or not, but you shouldn't directly use `this.state` in an object passed to `setState`. Use the [function form](https://medium.com/@baphemot/understanding-reactjs-setstate-a4640451865b) instead. – Robin Zigmond Jan 06 '20 at 19:43
  • @RobinZigmond U mean in `setState` timer? – Oliver D Jan 06 '20 at 20:01
  • Yes. Change it to `this.setState(oldState => ({playingNow: true, timer: oldState.timer + 1}))`. I can't say for sure that this will fix the issue (if I was sure I'd have made it an answer), but in my opinion it's certainly possible. The article I linked to should explain why this is necessary. – Robin Zigmond Jan 06 '20 at 20:04
  • @RobinZigmond sadly not solve the issue :\ – Oliver D Jan 06 '20 at 20:08
  • @RobinZigmond And when I use this way and log `timer` in `_renderPlayButton()` it's logged for 125 times, Although the sound just 3 seconds :O – Oliver D Jan 06 '20 at 20:13
  • Don't increment the seconds manually, use the value from `getCurrentTime`'s callback. – Emile Bergeron Jan 06 '20 at 20:59
  • @EmileBergeron It's got just 0 when `console.log('at: ' + seconds)` – Oliver D Jan 06 '20 at 21:56
  • You need to call `sound.getCurrentTime(seconds => console.log('at: ' + seconds));` inside the `setInterval` to get the updated value over time. – Emile Bergeron Jan 06 '20 at 21:58
  • @EmileBergeron But how can I refer this value on inside `_renderPlayButton()` – Oliver D Jan 06 '20 at 22:00
  • It was too long for a comment so I wrote an answer. – Emile Bergeron Jan 06 '20 at 22:43

1 Answers1

2

When dealing with realtime, never do the math yourself, always use the real data whenever it is available.

With Sound, it looks like the getCurrentTime. At first, it will show as 0 seconds, this is why you used an interval to begin with.

// Set it playing as soon as possible.
this.setState({ playingNow: true });

this.interval = setInterval(
  () =>
    sound.getCurrentTime(seconds => {
      this.setState({ timer: Math.floor(seconds) });
    }),
  500 // reduce the delay so it don't skip a second.
);

Note that I did not test the code in this answer. Also, it's important to do the correct cleanup on componentWillUnmount, like calling .release() on the sound object.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • What does `release()` do, and why calling it on `componentWillUnmount` – DevAS Jan 07 '20 at 00:15
  • 1
    @DevAS According to the documentation, it will release the memory of the audio. Though, I've never done React Native development, nor used this API, so I can't say for sure where it's best to call it. – Emile Bergeron Jan 07 '20 at 00:18
  • @EmileBergeron Thanks for the answer, but I got seconds in Decimal like this 2.072 it will not work well and when to use Math.floor(second) it's skipped some numbers – Oliver D Jan 07 '20 at 09:07
  • 1
    @OliverD I updated the answer so it's less likely to skip a second update. – Emile Bergeron Jan 07 '20 at 15:10
  • @EmileBergeron hi hope you are good. I have a quick question, is it ok to use setInterval and keep calling my aPI get request every 5s or will this cause performance issues? – kd12345 Jan 13 '21 at 05:29
  • @kd12345 that's a really broad question. On the React side of things, if it's a single component updating every 5 sec, unlikely to cause any performance issue. If a whole list as its own interval, updating every few seconds, then it may become noticeably underperforming from the user point of view. That's ignoring the possible load on the backend, the delay from the network, etc. – Emile Bergeron Jan 13 '21 at 05:53
  • Hi thank you for getting back to me, i have implemented chatting in my app and it is working but i am having a problem implementing socket to display the last message sent when the user is on the All chats screen and so i was thinking of using setInterval and calling my api every 5s to load all my chats and display the last message. But what i thought of is when i host my app on AWS it will cost alot since every 5s i will requesting a get request. – kd12345 Jan 13 '21 at 05:57
  • @kd12345 you're right that it may increase costs. But before any optimization, you should measure the bottlenecks (is it performance? costs? etc). That said, see: [Using `setInterval()` to do simplistic continuous polling](https://stackoverflow.com/q/8682622/1218980) for implementation tips. – Emile Bergeron Jan 13 '21 at 06:04
  • @kd12345 I meant estimating the cost from the average number of calls you're projecting. I can't really help much with socket, though you should search Stack Overflow for solutions and ask a question with a [mcve] if you become blocked. – Emile Bergeron Jan 13 '21 at 06:12