How to play local audio files

Hi,

I’m trying to play audio files in my android app with the plugin org.apache.cordova.media but it doesn’t work :frowning:

I’ve got ReferenceError: Media is not defined when I try to create a new media (var my_media = new Media('sound.mp3')).

Looking around, it seems like this plugin is not compatible with new versions of cordova (I’m using version 3.5).

Any idea or tips to get this working ?

Thanks a lot.

2 Likes

You have to reference it differently in Android. Add /android_asset/www/ before the URL. I’ve got a blog post where I demonstrate how to do this for Android only. http://www.raymondcamden.com/2014/6/23/Cordova-Media-API-Example

3 Likes

Thanks a lot for your answer !

I solved my problems :

  1. I called Media before the plugin was loaded (that’s why it wasn’t defined)
  2. I didn’t added /android_asset/www/ before my url on android :frowning:

So, I wrote an angular service to handle all this problems. If you think it could be usefull for you, here it is :

// for media plugin : http://plugins.cordova.io/#/package/org.apache.cordova.media
.factory('MediaSrv', function($q, $ionicPlatform, $window){
  var service = {
    loadMedia: loadMedia,
    getStatusMessage: getStatusMessage,
    getErrorMessage: getErrorMessage
  };

  function loadMedia(src, onError, onStatus, onStop){
    var defer = $q.defer();
    $ionicPlatform.ready(function(){
      var mediaSuccess = function(){
        if(onStop){onStop();}
      };
      var mediaError = function(err){
        _logError(src, err);
        if(onError){onError(err);}
      };
      var mediaStatus = function(status){
        if(onStatus){onStatus(status);}
      };

      if($ionicPlatform.is('android')){src = '/android_asset/www/' + src;}
      defer.resolve(new $window.Media(src, mediaSuccess, mediaError, mediaStatus));
    });
    return defer.promise;
  }

  function _logError(src, err){
    console.error('media error', {
      code: err.code,
      message: getErrorMessage(err.code)
    });
  }

  function getStatusMessage(status){
    if(status === 0){return 'Media.MEDIA_NONE';}
    else if(status === 1){return 'Media.MEDIA_STARTING';}
    else if(status === 2){return 'Media.MEDIA_RUNNING';}
    else if(status === 3){return 'Media.MEDIA_PAUSED';}
    else if(status === 4){return 'Media.MEDIA_STOPPED';}
    else {return 'Unknown status <'+status+'>';}
  }

  function getErrorMessage(code){
    if(code === 1){return 'MediaError.MEDIA_ERR_ABORTED';}
    else if(code === 2){return 'MediaError.MEDIA_ERR_NETWORK';}
    else if(code === 3){return 'MediaError.MEDIA_ERR_DECODE';}
    else if(code === 4){return 'MediaError.MEDIA_ERR_NONE_SUPPORTED';}
    else {return 'Unknown code <'+code+'>';}
  }

  return service;
})

It can be used like this :

MediaSrv.loadMedia('sounds/mysound.mp3').then(function(media){
  media.play();
});

Moreover, I wrote a a fallback for this plugin to emulate it on browser. Feel free to use it :

