Skip to content

Instantly share code, notes, and snippets.

@jdnichollsc
Last active July 17, 2021 05:18
Show Gist options
  • Save jdnichollsc/9d450ee36d9d75b201c01549f8f36fd7 to your computer and use it in GitHub Desktop.
Save jdnichollsc/9d450ee36d9d75b201c01549f8f36fd7 to your computer and use it in GitHub Desktop.
Record audio (Cordova/Ionic) using cordova-plugin-media and cordova-plugin-file
import { Media, MediaObject } from '@ionic-native/media';
import { File as NativeFile, FileEntry } from '@ionic-native/file';
import { delay, getBlobFromFileEntry } from './utils'
// Detect platform device and select extension (Cordova platform, etc)
// Android
const AUDIO_EXTENSION = '.mp3'
// iOS
const AUDIO_EXTENSION = '.m4a'
export class PageDemo {
private audioFile: FileEntry
private audioMedia: MediaObject
// private audioMediaSrc: string
async onStartRecording() {
const path = NativeFile.dataDirectory;
const name = `record_${(new Date()).getTime() + AUDIO_EXTENSION}`;
this.audioFile = await NativeFile.createFile(path, name, true);
// Deprecated versions
// this.audioMediaSrc = this.audioFile.nativeURL.replace(/^file:[\/]+/, '');
// this.audioMedia = Media.create(this.audioMediaSrc);
this.audioMedia = Media.create(this.audioFile.nativeURL);
this.audioMedia.startRecord();
}
async onStopRecording() {
if (this.audioFile) {
try {
this.audioMedia.stopRecord();
// It requires a little delay to get media file data
await delay(200);
const blob = await getBlobFromFileEntry(this.audioFile);
// SAVE AUDIO HERE!
// uploadFile(this.audioFile.name, blob)
} finally {
this.audioFile = null;
}
}
}
}
import { FileEntry } from '@ionic-native/file';
export const getBlobFromFileEntry = (fileEntry: FileEntry): Promise<Blob> => {
return new Promise((resolve, reject) => {
fileEntry.file((file) => {
const reader = new FileReader();
reader.onloadend = function(e) {
try {
const { result: buffer } = e.target
const blob = new Blob(
[new Uint8Array(buffer as any)],
{ type: file.type }
);
resolve(blob);
} catch (error) {
reject(error);
}
};
reader.onabort = (ev) => reject(ev);
reader.onerror = (ev) => reject(ev);
reader.readAsArrayBuffer(file);
}, reject)
})
}
export const delay = (time: number) =>
new Promise(resolve => setTimeout(() => resolve(), time))
@ToschaA
Copy link

ToschaA commented Apr 7, 2021

I solved managed to solve the errors and am now able to upload the audio file to firebase Storage.
Appreciate the help very much!

Now the code looks like:

// Audio recording management

  // Start recording
  async startRecord() {
    var path: string = "";
    if(this.platform.is("ios")){
      path = this.file.tempDirectory;
      this.audioFileName = new Date().getTime() + ".m4a";
      this.audioFile = await this.file.createFile(path, this.audioFileName, true);
      this.audioMedia = this.media.create(this.file.tempDirectory.replace(/^file:\/\//, '') + this.audioFileName);
    } else {
      path = this.file.dataDirectory;
      this.audioFileName = new Date().getTime() + ".mp3";
      this.audioFile = await this.file.createFile(path, this.audioFileName, true);
      //this.audioMediaSrc = this.audioFile.nativeURL.replace(/^file:[\/]+/, '');
      this.audioMedia = this.media.create(this.audioFile.nativeURL);
    }

    this.audioMedia.startRecord();
    this.isRecordingAudio = true;
  }
  // Stop recording
  async stopRecord() {
    this.audioMedia.stopRecord();
    this.isRecordingAudio = false;
    
    // Upload the new recording
    await this.delay(200);
    this.audioFile.file(async (file) => {
      const blob = await this.getBlobFromFile(file);
      this.guideService.uploadFilesToFs([blob]).then(files => {
        this.audioData = {name: files[0].name, url: files[0].url}
      });
    })
  }
  // Convert File to Blob
  getBlobFromFile(file: File): Promise<Blob> {
    return new Promise((resolve, reject) => {
      let fileReader = this.mainService.getFileReader();

      fileReader.onloadend = function() {
        console.log(this.result);
        var blob = new Blob([this.result], { type: file.type })
        resolve(blob);
      };

      fileReader.onerror = function(err) {
        reject(err);
      };

      fileReader.readAsArrayBuffer(file);
    });
  }

Because I am using capacitor I needed to fetch the FileReader instance in a different way. So this.mainService.getFileReader() translates to:

getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
  }

Thanks again! :)

