15

I'm trying to use the android MediaPlayer class to play some sounds.

Here's the code

MediaPlayer mp = new MediaPlayer(); 
mp.setDataSource(context, Uri.parse(soundUrl)); 
mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 
mp.setLooping(false); 
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 
    @Override 
            public void onCompletion(MediaPlayer mp) { 
                Log.i(LOGTAG, "onComplete hit"); 
                mp.stop(); 
                mp.release(); 
            } 
    });         

mp.prepare(); 
mp.start();

This code runs in a service, but for some reason the sound plays ok, but anything placed into the onCompletion doesn't seem to fire. I then get a message in the logcat that the mediaplayer wasn't released. I'm at a loss to what I've got wrong with this.

I'm running this testing on a galaxy nexus 4.0.4 stock rom.

I also notice that the sound can get clipped at the end.

Andrew
  • 7,548
  • 7
  • 50
  • 72
  • Maybe the app is on sleep. Try this https://stackoverflow.com/questions/4813486/oncompletion-isnt-being-called-when-i-would-expect-it-to/60911323#60911323 – SJX Mar 29 '20 at 09:26

5 Answers5

35

It's actually simple (but silly). Set your listener after you call start(), like so:

ediaPlayer mp = new MediaPlayer(); 
mp.setDataSource(context, Uri.parse(soundUrl)); 
mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 
mp.setLooping(false); 
mp.prepare(); 
mp.start();
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 
    @Override 
            public void onCompletion(MediaPlayer mp) { 
                Log.i(LOGTAG, "onComplete hit"); 
                mp.stop(); 
                mp.release(); 
            } 
    });         
ajacian81
  • 7,419
  • 9
  • 51
  • 64
  • 6
    Could you explain why calling start() before setting the completion listener is important? Is it an Android quirk? – Fabian Tamp Jun 02 '15 at 04:58
  • 1
    @FabianTamp it's another Android quirk. It might not be required for all devices (or even anymore) but at the time it was definitely a fix for the issue. – ajacian81 Jun 03 '15 at 15:30
  • 2
    I couldn't see any evidence to support why that would fix things in the Android source, though. Turns out the root problem for me was that the MediaPlayer was being GC'd before it finished playing, I've added an answer that outlines in more detail :) – Fabian Tamp Jun 04 '15 at 01:04
  • i can't complain more, this saved me :) – Ceddy Muhoza Aug 03 '16 at 09:31
3

Here is how I have it:

    video.setOnCompletionListener(this);
    IntroClip.execute(video);
}

@Override
public void onCompletion(MediaPlayer mp){
    Button LoginButton;
    Button CreateAccount;
    Button RecoverPass;

    setContentView(R.layout.loginmenu);
    Spin = (ProgressBar)findViewById(R.id.Spinner);

    mp.release();       
}
Araw
  • 2,410
  • 3
  • 29
  • 57
  • I guess "video" is an instance of MediaPlayer? and that you've got a "implements MediaPlayer.OnCompletionListener" in your class? I've tried this method also but it didn't work. – Andrew Apr 04 '12 at 06:06
  • 2
    @Andrew How is it accepted answer, if it didnt work? Did you make it work? If yes, how? – hendrix Apr 04 '13 at 15:40
  • 2
    @smitalm call start() before you set your listener. – ajacian81 Oct 24 '13 at 01:50
2

I was encountering similar symptoms to this, and the root cause was that the MediaPlayer was getting garbage collected before the OnCompletionListener was being called.

Judging from your code, it looks like the same problem - your code doesn't hold a long-lived reference to the MediaPlayer, so as soon as that function ends (and before the audio finishes playing) the MediaPlayer is susceptible to GC.

This problem is identifiable by this log line:

02-22 13:14:57.969: W/MediaPlayer-JNI(16888): MediaPlayer finalized without being released

You can fix it by rearchitecting the class such that the MediaPlayer reference is kept for longer - by storing a reference to it in the activity, and reusing the same instance to play the same sound multiple times, for example.

There's a more detailed explanation here: Garbage Collection causes : MediaPlayer finalized without being released

Community
  • 1
  • 1
Fabian Tamp
  • 4,416
  • 2
  • 26
  • 42
2

Actually, the reason is that the MediaPlayer is a local variable. After the function finished, the MediaPlayer is collected by GC. So the fix is easy, make your MediaPlayer a member of the class.

YourClassName {
    MediaPlayer mp = new MediaPlayer(); 

    void YourFunction() {
          mp.setDataSource(context, Uri.parse(soundUrl)); 
          mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 
          mp.setLooping(false); 
          mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 
              @Override 
              public void onCompletion(MediaPlayer mp) { 
                  Log.i(LOGTAG, "onComplete hit"); 
                  mp.stop(); 
                  mp.release(); 
              }
          });         
          mp.prepare(); 
          mp.start();
    }
}
DàChún
  • 4,751
  • 1
  • 36
  • 39
1

There are two approaches to initialise the MediaPlayer object, "new" and "create()". In order to do OnCompletionListener, it is different for the objects obtained in these two approaches.

1) the "new" approach

MediaPlayer mp = new MediaPlayer();
mp.setDataSource(context, Uri.parse(soundUrl)); 
mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 
mp.setLooping(false); 
mp.prepare(); 
mp.start();
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 
    @Override 
    public void onCompletion(MediaPlayer mp) { 
        Log.i(LOGTAG, "onComplete hit"); 
        mp.stop(); 
        mp.release(); 
    } 
}); 

2) the "create" method

MediaPlayer mp = MediaPlayer.create(getActivity(), Uri.parse(soundUrl));
//mp.prepare() is not needed here
mp.setLooping(false);
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
    @Override
    public void onCompletion(MediaPlayer mp) {
        Log.i(LOGTAG, "onComplete hit");
        mp.stop();
        mp.release();
    }
});

For the create() method, I have experienced the similar issue. If mp.prepare() is called after calling the create(), the procedure will never reach the following setOnCompletionListener, not even to the start(). The essential reason is that "the objects are in the Prepared state if the creation using create method is successful"(https://developer.android.com/reference/android/media/MediaPlayer.html). So you do not need to call the prepare() after using the create() method.

Shuyan Ji
  • 121
  • 2
  • 3