Playing a single file onPress react native and expo av

158 Views Asked by At

I have a json that looks like this

{
    q: 'Sitting crossed legged',
    a: 'Anza',
    url: 'assets/audio/Anza.mp3'
},
{
    q: 'Attention!',
    a: 'Kiotsuke!',
    url: 'assets/audio/Kiotzuke.mp3'
},
{
    q: 'Bow!',
    a: 'Rei!',
    url: 'assets/audio/Rei.mp3'
},

And my component using expo av looks like this

import { StyleSheet, ScrollView, Text, View, TouchableOpacity } from 'react-native'
import React, {useState, useEffect} from 'react'
import ScreenTitle from '../../components/ScreenTitle';
import yellowbelt from '../../components/vocab/yellowbelt';
import {Audio} from 'expo-av';
import * as FileSystem from 'expo-file-system';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';


const RokkyuRoute = () => {  
  const [sound, setSound] = useState();

  const playSound = async (audioPath) => {
    console.clear();
    console.log('Playing Sound: ', FileSystem.documentDirectory + audioPath);
    const { sound } = await Audio.Sound.createAsync(
      { uri: FileSystem.documentDirectory + audioPath },
      { shouldPlay: true }
    );
    setSound(sound);
  };

  console.log('Playing Sound: ', sound);

  useEffect(() => {
    return sound
      ? () => {
          sound.unloadAsync();
        }
      : undefined;
  }, [sound]);

  return(
    <View style={{flex:1, height:"100%"}}>
      <View style={styles.yellowscreentitle}>
        <ScreenTitle title="Yellow Belt - Rokkyu" style={styles.darkh1}/>
      </View>
      <View style={styles.routecontainer}>
        <ScrollView>
          <View>
            {yellowbelt.map((item, index) => {
              return (
                <View key={index} style={styles.item}>
                  <View style={styles.question}>
                    <Text style={styles.questiontext}>{index+1}. {item.q} </Text>
                  </View>
                  <View style={styles.answer}>
                    <Text style={styles.answertext}>{item.a}</Text>

                    {item.url && 
                      <TouchableOpacity 
                        style={styles.button} 
                        onPress={() => playSound(item.url)}>
                        <Icon name="play" size={25} color={theme.colors.primary}/>
                      </TouchableOpacity>
                    }                 
                  </View>
                </View>
              )
            })}
          </View>
        </ScrollView>
      </View>
    </View>
  )
};

export default RokkyuRoute

const styles = StyleSheet.create({
  yellowscreentitle:{
    backgroundColor: "#ffe100",
    paddingTop: 15,
    paddingHorizontal: 25,
  },
  darkh1:{
    fontSize: 20,
    fontWeight: 'bold',
    color: theme.colors.dark,
  },
  routecontainer:{
    padding:25,
    paddingBottom: 250,
  },
  item:{
    width: "100%",
    marginBottom: 20,
  },
  question:{
    width: "100%",
    marginBottom: 10,
  },
  answer:{
    width: "100%",
    marginBottom: 10,
  },
  questiontext:{
    fontSize: 16,
    fontWeight: 'bold',
    color: theme.colors.dark,
  },
  answertext:{
    fontSize: 16,
    color: theme.colors.dark,
  },
  button:{
    backgroundColor: theme.colors.secondary,
    borderRadius: 50,
    width: 40,
    height: 40,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'absolute',
    right: 0,
    bottom: 0,
  }
})

I have tried everything I can think of including replicating this and with a single file rather than a mapped prop and I can't get it to play. I also added require in front of every instance of the file uri and nothing

EDIT - Following Ben Smith suggestions in the comments I installed FileSystem from expo-file-system and recreated the playSound() function. Console logging the audioPath that get's the path clicked on, and appending it to FileSystem I get this, But this is not the only sound. Each item has their own sound in the json so it must come from item.url