@jdnichollsc
Copy link
Author

jdnichollsc commented Apr 7, 2021

Awesome, thanks for letting me know! <3

@Harshk16
Copy link

Harshk16 commented May 24, 2021

@jdnichollsc, @ToschaA

I am trying to implement the same solution but it's not working for, can you please help me out, below i am adding the code with the issue, Please let me, i will be grateful. Thank you

this.audioFile = await this.file.createFile(this.filePath, this.record.FileName, true);
      this.mediaObject = this.media.create(this.audioFile.nativeURL);
async uploadFile() {
    await delayTimer(200);
    this.audioFile.file(async file => {
        const blob =  await this.getBlobFromFile(file) // fIle return of  IFile not File
    })
  }
getBlobFromFile(file: File): Promise<Blob> {
    return new Promise((resolve, reject) => {
      let fileReader = this.utils.getFileReader();

      fileReader.onloadend = function() {
        console.log(this.result);
        var blob = new Blob([this.result], { type: file.type })
        resolve(blob);
      };
      fileReader.onerror = function(err) {
        reject(err);
      };
      fileReader.readAsArrayBuffer(file);  //(parameter) file: FileArgument of type 'File' is not assignable to parameter of type 'Blob'.
                                                                Type 'File' is missing the following properties from type 'Blob': size, type, arrayBuffer, slice, and 2 
                                                                 
    });
  }
  getFileReader(): FileReader {
    const fileReader = new FileReader();
    const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
    return zoneOriginalInstance || fileReader;
  }

Audio_Error

@jdnichollsc
Copy link
Author

That error is clear, type is wrong :)

@gregor-srdic
Copy link

gregor-srdic commented Jul 17, 2021

Hi guys, great example, thanks, but I am running into a strange problem (although I am setting the file path manualy as above):
W/System.err: java.io.FileNotFoundException: /storage/emulated/0/tmprecording-1626497965957.3gp: open failed: EPERM (Operation not permitted)
Adding android:requestLegacyExternalStorage="true" solved the problem, but I would need to avoid that.
Any idea? Thanks!

@jdnichollsc
Copy link
Author

Why are you using that format? 3gp. Do you know if that codec is supported by that platform? Did you request the permissions for recording audio?

@gregor-srdic
Copy link

I am not using any specific format, it seems like it is the default. This is my entire code, I didn't manually request permissions, but the dialog for recording and file access does popup.

@Injectable()
export class MediaService {
  private readonly logger = new Logger(this.constructor.name);

  constructor(private readonly media: Media, private readonly file: File) {}

  async recordAudio(): Promise<void> {
    const path = this.file.dataDirectory;
    const name = 'recording.mp3';
    const file = await this.file.createFile(path, name, true);
    console.log('file native url', file.nativeURL);

    const mediaFile: MediaObject = this.media.create(file.nativeURL);
    mediaFile.startRecord();
    await wait(10000);
    this.logger.debug('recording stopped');
    mediaFile.stopRecord();
    await wait(1000);
    this.logger.debug('playing started');
    mediaFile.play();
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment