Ionic-native: MediaPlugin does not save audio recording into expected path/file [Android]

Created on 26 May 2017  路  8Comments  路  Source: ionic-team/ionic-native

I'm submitting a ... (check one with "x")
[x] bug report
[ ] feature request

Current behavior:

Deployed to an android v5.1.1 tablet and the code hits all console.log message successfully.

  • The audio file, namely record.m4a has 0 byte size after the code run, and the audioObject.getDuration() is -1, indicating no audio content is saved.
  • The recorded content, however, is saved in local -> internal storage (a path that I can view in File tool in Android without root) with name temprecording-xxxxxxx.3gp (the xxxxxxx system assigned digital numbers)

Expected behavior:

the audio file should have the content and appears in correct path.
Steps to reproduce:

Related code:

import { Component } from '@angular/core';
import { Platform, IonicPage, NavController } from 'ionic-angular';
import { MediaPlugin, MediaObject } from '@ionic-native/media';
import { File } from '@ionic-native/file';

constructor(public navCtrl: NavController, private media: MediaPlugin, private file: File, private platform: Platform) {
    this.platform.ready().then(() => {
      if (!this.platform.is('cordova')) {
        return false;
      }

      if (this.platform.is('ios')) {
        this.storageDirectory = file.documentsDirectory.replace(/^file:\/\//, '');
      }
      else if (this.platform.is('android')) {
        this.storageDirectory = file.dataDirectory;
      }
      else {
        // future usage for more platform support
        return false;
      }
    });
  }

  record() {
    const fileName = 'record.m4a';

    this.file.createFile(this.storageDirectory, fileName, true).then(() => {
      let audioObject: MediaObject = this.media.create(this.storageDirectory + fileName);

      audioObject.startRecord();
      console.log('cache dir: ' + this.file.cacheDirectory);
      console.log(`start recording ${fileName}`);
      window.setTimeout(() => {
        audioObject.stopRecord();
        console.log('duration: ' + audioObject.getDuration());
        audioObject.release();
        console.log(`done recording ${fileName}`);
        /** Do something with the record file and then delete */
        // this.file.removeFile(this.file.tempDirectory, 'record.m4a');
      }, 5000);
    });
  }

Other information:

this thread has similar issue, but with older version ionic-native.
https://forum.ionicframework.com/t/audio-recording-with-the-native-media-plugin-on-android/86042
package.json info:

{
    "name": "firebase-email-auth",
    "version": "0.0.1",
    "author": "Ionic Framework",
    "homepage": "http://ionicframework.com/",
    "private": true,
    "scripts": {
        "clean": "ionic-app-scripts clean",
        "build": "ionic-app-scripts build",
        "lint": "ionic-app-scripts lint",
        "ionic:build": "ionic-app-scripts build",
        "ionic:serve": "ionic-app-scripts serve"
    },
    "dependencies": {
        "@angular/common": "4.1.0",
        "@angular/compiler": "4.1.0",
        "@angular/compiler-cli": "4.1.0",
        "@angular/core": "4.1.0",
        "@angular/forms": "4.1.0",
        "@angular/http": "4.1.0",
        "@angular/platform-browser": "4.1.0",
        "@angular/platform-browser-dynamic": "4.1.0",
        "@ionic-native/core": "3.7.0",
        "@ionic-native/facebook": "^3.10.3",
        "@ionic-native/file": "^3.10.3",
        "@ionic-native/keyboard": "^3.10.3",
        "@ionic-native/media": "^3.10.3",
        "@ionic-native/splash-screen": "^3.7.0",
        "@ionic-native/status-bar": "3.7.0",
        "@ionic-native/transfer": "^3.10.3",
        "@ionic/storage": "2.0.1",
        "angularfire2": "^4.0.0-rc.0",
        "cordova-android": "^6.2.3",
        "cordova-plugin-compat": "^1.1.0",
        "cordova-plugin-console": "^1.0.5",
        "cordova-plugin-crosswalk-webview": "^2.3.0",
        "cordova-plugin-device": "^1.1.4",
        "cordova-plugin-facebook4": "^1.9.0",
        "cordova-plugin-file": "^4.3.3",
        "cordova-plugin-file-transfer": "^1.6.3",
        "cordova-plugin-media": "^3.0.1",
        "cordova-plugin-splashscreen": "^4.0.3",
        "cordova-plugin-statusbar": "^2.2.2",
        "cordova-plugin-whitelist": "^1.3.1",
        "cordova-plugin-wkwebview-engine": "git+https://github.com/driftyco/cordova-plugin-wkwebview-engine.git",
        "firebase": "3.9.0",
        "ionic-angular": "3.2.1",
        "ionic-plugin-keyboard": "^2.2.1",
        "ionicons": "3.0.0",
        "plyr": "^2.0.13",
        "rxjs": "5.1.1",
        "sw-toolbox": "3.6.0",
        "zone.js": "0.8.10"
    },
    "devDependencies": {
        "@ionic/app-scripts": "1.3.7",
        "@ionic/cli-plugin-cordova": "1.2.1",
        "@ionic/cli-plugin-ionic-angular": "1.2.0",
        "typescript": "2.2.1"
    },
    "description": "An Ionic project",
    "cordova": {
        "plugins": {
            "cordova-plugin-facebook4": {
                "APP_ID": "229997797493525",
                "APP_NAME": "test-mobile"
            },
            "cordova-plugin-console": {},
            "cordova-plugin-device": {},
            "cordova-plugin-splashscreen": {},
            "cordova-plugin-statusbar": {},
            "cordova-plugin-whitelist": {},
            "ionic-plugin-keyboard": {},
            "cordova-plugin-media": {},
            "cordova-plugin-file-transfer": {},
            "cordova-plugin-crosswalk-webview": {
                "XWALK_VERSION": "23+",
                "XWALK_LITEVERSION": "xwalk_core_library_canary:17+",
                "XWALK_COMMANDLINE": "--disable-pull-to-refresh-effect",
                "XWALK_MODE": "embedded",
                "XWALK_MULTIPLEAPK": "true"
            },
            "cordova-plugin-wkwebview-engine": {}
        },
        "platforms": [
            "android"
        ]
    }
}
media

Most helpful comment

Ok I just figured out how to get this working on Android...

I tried using full file URLs and it didn't work, even with cdvfile:/// protocol. Here's what did work though:

  • create new media file, enter file name only, not a full path. Example file.mp3
  • record to the file
  • stop recording
  • wait a second or so (or wait for onStatusChange event to fire)
  • now the file should be available at /sdcard/file.mp3... meaning you can retrieve it by calling this.file.resolveLocalFilesystemUrl(this.file.externalRootDirectory + 'file.mp3')
  • you can now move the file elsewhere if you wish...

So my guess is that on Android, the path is relative to /sdcard/ (or externalRootDirectory).

I haven't tested this on iOS yet but by reading their docs here, it looks like you can do something like this:

let file = this.media.create(this.file.tempDirectory + 'file.mp3');

Here's my code:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { HomePage } from '../pages/home/home';

import { MediaPlugin } from '@ionic-native/media';
import { File } from '@ionic-native/file';
@Component({
  templateUrl: 'app.html',
  providers: [ MediaPlugin, File ]
})
export class MyApp {
  rootPage:any = HomePage;

  constructor(platform: Platform, media: MediaPlugin, file: File) {
    platform.ready()
      .then(() => {

        let stopCount = 0;

        const fileName: string = 'myfile2.mp3';

        let m = media.create(fileName, (status) => {
          console.log('Status updated. ', status, (status === 4 && ++stopCount === 2 && m.getDuration()) || '');
        }, x => console.log('Success: ',  x), e => console.log('Error: ',  e));

        m.startRecord();
        console.log('RECORDING');


        setTimeout(() => {
          m.stopRecord();
          console.log('DONE RECORDING');

          m.play();
          console.log('PLAYING');

          file.resolveLocalFilesystemUrl(file.externalRootDirectory + fileName)
            .then(r => console.log(r))
            .catch(e => console.log('Error #2: ',  e));

        }, 3000);



      });
  }
}


All 8 comments

This is not an issue with Ionic Native.. Just how the plugin works.

I'm not so sure how it to works to be honest. I noticed that if I just provide a file name it works well. But if I try to provide a full path like what you're trying to do, it doesn't work that well.

When I provide only the file name, it saves it to the root storage directory of my Android device.

I'll try to have a look at this when I get a chance. Let me know if you figure it out. We should probably update the docs so the usage is more clear.

I have spent the whole day figuring it, no success.

I also tried using cordova-plugin-media directly (both latest version and older versions) without ionic-native wrapper. Same problem.

I am now really uncertain whether it is my device or what caused this..

@ihadeed I assume an app has no write permission to root?
when you say 'it doesn't work that well', do you mean you have exact same symptom?

Ok I just figured out how to get this working on Android...

I tried using full file URLs and it didn't work, even with cdvfile:/// protocol. Here's what did work though:

  • create new media file, enter file name only, not a full path. Example file.mp3
  • record to the file
  • stop recording
  • wait a second or so (or wait for onStatusChange event to fire)
  • now the file should be available at /sdcard/file.mp3... meaning you can retrieve it by calling this.file.resolveLocalFilesystemUrl(this.file.externalRootDirectory + 'file.mp3')
  • you can now move the file elsewhere if you wish...

So my guess is that on Android, the path is relative to /sdcard/ (or externalRootDirectory).

I haven't tested this on iOS yet but by reading their docs here, it looks like you can do something like this:

let file = this.media.create(this.file.tempDirectory + 'file.mp3');

Here's my code:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { HomePage } from '../pages/home/home';

import { MediaPlugin } from '@ionic-native/media';
import { File } from '@ionic-native/file';
@Component({
  templateUrl: 'app.html',
  providers: [ MediaPlugin, File ]
})
export class MyApp {
  rootPage:any = HomePage;

  constructor(platform: Platform, media: MediaPlugin, file: File) {
    platform.ready()
      .then(() => {

        let stopCount = 0;

        const fileName: string = 'myfile2.mp3';

        let m = media.create(fileName, (status) => {
          console.log('Status updated. ', status, (status === 4 && ++stopCount === 2 && m.getDuration()) || '');
        }, x => console.log('Success: ',  x), e => console.log('Error: ',  e));

        m.startRecord();
        console.log('RECORDING');


        setTimeout(() => {
          m.stopRecord();
          console.log('DONE RECORDING');

          m.play();
          console.log('PLAYING');

          file.resolveLocalFilesystemUrl(file.externalRootDirectory + fileName)
            .then(r => console.log(r))
            .catch(e => console.log('Error #2: ',  e));

        }, 3000);



      });
  }
}


@ihadeed Thanks for your time! The problem has been located in the thread below.
It seems the media.create resolves file-path in an odd way both for iOS and Android.

https://forum.ionicframework.com/t/mediaplugin-does-not-save-audio-recording-into-file-ionic-native/91868

MediaPlugin has since updated and it seems like using the "file:///" replace is no longer the solution; it just causes a different error to pop up.

I've been able to save the file under the internal root directory on android and then I can move it elsewhere. It does solve the problem in a way, but there's no reason I can't outright save the file in the correct place.

Hi everyone, I found this solution for Android

// Create root file -> my_file.mp3
this.file.createFile(this.file.externalApplicationStorageDirectory, 'my_file.mp3', true).then(() => {
  // Get file created -> my_file.mp3
  let audioObject: MediaObject = this.media.create(this.file.externalApplicationStorageDirectory.replace(/file:\/\//g, '') + 'my_file.mp3');
  audioObject.release();
  // Start Record
  audioObject.startRecord();

  // This method is very important to know your execution process
  // In my case the recording interface is not shown as in IOS
  audioObject.onStatusUpdate.forEach(n => {
    switch (n) {
        case MEDIA_STATUS.RUNNING: // return code 2 = recording audio
          // recording
        break;
        case MEDIA_STATUS.STOPPED: // return code 4 = stopped audio
            this.file.readAsDataURL(this.file.externalApplicationStorageDirectory, 'my_file.mp3').then(audioo => {
              // base64 audio
            });
            this.loading.hide();
        break;
    }

  });

  window.setTimeout(() => audioObject.stopRecord(), 9000);
});
DOCS https://www.npmjs.com/package/cordova-plugin-media

Media.MEDIA_NONE = 0;
Media.MEDIA_STARTING = 1;
Media.MEDIA_RUNNING = 2;
Media.MEDIA_PAUSED = 3;
Media.MEDIA_STOPPED = 4;

I hope it helps many. 馃憤

I'm not familiar with using externalApplicationStorageDirectory, but doesn't that also involve external storage, same as externalRootDirectory?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ajcrites picture ajcrites  路  3Comments

kyleap picture kyleap  路  4Comments

goleary picture goleary  路  3Comments

icchio picture icchio  路  3Comments

rajanshahsa picture rajanshahsa  路  3Comments