Android MediaPlayer reset freezes UI

android, android-mediaplayer, java

Solution

MediaPlayer is a tricky bastard. I recommend you take a look at the sample app where the MediaPlayer bad design is made evident by looking at the mess of code you have to write around it to have a consistent media playback experience.

If anything, after looking at the sample, you see that when they want to skip a track, they essentially reset and release…

    mPlayer.reset();
    mPlayer.release();

…and later when they are ready to load a new track…

    try {
          mPlayer.reset();
          mPlayer.setDataSource(someUrl);
          mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
             @Override
              public void onPrepared(MediaPlayer mediaPlayer) {
                   //bam!
              }
          });
          mPlayer.prepareAsync();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    }

I have added the try/catch because on some devices/OS versions, the MediaPlayer is worse than others and sometimes it just does weird stuff. You should have an Interface/Listener that is capable of reacting to these situations

UPDATE:

This is a method I use when I stop (or pause) my Music Playback (mostly taken from the sample app, this is running in a service and it has been modified to suit my own app but still).

The first method is used by both `stop` and `pause`, the former passes `true`, the later `false`

/**
 * Releases resources used by the service for playback. This includes the "foreground service"
 * status and notification, the wake locks and possibly the MediaPlayer.
 *
 * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
 */
void relaxResources(boolean releaseMediaPlayer) {
    stopForeground(true);
    stopMonitoringPlaybackProgress();
    // stop and release the Media Player, if it's available
    if (releaseMediaPlayer && mPlayer != null) {
        mPlayer.reset();
        mPlayer.release();
        mPlayer = null;
    }
    // we can also release the Wifi lock, if we're holding it
    if (mWifiLock.isHeld()) {
        mWifiLock.release();
    }
}

This is part of the `processPauseRequest()`:

if (mState == State.Playing) {
        // Pause media player and cancel the 'foreground service' state.
        mState = State.Paused;
        mPlayer.pause();
        dispatchBroadcastEvent(ServiceConstants.EVENT_AUDIO_PAUSE);//notify broadcast receivers
        relaxResources(false); // while paused, we always retain the mp and notification

And this is part of the `processStopRequest()` (simplified):

void processStopRequest(boolean force, final boolean stopSelf) {
    if (mState == State.Playing || mState == State.Paused || force) {
        mState = State.Stopped;
        // let go of all resources...
        relaxResources(true);
        currentTrackNotification = null;
        giveUpAudioFocus();         

    }
}

Now the core part is the next/skip…

This is what I do…

void processNextRequest(final boolean isSkipping) {
    processStopRequest(true, false); // THIS IS IMPORTANT, WE RELEASE THE MP HERE
    mState = State.Retrieving;
    dispatchBroadcastEvent(ServiceConstants.EVENT_TRACK_INFO_LOAD_START);
    // snipped but here you retrieve your next track and when it's ready…
    // you just processPlayRequest() and "start from scratch"

This is how the MediaPlayer sample does it (found in the samples folder) and I haven't had problems with it.

That being said, i know what you mean when you say you get the whole thing blocked, I've seen it and it's the MP buggyness. If you get an ANR I'd like to see the log for it.

For the record here's how I "begin playing" (a lot of custom code has been omited but you get to see the MP stuff):"

/**
 * Starts playing the next song.
 */
void beginPlaying(Track track) {
    mState = State.Stopped;
    relaxResources(false); // release everything except MediaPlayer
    try {
        if (track != null) {
            createMediaPlayerIfNeeded();
            mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mPlayer.setDataSource(track.audioUrl);
        } else {
            processStopRequest(true, false); // stop everything! 
            return;
        }
        mState = State.Preparing;
        setUpAsForeground(); //service

        /* STRIPPED ALL CODE FROM REMOTECONTROLCLIENT, AS IT ADDS A LOT OF NOISE :) */

        // starts preparing the media player in the background. When it's done, it will call
        // our OnPreparedListener (that is, the onPrepared() method on this class, since we set
        // the listener to 'this').
        // Until the media player is prepared, we *cannot* call start() on it!
        mPlayer.prepareAsync();
        // We are streaming from the internet, we want to hold a Wifi lock, which prevents
        // the Wifi radio from going to sleep while the song is playing.
        if (!mWifiLock.isHeld()) {
            mWifiLock.acquire();
        }

    } catch (IOException ex) {
        Log.e("MusicService", "IOException playing next song: " + ex.getMessage());
        ex.printStackTrace();
    }
}

As a final note, I've noticed that the "media player blocking everything" happens when the audio stream or source is unavailable or unreliable.

Good luck! Let me know if there's anything specific you'd like to see.

Problem

I have a problem with the Android `MediaPlayer` when changing the `dataSource` of the player. According the specification of the `MediaPlayer` (http://developer.android.com/reference/android/media/MediaPlayer.html) I have to `reset` the player when changing the `dataSource`. This works fine, but as soon as the `channelChanged` method is called twice in quick succession the `MediaPlayer.reset` freezes the UI. I profile the code as seen here: ``` public void channelChanged(String streamingUrl) { long m1 = System.currentTimeMillis(); mMediaPlayer.reset(); long m2 = System.currentTimeMillis(); try { mMediaPlayer.setDataSource(streamingUrl); } catch (IOException e) { e.printStackTrace(); } long m3 = System.currentTimeMillis(); mMediaPlayer.prepareAsync(); long m4 = System.currentTimeMillis(); Log.d("MEDIAPLAYER", "reset: " + (m2 - m1)); Log.d("MEDIAPLAYER", "setDataSource: " + (m3 - m2)); Log.d("MEDIAPLAYER", "preparing: " + (m4 - m3)); } ``` reset: 3 setDataSource: 1 preparing: 0 reset: 3119 setDataSource: 2 preparing: 1 So apparently the `reset` is blocked by the `asynchronous preparing` of the first call (when I wait until the first stream starts and then call `channelChanged()` again, everything is fine). Any ideas how to solve the problems? Should I execute the whole method in a separate thread? Basically I want to avoid that, because it seems not to be a good coding style and can possibly cause some further issues, e.g. when the user tries to start the player again, but the player is still in the `reset` method, which on the other hand seems to wait for the `asyncPrepare` method. It is not clear how the player would behave... Is there any other good solution?

Original source