tchvu3 / capacitor-voice-recorder

Capacitor plugin for voice recording
69 stars 57 forks source link

Help/Question: using this plugin to play the recorded audio with capacitor native-audio #3

Closed damngamerz closed 3 years ago

damngamerz commented 3 years ago

Im using the plugin to record and now want to use it with native-audio plugin for playing. What I found as a problem? This plugin give base64 string. I found a solution to use it with cordova https://ourcodeworld.com/articles/read/279/how-to-create-an-audio-file-from-a-mp3-base64-string-on-the-device-with-cordova In this given solution we convert it to blob object which in turn taken by cordova FileSystem plugin to write it to a file.

Although to use it with capacitor we need to convert it to string. Because Filesystem Plugin by capacitor accepts a string as data then encodes it with utf-8, utf-16 or ascii to save it. Which can be consumed by native audio.

Implementation.

import { FilesystemDirectory, FilesystemEncoding, Plugins } from '@capacitor/core';
import { useState } from 'react';
import { GenericResponse, RecordingData } from 'capacitor-voice-recorder';
import { TextAudioFieldOperations } from '@gastromatic/ui-mobile/dist/components/TextAudioField';

const b64toBlob = (b64Data: string, contentType: string, sliceSize= 512): Blob  => {

  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  return new Blob(byteArrays, { type: contentType });
}

const useAudio = (): TextAudioFieldOperations => {
  const { VoiceRecorder, NativeAudio, Filesystem } = Plugins;
  const [audio, setAudio] = useState({recordDataBase64: ''});
  const [uri, setURI] = useState('');

  const onStart = ():void => {
    VoiceRecorder.hasAudioRecordingPermission().then((result: GenericResponse) => {
        if(result.value) {
          VoiceRecorder.startRecording();
        } else {
          VoiceRecorder.requestAudioRecordingPermission().then((result: GenericResponse) =>
            result.value && VoiceRecorder.startRecording()
          );
        }
      }
    );
  };

  const onStop = async (): Promise<void> => {
    VoiceRecorder.stopRecording()
      .then((result: RecordingData) => setAudio(result.value));
    const blob = b64toBlob(
      audio.recordDataBase64,
      'audio/aac'
    );
    await Filesystem.writeFile({
      path: 'temp.aac',
      data: await blob.text(),
      directory: FilesystemDirectory.Cache,
      encoding: FilesystemEncoding.UTF8,
    }).then((result)=> setURI(result.uri));
  };

  const onPlay =():void => {
    NativeAudio.preloadComplex({
      assetPath: uri,
      assetId: "im_batman",
      volume: 1.0,
      audioChannelNum: 1,
    });
    NativeAudio.play({
      assetId: "im_batman",
    });
  };

  const onPause = ():void => {
    NativeAudio.pause({
      assetId: "im_batman",
    });
  }

  const onDelete = ():void => {
    setAudio({recordDataBase64: ''});
  };

  return {
    onStart,
    onStop,
    onPlay,
    onPause,
    onDelete,
    onSeek: (): void => console.log('Seek'),
  }
};

export default useAudio;

I'm still getting Error 00x00 by native-audio. Or am I doing something wrong? How can we achieve this?

tchvu3 commented 3 years ago

thanks for the input and for the code snippet. i will try to recreate the issue this week and will update this thread with my findings

Jeff909Dev commented 3 years ago

setAudio is asynchronous, then when you try to access audio.recordDataBase64 in the next line you will find an empty string, this could be the problem.

VoiceRecorder.stopRecording()
      .then((result: RecordingData) => setAudio(result.value)); 
    const blob = b64toBlob(
      audio.recordDataBase64,
      'audio/aac'
    );