How can I play a sound after recording in React Native Expo?

5.4k Views Asked by At

I'm building a voice recording application with Expo. This is my function to start voice recording, it works properly.

  const [recording, setRecording] = React.useState();

  async function startRecording() {
    try {
      console.log('Requesting permissions..');
      await Audio.requestPermissionsAsync();
      await Audio.setAudioModeAsync({
        allowsRecordingIOS: true,
        playsInSilentModeIOS: true,
      }); 
      console.log('Starting recording..');
      const recording = new Audio.Recording();
      await recording.prepareToRecordAsync(Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY);
      await recording.startAsync(); 
      setRecording(recording);
      console.log('Recording started');
    } catch (err) {
      console.error('Failed to start recording', err);
    }
  }

And this is my 'stop' function, Recording stopped and stored...

  async function stopRecording() {
    console.log('Stopping recording..');
    setRecording(undefined);
    await recording.stopAndUnloadAsync();
    const uri = recording.getURI(); 
    console.log('Recording stopped and stored at', uri);
  }

Now I want to play this saved sound with a play button. How can I reach this saved sound?

  return (
    <View style={styles.container}>
      <Button
        title={recording ? 'Stop Recording' : 'Start Recording'}
        onPress={recording ? stopRecording : startRecording}
      />
    </View>
  );

I want to pass this stored uri as a file location. Is it possible in this way?

async function playSound() {
    console.log('Loading Sound');
    const { sound } = await Audio.Sound.createAsync(
       require(RecordedURI)
    );
    setSound(sound);
    console.log('Playing Sound');
    await sound.playAsync();
}

This is the snack link: https://snack.expo.io/ZN9MBtpLd

2

There are 2 best solutions below

8
On BEST ANSWER

First of all, you are assigning your state to undefined after you get the recording.

I would suggest you to do something like this

Create two refs for Recording and Playing audio.

const AudioRecorder = useRef(new Audio.Recording());
const AudioPlayer = useRef(new Audio.Sound());

And some states for recordingStatus, permission etc.

const [RecordedURI, SetRecordedURI] = useState<string>("");
const [AudioPermission, SetAudioPermission] = useState<boolean>(false);
const [IsRecording, SetIsRecording] = useState<boolean>(false);
const [IsPLaying, SetIsPLaying] = useState<boolean>(false);

Snack for the recording implementation is here

Here's a GitHub Repo which has the implementation.

Screenshot of Result

I've added below the Rest of the implementation

import React, { useState, useRef, useEffect } from "react";
import { View, StyleSheet, Button, Text } from "react-native";
import { Audio } from "expo-av";

export default function App() {
  // Refs for the audio
  const AudioRecorder = useRef(new Audio.Recording());
  const AudioPlayer = useRef(new Audio.Sound());

  // States for UI
  const [RecordedURI, SetRecordedURI] = useState<string>("");
  const [AudioPermission, SetAudioPermission] = useState<boolean>(false);
  const [IsRecording, SetIsRecording] = useState<boolean>(false);
  const [IsPLaying, SetIsPLaying] = useState<boolean>(false);

  // Initial Load to get the audio permission
  useEffect(() => {
    GetPermission();
  }, []);

  // Function to get the audio permission
  const GetPermission = async () => {
    const getAudioPerm = await Audio.requestPermissionsAsync();
    SetAudioPermission(getAudioPerm.granted);
  };

  // Function to start recording
  const StartRecording = async () => {
    try {
      // Check if user has given the permission to record
      if (AudioPermission === true) {
        try {
          // Prepare the Audio Recorder
          await AudioRecorder.current.prepareToRecordAsync(
            Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY
          );

          // Start recording
          await AudioRecorder.current.startAsync();
          SetIsRecording(true);
        } catch (error) {
          console.log(error);
        }
      } else {
        // If user has not given the permission to record, then ask for permission
        GetPermission();
      }
    } catch (error) {}
  };

  // Function to stop recording
  const StopRecording = async () => {
    try {
      // Stop recording
      await AudioRecorder.current.stopAndUnloadAsync();

      // Get the recorded URI here
      const result = AudioRecorder.current.getURI();
      if (result) SetRecordedURI(result);

      // Reset the Audio Recorder
      AudioRecorder.current = new Audio.Recording();
      SetIsRecording(false);
    } catch (error) {}
  };

  // Function to play the recorded audio
  const PlayRecordedAudio = async () => {
    try {
      // Load the Recorded URI
      await AudioPlayer.current.loadAsync({ uri: RecordedURI }, {}, true);

      // Get Player Status
      const playerStatus = await AudioPlayer.current.getStatusAsync();

      // Play if song is loaded successfully
      if (playerStatus.isLoaded) {
        if (playerStatus.isPlaying === false) {
          AudioPlayer.current.playAsync();
          SetIsPLaying(true);
        }
      }
    } catch (error) {}
  };

  // Function to stop the playing audio
  const StopPlaying = async () => {
    try {
      //Get Player Status
      const playerStatus = await AudioPlayer.current.getStatusAsync();

      // If song is playing then stop it
      if (playerStatus.isLoaded === true)
        await AudioPlayer.current.unloadAsync();

      SetIsPLaying(false);
    } catch (error) {}
  };

  return (
    <View style={styles.container}>
      <Button
        title={IsRecording ? "Stop Recording" : "Start Recording"}
        color={IsRecording ? "red" : "green"}
        onPress={IsRecording ? StopRecording : StartRecording}
      />
      <Button
        title={IsPLaying ? "Stop Sound" : "Play Sound"}
        color={IsPLaying ? "red" : "orange"}
        onPress={IsPLaying ? StopPlaying : PlayRecordedAudio}
      />
      <Text>{RecordedURI}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    backgroundColor: "#ecf0f1",
    padding: 8,
  },
});
0
On

It an interesting question and also I got same problem. I was not able to play from recorded URI.

Moreover, I have been through from official document, however they only provided limited example.

But I come up with interesting solution, which is working for me efficiently. Audio.Sound.createAsync() Is also support URI support.

You simply need to pass {uri: recording. getURI() || URIFROMFileSystem} and It will work perfectly.

Example in my case

  const { sound } = await Audio.Sound.createAsync({
  uri: "file:///Users/xyz/Library/Developer/CoreSimulator/Devices/1BBRFGFCBC414-6685-4818-B625-01038771B105/data/Containers/Data/Application/18E8D28E-EA03-4733-A0CF-F3E21A23427D/Library/Caches/ExponentExperienceData/%2540anonymous%252Fchat-app-c88a6b2e-ad36-45b8-9e5e-1fb6001826eb/AV/recording-87738E73-A38E-49E8-89D1-9689DC5F316B.caf",
});
setSound(sound);

console.log("Playing Sound");
await sound.playAsync();