window.Media = function(src, mediaSuccess, mediaError, mediaStatus){
  // src: A URI containing the audio content. (DOMString)
  // mediaSuccess: (Optional) The callback that executes after a Media object has completed the current play, record, or stop action. (Function)
  // mediaError: (Optional) The callback that executes if an error occurs. (Function)
  // mediaStatus: (Optional) The callback that executes to indicate status changes. (Function)

  if (typeof Audio !== "function" && typeof Audio !== "object") {
    console.warn("HTML5 Audio is not supported in this browser");
  }
  var sound = new Audio();
  sound.src = src;
  sound.addEventListener("ended", mediaSuccess, false);
  sound.load();

  return {
    // Returns the current position within an audio file (in seconds).
    getCurrentPosition: function(mediaSuccess, mediaError){ mediaSuccess(sound.currentTime); },
    // Returns the duration of an audio file (in seconds) or -1.
    getDuration: function(){ return isNaN(sound.duration) ? -1 : sound.duration; },
    // Start or resume playing an audio file.
    play: function(){ sound.play(); },
    // Pause playback of an audio file.
    pause: function(){ sound.pause(); },
    // Releases the underlying operating system's audio resources. Should be called on a ressource when it's no longer needed !
    release: function(){},
    // Moves the position within the audio file.
    seekTo: function(milliseconds){}, // TODO
    // Set the volume for audio playback (between 0.0 and 1.0).
    setVolume: function(volume){ sound.volume = volume; },
    // Start recording an audio file.
    startRecord: function(){},
    // Stop recording an audio file.
    stopRecord: function(){},
    // Stop playing an audio file.
    stop: function(){ sound.pause(); if(mediaSuccess){mediaSuccess();} } // TODO
  };
};

If you see any possible improvments, I will be glad to discuss about :smiley:

14 Likes

hey @loicknuchel, thanks so much for posting this! I was trying to do something similar but then realized that the audio stops playing once I exit the app or lock the phone. I’ve googled around and tried some things without luck. Did you try this and if so did you get it to work?

Hi @victormejia,

I didn’t tried to keep sound while user leave app…
In Android, I think you should look to service (still active even if the user exit app) but if you need this, it could be interesting to go native…
Anyway, I’m interested if you find some solutions…

Good luck :wink:

Hi everyone,

