Search code examples
javascriptreactjsreact-nativeexpo-av

Playing a single file onPress react native and expo av


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


Solution

  • 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.