title: How to capture microphone audio on React Native and stream it to IceCast endpoint?
body: <p>I'm working on a React Native mobile radio application (testing on Android API 28 as target and 26 as check, both emulator and physical device), and the idea is to enable the radio channel host to talk into the phone microphone and have that audio overlayed on the radio music.</p>
  
  <p>I'm using IceCast 2 and Liquidsoap to (successfully) stream music to the listeners, and mix in a microphone stream (liquidsoap input.harbor, at URL:PORT/ICECAST_ENDPOINT) which I am currently able to stream microphone to using <a href="https://danielnoethen.de/butt/" rel="nofollow noreferrer">butt</a>.</p>
  
  <p>Now my question is how to capture mic input from the mobile device and then stream it to the same URL endpoint from the React Native app?</p>
  
  <p>I've tried using <a href="https://github.com/chadsmith/react-native-microphone-stream" rel="nofollow noreferrer">react-native-microphone-stream</a>, but the listener lambda is never called:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>import React, { useState, useEffect } from 'react';
  import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
  import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
  import MicStream from 'react-native-microphone-stream';
  
  /**
   * STYLING
   */
  const styles = StyleSheet.create({
    container: {
      flex: 0.1,
      height: 5,
      width: '100%',
      flexDirection: 'row',
      justifyContent: 'space-around',
      alignItems: 'center',
      paddingVertical: 4,
      paddingHorizontal: 16,
      backgroundColor: 'black',
      paddingHorizontal: 32,
    },
    autoFadeButton: {
      color: 'white',
      textAlignVertical: 'center',
      alignSelf: 'center',
    },
    microphoneButton: {
      flexDirection: 'row',
      color: '#B52C55',
    },
  });
  
  /**
   * Navigational function for choosing the channel and searching for new channels
   */
  export default function ActionBar() {
    const [auto, setAuto] = useState(false);
    const [recording, setRecording] = useState(false);
    const listener = MicStream.addListener((data) =&gt; console.log('data', data)); // This never occurs
  
    MicStream.init({
      bufferSize: 4096,
      sampleRate: 44100,
      bitsPerChannel: 16,
      channelsPerFrame: 1,
    });
  
    function toggleRecord() {
      if (recording) {
        console.log('starting mic');
        MicStream.start();
      } else {
        console.log('stopping mic');
        MicStream.stop();
      }
    }
  
    useEffect(() =&gt; {
      return () =&gt; listener.remove();
    }, []);
  
    useEffect(() =&gt; {
      toggleRecord();
    }, [recording]);
  
    return (
      &lt;View style={styles.container}&gt;
        &lt;TouchableOpacity onPress={() =&gt; setAuto(!auto)}&gt;
          &lt;Text style={styles.autoFadeButton}&gt;Auto.&lt;/Text&gt;
        &lt;/TouchableOpacity&gt;
        &lt;TouchableOpacity
          style={styles.microphoneButtonBroadcasting}
          onPress={() =&gt; setRecording(!recording)}
        &gt;
          &lt;Icon
            name="microphone-outline"
            size={40}
            color={recording ? '#B52C55' : 'grey'}
          /&gt;
        &lt;/TouchableOpacity&gt;
        &lt;TouchableOpacity&gt;
          &lt;Icon
            style={[{ transform: [{ scaleX: 2 }, { scaleY: 0.8 }] }]}
            name="chevron-down"
            size={40}
            color="white"
          /&gt;
        &lt;/TouchableOpacity&gt;
      &lt;/View&gt;
    );
  }</code></pre>
  </div>
  </div>
  </p>