DEV Community

Lankinen
Lankinen

Posted on

Expo Audio - Upload recording to Firebase Storage and download it later

When I started to search this online people said that it's impossible to do it with Firebase Storage and there wasn't that many good tutorials explaining how to do it with other types of servers. That is why I'm so proud of solving this problem. Hopefully people who face the same problem find this because this at least worked for me June 2020.

const recordingSettings = {
  android: {
    extension: ".m4a",
    outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4,
    audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC,
    sampleRate: 44100,
    numberOfChannels: 2,
    bitRate: 128000,
  },
  ios: {
    extension: ".m4a",
    outputFormat: Audio.RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC,
    audioQuality: Audio.RECORDING_OPTION_IOS_AUDIO_QUALITY_MIN,
    sampleRate: 44100,
    numberOfChannels: 2,
    bitRate: 128000,
    linearPCMBitDepth: 16,
    linearPCMIsBigEndian: false,
    linearPCMIsFloat: false,
  },
};

const db = firebase.database();

const [isRecording, setIsRecording] = useState(false);
const [recording, setRecording] = useState(null);
const [sound, setSound] = useState(null);
const [isPlaying, setIsPlaying] = useState(false);
const startRecording = async () => {
    // stop playback
    if (sound !== null) {
      await sound.unloadAsync();
      sound.setOnPlaybackStatusUpdate(null);
      setSound(null);
    }

    await Audio.setAudioModeAsync({
      allowsRecordingIOS: true,
      interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
      playsInSilentModeIOS: true,
      shouldDuckAndroid: true,
      interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
      playThroughEarpieceAndroid: false,
      staysActiveInBackground: true,
    });
    const _recording = new Audio.Recording();
    try {
      await _recording.prepareToRecordAsync(recordingSettings);
      setRecording(_recording);
      await _recording.startAsync();
      console.log("recording");
      setIsRecording(true);
    } catch (error) {
      console.log("error while recording:", error);
    }
  };
const stopRecording = async () => {
    try {
      await recording.stopAndUnloadAsync();
    } catch (error) {
      // Do nothing -- we are already unloaded.
    }
    const info = await FileSystem.getInfoAsync(recording.getURI());
    console.log(`FILE INFO: ${JSON.stringify(info)}`);
    await Audio.setAudioModeAsync({
      allowsRecordingIOS: false,
      interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
      playsInSilentModeIOS: true,
      playsInSilentLockedModeIOS: true,
      shouldDuckAndroid: true,
      interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
      playThroughEarpieceAndroid: false,
      staysActiveInBackground: true,
    });
    const { sound: _sound, status } = await recording.createNewLoadedSoundAsync(
      {
        isLooping: true,
        isMuted: false,
        volume: 1.0,
        rate: 1.0,
        shouldCorrectPitch: true,
      }
    );
    setSound(_sound);
    setIsRecording(false);
  };
const uploadAudio = async () => {
    const uri = recording.getURI();
    try {
      const blob = await new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.onload = () => {
          try {
            resolve(xhr.response);
          } catch (error) {
            console.log("error:", error);
          }
        };
        xhr.onerror = (e) => {
          console.log(e);
          reject(new TypeError("Network request failed"));
        };
        xhr.responseType = "blob";
        xhr.open("GET", uri, true);
        xhr.send(null);
      });
      if (blob != null) {
        const uriParts = uri.split(".");
        const fileType = uriParts[uriParts.length - 1];
        firebase
          .storage()
          .ref()
          .child(`nameOfTheFile.${fileType}`)
          .put(blob, {
            contentType: `audio/${fileType}`,
          })
          .then(() => {
            console.log("Sent!");
          })
          .catch((e) => console.log("error:", e));
      } else {
        console.log("erroor with blob");
      }
    } catch (error) {
      console.log("error:", error);
    }
  };
const downloadAudio = async () => {
    const uri = await firebase
      .storage()
      .ref("nameOfTheFile.filetype")
      .getDownloadURL();

    console.log("uri:", uri);

    // The rest of this plays the audio
    const soundObject = new Audio.Sound();
    try {
      await soundObject.loadAsync({ uri });
      await soundObject.playAsync();
    } catch (error) {
      console.log("error:", error);
    }
  };

Top comments (7)

Collapse
 
ahmadalanazi profile image
Ahmad Alanazi

For those who wants to Upload to firestore v9

here is the updated code.

*These are the Firebase Storage rules setup:
*

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Above rules are to be applied through your Firebase Console.

Imports

import { db, auth, storage } from '../../backend/firebase';

import {
  getFirestore,
  collection,
  getDocs,
  getDoc,
  addDoc,
  deleteDoc,
  doc,
  onSnapshot,
  query,
  where,
  orderBy,
  serverTimestamp,
  updateDoc,
} from 'firebase/firestore';

import {
  createUserWithEmailAndPassword,
  signOut,
  signInAnonymously,
  signInWithEmailAndPassword,
  onAuthStateChanged,
} from 'firebase/auth';

import { getStorage, ref, uploadBytes, getDownloadURL, uploadBytesResumable } from 'firebase/storage';
Enter fullscreen mode Exit fullscreen mode

Then just delete whatever is not highlighted from the imports. _(Sorry I could not clean the imports up for you, I am kind on a rush.) _

The backend exporting functions for firebase (Optional)

// 🔥 init firebase app 
const firefirebase = initializeApp(clientCredentials);
function initFirebase() {
  firefirebase
    ? firefirebase && console.log('Firebase initalized')
    : console.log('Firebase NOT initalized');
}


// 📦 Firestore
// init Store & function to export.
const db = getFirestore(firefirebase);
function initFirestore() {
  db
    ? db && console.log('Firestore DB is: Connected')
    : console.log('Firestore DB is: NOT connected!');
}

// đź«‚ Auth ( Authentication )
const auth = initializeAuth(firefirebase, {
  persistence: getReactNativePersistence(AsyncStorage),
});

// 🗳️ Create a Storage reference
const storage = getStorage();


export {
  firefirebase,
  db,
  auth,
  user,
  storage};
Enter fullscreen mode Exit fullscreen mode

**Now the code to use ( expo-av ) is required !
**just google it easy to install the dependency.

import { Audio } from 'expo-av';
Enter fullscreen mode Exit fullscreen mode
  const [errorMessage, setErrorMessage] = useState('');
  const [recording, setRecording] = useState();
  const [recordings, setRecordings] = useState([]);
  const [recordingFile, setRecordingFile] = useState();
Enter fullscreen mode Exit fullscreen mode
  async function startRecording() {
    try {
      const permissions = await Audio.requestPermissionsAsync();

      if (permissions.status === 'granted') {
        await Audio.setAudioModeAsync({
          allowsRecordingIOS: true,
          playsInSilentModeIOS: true,
        });

        const { recording } = await Audio.Recording.createAsync(
          Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY
        );
        setRecording(recording);
      } else {
        setErrorMessage('No Permission!');
      }
    } catch (err) {
      console.error('Failed to start recording', err);
    }
  }

  async function stopRecording() {

    setRecording(undefined);
    await recording.stopAndUnloadAsync();

    let updatedRecordings = [recordings];
    const { sound, status } = await recording.createNewLoadedSoundAsync();
    updatedRecordings.push({
      sound: sound,
      duration: toDuration(status.durationMillis),
      file: recording.getURI(),
    });
    setRecordings(updatedRecordings);
    setRecordingFile(updatedRecordings[1].file);
  }

Enter fullscreen mode Exit fullscreen mode

*Finally upload to firebase Storage and Update Firestore !
*

// Assuming Firebase is initiated & Authnticationg is initiated. 
// Authniticating is not 100% neccessary, but recommended. 
// To add each file uploads to the user UID
// Also to pass the rules or firestorage. 
const saveSoundAndUpdateDoc = async (writing, recordings) => {
  const user = auth.currentUser;
  const path = `[folderNameHere if you like]/${user.uid}/[specify file name here or add a unique random generator]`;
  const blob = await new Promise((resolve, reject) => {
    const fetchXHR = new XMLHttpRequest();
    fetchXHR.onload = function () {
      resolve(fetchXHR.response);
    };
    fetchXHR.onerror = function (e) {
      reject(new TypeError('Network request failed'));
    };
    fetchXHR.responseType = 'blob';
    fetchXHR.open('GET', recordings, true);
    fetchXHR.send(null);
  }).catch((err) => console.log(err));

  const recordRef = ref(storage, path);

  await uploadBytes(recordRef, blob)
    .then(async (snapshot) => {
      const downloadURL = await getDownloadURL(recordRef).then((recordURL) => {
        const addDocRef = collection(db, 'posts');
        addDoc(addDocRef, {
          creator: user.uid,
          recordURL,
          creation: serverTimestamp(),
        })
          .then(() => {})
          .then(() => resolve())
          .catch((err) => console.log(err));
      });
      blob.close();
    })
    .catch((err) => console.log(err));
};

export { saveSoundAndUpdateDoc };
Enter fullscreen mode Exit fullscreen mode

Sorry for the mess, but I am a noob and I hope others can take me step by step like that.

Collapse
 
charlesgoode profile image
charles-goode • Edited

Very helpful. I didn't know until now that the contentType and file extension were necessary for this to work. Image tags worked fine without them, but when I started uploading video, expo-av didn't know how to read the format.

Collapse
 
ahmadalanazi profile image
Ahmad Alanazi

I replied with a way to get the file to be applied to this code. Maybe this will help.

Collapse
 
ahmadalanazi profile image
Ahmad Alanazi

Your are amazing man ! I have been struggling with this for the last couple of days. I have no idea what I did not think of searching the main subject. I have been searching and digging into the details to fix one thing and break another. Thanks alot !

I will go ahead and read the code and apply it. I just got too excited and I had to thank you after reading the introduction !

Collapse
 
gtallen13 profile image
Thomas Allen

You're such a boss ! Worked for me. Thank you so much

Collapse
 
rex12659 profile image
Rex12659

thank you brother, its work, i little change the code to web version 9, but it still work, thank you again lol :'D

Collapse
 
jsathu profile image
Sathurshan

I got an error as "bytes cannot be null" please help