I just publish my app for a public beta (http://cookers.io/download/) and I just noticed that the plugin org.apache.cordova.media need a lot of permissions on Android (I simply want to play a sound !) :

  • android.permission.WRITE_EXTERNAL_STORAGE
  • android.permission.RECORD_AUDIO
  • android.permission.MODIFY_AUDIO_SETTINGS
  • android.permission.READ_PHONE_STATE

Any idea to reduce permissions asked ?

Thanks !

Hey that is nice!
How is your custom service playing media in your app? Is it consistent across all device (different versions of Android and IOS).

Hi @siddhartha,

My service is just a wrapper for the plugin http://plugins.cordova.io/#/package/org.apache.cordova.media
His main purpose is to add a specific path for android devices and wait for cordvoa ready event before trying to load the Media.
I use the promise API to get things easy with asynchronous operations.

You can play a sound with :

MediaSrv.loadMedia('sounds/mysound.mp3').then(function(media){
  media.play();
});

For the device consistency, it should be OK but I only tested on my Nexus 4…

I tried it but without the plugin for the browser. Yeah I am using it for its async operation.
Is it ok if I call $ionicPlatform.ready() in every controller instance that I have?

I think it’s ok to call $ionicPlatform.ready() as much as needed (it’s fired immediatly if device if already ready http://ionicframework.com/docs/api/utility/ionic.Platform/).
But the service I posted take care of that.

1 Like

this worked fine for me in the browser. but one thing i don’t know is how to stop or pause the playback. i am pretty much a noob at this. Please help

Hi,

You have some method to do that on media object :

  • stop()
  • pause()

My browser fallback details all avaiable method with a comment to explain its usage.
You can also look at the plugin documentation which details every method with an example : http://plugins.cordova.io/#/package/org.apache.cordova.media

1 Like

@loicknuchel, thanks for your Angularized media service. I was able to use it with additions to support pause and stop the media. This works fine on the browser but does not work on my android device(v4.2).
I tried prepending file:// as mentioned by some articles on SO. However that didn’t help either.
On the Logcat window, I see media error (1, -2147483648) when I play the file. I have verified that my url is indeed correct. Any help would be appreciated? Thanks

it’s taken me most of the weekend to get to the bottom of this!! just for info. it looks like the ngCordova implementation might have a few problems

after careful consideration, it might be better to change the constructor to a non-promisey one, but only make certain functions…like getDuration promisey. The reasoning here is that the media callbacks need to fire every time the file is played to allow ui updates, for example the play button could change to a pause button. If you make the constructor promisey it will only fire these callbacks the first time the file is played.

therefore you might want to change the constructor to the following in ng-cordova.js

  newMedia: function (src,success,error,mediaStatus) { 
    var media = new Media(src,success,error,mediaStatus);
    return media;
  },

so the finished module would be something like

angular.module('ngCordova.plugins.media', [])

  .factory('$cordovaMedia', ['$q', function ($q) {

    return {
      newMedia: function (src,success,error,mediaStatus) { 
        var media = new Media(src,success,error,mediaStatus);
        return media;
      },

      getCurrentPosition: function (source) {
        var q = $q.defer();

        source.getCurrentPosition(function (success) {
          q.resolve(success);

        }, function (error) {
          q.reject(error);
        });

        return q.promise;
      },

      getDuration: function (source) {

        return source.getDuration();
      },

      play: function (source) {
        source.play();

        // iOS quirks :
        // -  myMedia.play({ numberOfLoops: 2 }) -> looping
        // -  myMedia.play({ playAudioWhenScreenIsLocked : false })
      },

      pause: function (source) {
        return source.pause();
      },

      release: function (source) {
        return source.release();
      },


      seekTo: function (source, milliseconds) {

        return source.seekTo(milliseconds);
      },

      setVolume: function (source, volume) {
        return source.setVolume(volume);
      },

      startRecord: function (source) {
        return source.startRecord();
      },

      stopRecord: function (source) {

        return source.stopRecord();
      },

      stop: function (source) {
        return source.stop();
      }
    };

you coud then play the file with this kind of code

$ionicPlatform.ready(
    function () {

        var filestring='/android_asset/www/gong.mp3';

          $scope.thisMedia = new $cordovaMedia.newMedia(filestring,
              //fires after the file has played
            function(success){
                alert('success');
            },
            function(error){
                alert(err);
            },
            //fires when media status changes
            function(ms){
                alert(ms);
            }    
          );

          $scope.mediaStuff=$scope.thisMedia;
      }
  );



$scope.playMedia = function () {  
  $cordovaMedia.play($scope.thisMedia);
};

$scope.stopMedia = function () {
  $cordovaMedia.stop($scope.thisMedia);
};

$scope.pauseMedia = function () {
  $cordovaMedia.pause($scope.thisMedia);
};

I got it working finally with the help from this excellent blog and was able to adapt it to code from @loicknuchel.

1 Like

Hello, how did You solved automatic repeating of the sound file in Your service?

In onStatus should be placed something like:

 if (status === Media.MEDIA_STOPPED) {
        myMedia.play();
    }

But i don’t know how to get instance of myMedia ?

Thanks for any help.

Really nobody knows how to do repeat?

Hi @loicknuchel, I followed you post yesterday, the Media Plugin works only with local file. When I try to play an online mp3 file with my iOS device & iOS emulator, I received the error below and the HTML5 fallback have never been called:
media error
[Object
code: 1
message: “MediaError.MEDIA_ERR_ABORTED”
]

It seems the Media plugin is outdated since the Cordova 4.2.0, can you help ?

Hi,

Sadly the Media plugin is really poor in feature, documentation, standards, support and modularity but it’s the only one plugin to play sound until someone else writes and support one :frowning:

I didn’t tried online files with this plugin but it can be a plugin limitation (one more pain point…). In this case, you should look to locally download the file and then play it… Quite overkill but I don’t see an other way if the plugin do not accept online files… :frowning:
And I didn’t know iOS…

In Android, the HTML5 audio API is muted, it should be the same on iOS, that’s why the fallback didn’t work.

Thank you for your fast response, I will try your suggestion. Merci bcp !

@loicknuchel
Hi, I use you code, now it’s worded.
But I want get the media status, like html5 pased( ).
And when the media end, the media should give me a event, tell me the media is end.
Can you tell me how to do it ?

Thanks.