1

I am developing a widget that plays a random sound. My problem lies in getting the service I use to handle the MediaPlayer and play the sounds to load the sound files I have stored in my app's asset folder.
Instead of a SoundPool as in my application, I opted for a MediaPlayer since it has an onCompletionListener which allows me to stop the Service after the sound has been completely played. That way I hope to minimize the resource usage of the widget.

More precisely, in my code (checkout the [now outdated] commit on GitHub) I try to load a random sound with the MediaPlayer's create() convenience method using an Uri to file:///android_asset/pathToFile.
This however throws an IOException at runtime.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null) {
        final String action = intent.getAction();
        if (ACTION_PLAY_FART.equals(action)) {
            Log.v(LOG_TAG, "Intent received");

            String pathToFile = String.format(
                    Locale.US,
                    "fart%02d.ogg",
                    Utility.getIntBetween(1, 15)
            );
           MediaPlayer tempMediaPlayer = MediaPlayer.create(
                    this,
                    Uri.parse("file:///android_asset/"+pathToFile)
            );
            tempMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    mp.release();
                    stopSelf();
                }
            });
            Log.v(LOG_TAG, "Start playing");
            tempMediaPlayer.start();
        }
    }

    return START_NOT_STICKY;
}

Is there an easy way (i. e., other than a Content Provider) to load those asset files from inside the Service?


[Edit, providing more information]

Apparently it is possible to access the asset files but the MediaPlayer cannot load them properly.

I modified the code to this:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null) {
        final String action = intent.getAction();
        if (ACTION_PLAY_FART.equals(action)) {
            Log.v(LOG_TAG, "Intent received");

            String pathToFile = String.format(
                    Locale.US,
                    "fart%02d.ogg",
                    Utility.getIntBetween(1, 15)
            );
            try {
                AssetFileDescriptor assetFD = this.getAssets().openFd(pathToFile);

                MediaPlayer tempMediaPlayer = new MediaPlayer();
                tempMediaPlayer.setDataSource(assetFD.getFileDescriptor());
                tempMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        mp.release();
                        stopSelf();
                    }
                });
                Log.v(LOG_TAG, "Start playing");
                tempMediaPlayer.start();
            } catch (IOException e) {
                Log.e(LOG_TAG, "File "+pathToFile+" could not be loaded.", e);
            }
        }
    }

The output I get is:

08-29 15:29:54.868  10191-10191/com.y0hy0h.furzknopf V/VolatileFartService﹕ Intent received
08-29 15:29:54.868  10191-10191/com.y0hy0h.furzknopf V/MediaPlayer﹕ constructor
08-29 15:29:54.868  10191-10191/com.y0hy0h.furzknopf V/MediaPlayer﹕ setListener
08-29 15:29:54.868  10191-10191/com.y0hy0h.furzknopf V/MediaPlayer﹕ setDataSource(51, 0, 576460752303423487)
08-29 15:29:54.898  10191-10191/com.y0hy0h.furzknopf E/MediaPlayer﹕ Unable to to create media player
08-29 15:29:54.908  10191-10191/com.y0hy0h.furzknopf E/VolatileFartService﹕ File fart07.ogg could not be loaded.
    java.io.IOException: setDataSourceFD failed.: status=0x80000000
            at android.media.MediaPlayer.setDataSource(Native Method)
            at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1032)
            at com.y0hy0h.furzknopf.widget.VolatileFartService.onStartCommand(VolatileFartService.java:39)
            at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2524)
            at android.app.ActivityThread.access$1900(ActivityThread.java:138)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1302)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4929)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:798)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:565)
            at dalvik.system.NativeStart.main(Native Method)

Note that line 39 is

tempMediaPlayer.setDataSource(assetFD.getFileDescriptor());

Alternitavely, is there a way to pass the FileDescriptor or other object for the service to load?

Or might there be an even better way to handle the playback of possibly multiple sounds simultaneously than to put that functionality in a Service?