audioPath:  file:///Users/lotusms/Library/Developer/CoreSimulator/Devices/79B602DF-85B4-4ABE-8745-4A3276E07C58/data/Containers/Data/Application/D0605A63-2061-4064-9B10-5304381E715F/Documents/ExponentExperienceData/%2540anonymous%252Fjudopedia-app-d3ac072a-351a-46b3-b19f-00fec4e03363/assets/audio/Sensei.mp3

But still no sound

I'm out of ideas. Any helps is appreciated

2

There are 2 best solutions below

4
Manoj Bhardwaj On BEST ANSWER

If you have the local audio file path, you can use it directly in the AudioPlayer component. Here's how you can modify the code: Also, check the working snack example here

https://snack.expo.dev/qN9MBoJH5

Update the AudioPlayer component to accept a local file path:jsx

import React, { useState, useEffect } from 'react';
import { Text, TouchableOpacity ,SafeAreaView} from 'react-native';
import { Audio } from 'expo-av';

const App = () => {
  const [sound, setSound] = useState();

  const playSound = async () => {
    const { sound } = await Audio.Sound.createAsync(
      { uri: "https://download.samplelib.com/mp3/sample-3s.mp3" },
      { shouldPlay: true }
    );
    setSound(sound);
  };

  useEffect(() => {
    return sound
      ? () => {
          sound.unloadAsync();
        }
      : undefined;
  }, [sound]);

  return (
    <SafeAreaView style={{flex:1,justifyContent:'center',alignItems:'center'}}>
    <TouchableOpacity onPress={playSound}>
      <Text>Play Audio</Text>
    </TouchableOpacity>
    </SafeAreaView>
  );
};

export default App;

Make sure that the provided path is correct and points to the actual audio file on your local device. This example assumes that the file path is correct, and the audio file is accessible at that location.

4
mused On

This works for a single audio file if you specify the full file path.

import { StyleSheet, ScrollView, Text, View, TouchableOpacity } from 'react-native'
import React, { useState, useEffect } from 'react'
import ScreenTitle from '../../components/ScreenTitle'
import yellowbelt from '../../components/vocab/yellowbelt'
import { Audio } from 'expo-av'
import * as FileSystem from 'expo-file-system'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'

const RokkyuRoute = () => {
  return (
    <View style={{ flex: 1, height: '100%' }}>
      <View style={styles.yellowscreentitle}>
        <ScreenTitle title="Yellow Belt - Rokkyu" style={styles.darkh1} />
      </View>
      <View style={styles.routecontainer}>
        <ScrollView>
          <View>
            {yellowbelt.map((item, index: number) => {
              return <Play key={index} index={index} item={item} />
            })}
          </View>
        </ScrollView>
      </View>
    </View>
  )
}

Load each audio in its component. If you plan to store the files in the cloud you can try the commented code that should work.

const Play = ({ item, index }) => {
  const AudioPlayer = useRef(new Audio.Sound())

  useEffect(() => {
    const loadAudio = async () => {
      if (item.url) {
        //specify the full file path, dynamic file path not working
        await AudioPlayer.current.loadAsync(require('assets/audio/Rei.mp3'))
        //if you store the audio in the cloud try this
        //await AudioPlayer.current.loadAsync({uri: item.url})
      }
    }

    loadAudio()

    return () => {
      if (AudioPlayer.current) {
        // Stop and unload audio when the component is unmounted
        AudioPlayer?.current?.stopAsync()
        AudioPlayer?.current?.unloadAsync()
      }
    }
  }, [item])

  const playSound = async () => {
    const status = await AudioPlayer.current.getStatusAsync()

    if (status.isLoaded) {
      await AudioPlayer.current.playAsync()
    }
  }
  return (
    <View style={styles.item}>
      <View style={styles.question}>
        <Text style={styles.questiontext}>
          {index + 1}. {item.q}{' '}
        </Text>
      </View>
      <View style={styles.answer}>
        <Text style={styles.answertext}>{item.a}</Text>

        {item.url && (
          <TouchableOpacity style={styles.button} onPress={playSound}>
            <Icon name="play" size={25} color={theme.colors.primary} />
          </TouchableOpacity>
        )}
      </View>
    </View>
  )
}