J0hj0h
  • 894
  • 1
  • 8
  • 34
  • You can load file from Assets folder in the Service , but before suggesting an answer, Please include your codes so people can easily help rather than assuming what you might or might not need. – Want2bExpert Aug 29 '15 at 13:00
  • I have added a code snippet. For more of the code, I included links to my GitHub repository. Is that a bad way to provide code, should I include more code directly here at Stackoverflow? – J0hj0h Aug 29 '15 at 13:05
  • Have you tried AssetManager asset = Context.get assets(); asset.open("filename");? – Want2bExpert Aug 29 '15 at 13:11
  • That's exactly the problem, the service has no `context` variable. Hence I can't use the `AssetManager` there. – J0hj0h Aug 29 '15 at 13:17

2 Answers2

1

As an alternative to Want2bExpert's suggestion of moving the files into the res/raw folder, the asset files can be loaded "manually" into the MediaPlayer.


Apparently the assets are not stored as would be expected. Instead, the start position and length of the asset together with its (non Asset-)FileDescriptor needs to be passed on to the MediaPlayer.

The working code:

String pathToFile = String.format(
        Locale.US,
        "fart%02d.ogg",
        Utility.getIntBetween(1, 15)
);

try {
    AssetFileDescriptor assetFD = getAssets().openFd(pathToFile);
    MediaPlayer tempMediaPlayer = new MediaPlayer();
    tempMediaPlayer.setDataSource(
            assetFD.getFileDescriptor(),
            assetFD.getStartOffset(),
            assetFD.getLength()
    );
    assetFD.close();
    tempMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            mp.release();
            stopSelf();
        }
    });
    tempMediaPlayer.prepare();
    Log.v(LOG_TAG, "Start playing");
    tempMediaPlayer.start();
} catch (IOException e) {
    Log.e(LOG_TAG, "File "+pathToFile+" could not be loaded.", e);
}
Community
  • 1
  • 1
J0hj0h
  • 894
  • 1
  • 8
  • 34
0

Try this;

  • Create a raw folder inside your resource dir e.g. res/raw
  • put the files to play in the raw folder
  • load the file using media player as below

    MediaPlayer mediaPlayer = MediaPlayer.create(serviceClass.this, R.raw.sound_file_1); mediaPlayer.start();

Read more; http://developer.android.com/guide/topics/media/mediaplayer.html#mpandservices

Want2bExpert
  • 527
  • 4
  • 11
  • From where can I get the `context` to pass on to `create()`? – J0hj0h Aug 29 '15 at 13:17
  • Have you tried to declare a Context global variable in the service? I can't remember doing it before but give it a try. E.g. in your Service class Context context = this; – Want2bExpert Aug 29 '15 at 13:22
  • The only place the Service gets provided a `Context` is in its static `createIntentPlayFart()` method. I cannot set a member variable there. Also, I updated my question since your proposition helped me nail down the problem further. – J0hj0h Aug 29 '15 at 13:38
  • This is interesting, how about you have an intent,service that loads the file from Assest folder into and internal App directory, then broadcast the file directory to Receiver which then starts the Service with FileDir as an extra to intent that starts the service? – Want2bExpert Aug 29 '15 at 14:02
  • Besides Isn't Service a Context? I think the solution I gave should work.for you. See my edited answer. – Want2bExpert Aug 29 '15 at 14:08
  • Yes, you're right. Is there any reason to choose assets over raw folder? I'd like to use the asset folder, since I find the access to it a bit more convenient. And shouldn't it be possible to access the file directly instead of copying it somewhere, as I understand your first comment? – J0hj0h Aug 29 '15 at 14:12
  • Assets is kind of raw anyway – Want2bExpert Aug 29 '15 at 14:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88264/discussion-between-j0hj0h-and-want2bexpert). – J0hj0h Aug 29 '15 at 14:17