Android MediaPlayer stuck in prepare()

与世无争的帅哥 提交于 2020-01-23 06:45:28

问题


I'm facing a serious problem with the Media Player(MP) being stuck at the prepare() method. My app runs prepare() in an AsyncTask to avoid blocking the UI, since the sources are from the web. There are several 'play' buttons that the user can click at any time, so I added the prepare() inside a synchronized method to better control the state of the MP. My app also call a release() onPause to free the used resources.

Thing is, I noticed that if release() is called while preparing, prepare() never returns, and so I'm stuck inside a synchronized method. The worst of it is that the AsyncTask thread is in a deadlock, and every time that the user click on play in that state another thread is wasted since it keeps waiting to acquire the monitor that is in possesion of the never-returning prepare(). Soon all my AsyncTasks threads are wasted and since I use them extensively, my app stops working.

So my question is: do anyone has an idea of how overcoming this problem? I'm seriously thinking of redoing all my work with the MediaPlayer, but I need to know the best way to handle situations like this one beforehand.


回答1:


You should use prepareAsync() instead. And you won't need AsyncTask just for the sake of MediaPlayer preparation.




回答2:


The problem with using Asyntasks or prepareAsync() for that matter is that it doesn't automatically switch the MediaPlayer's state to prepared. You would think it does, but for some reason it doesn't. I got stuck with this same problem for a few days until someone suggested me to implement the OnPreparedListener, and use that to use my MediaPlayer.

Adding it is quite simple.

First, implement the listener in your class: (I'm using this for a Service so yours will look slightly different.)

public class MusicService extends Service implements OnPreparedListener

Next, in your play / prepare statement, use prepareAsync, and set the listener.

mp.prepareAsync();
mp.setOnPreparedListener(this);

Next, add the onPrepared method and add your starting code:

public void onPrepared(MediaPlayer mediaplayer) {
        // We now have buffered enough to be able to play
        mp.start();
    }

That should do the trick.




回答3:


Thanks for all the answers, but I fixed this not using the prepareAsync() as was mentioned in previous answers. I think that if a synchronous method of preparing was made available, there should be a way of making it work correctly.

The fix, although not elegant IMO, is simple: avoid calling release() inside a prepare(). Although the state diagram in the Media Player docs says that release() can be called in any state, experimentation proved that calling it inside a prepare() (presumably in the 'Preparing' state) will hang your app. So I added a check to see if the MP is in this state and I am now avoding calling release() if it is. I needed to add a boolean in my code to check this, since there is no isPreparing() method in the MP.

I'm confident that this shouldn't happen and is a bug in Android itself. As can be seen in the comments, there is a contradiction in the docs: it says that release() can be called at any state, but also says that calling any state changing commands while in the Preparing state has an undefined outcome. Hope that this will help other people.




回答4:


I solved this issue in my app like this:

Create objects for the AsyncTasks (so you can check if they're in progress):

private AsyncTask<String, Void, String> releaseMP;
private AsyncTask<String, Void, String> setSource;

Create an AsyncTask for the prepare call:

private class setSource extends AsyncTask<String, Void, String> {
    @Override
    protected synchronized String doInBackground(final String... urls) {
        try {
            mMediaPlayer.prepare();
        } catch (final IllegalStateException e) {
            e.printStackTrace();
            return e.getMessage();
        } catch (final IOException e) {
            e.printStackTrace();
            return e.getMessage();
        } catch (final Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }

        return null;
    }

    @Override
    protected void onCancelled() {
        if (setSource != null)
            setSource = null;

        // Send error to listener
        mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);

        releaseMP = new releaseMP().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
    }

    @Override
    protected void onPostExecute(final String result) {
        if (setSource != null)
            setSource = null;

        // Check for error result
        if (result != null) {
            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        }
    }

    @Override
    protected void onPreExecute() {

    }

}

Now your prepare code:

    try {
        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setOnPreparedListener(mPreparedListener);
        mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
        mDuration = -1;
        mMediaPlayer.setOnCompletionListener(mCompletionListener);
        mMediaPlayer.setOnErrorListener(mErrorListener);
        mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
        mCurrentBufferPercentage = 0;
        mMediaPlayer.setDataSource(getContext(), mUri, mHeaders);
        mMediaPlayer.setDisplay(mSurfaceHolder);
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mMediaPlayer.setScreenOnWhilePlaying(true);

        // mMediaPlayer.prepareAsync();
        // we don't set the target state here either, but preserve the
        // target state that was there before.
        mCurrentState = STATE_PREPARING;
    } catch (final IOException ex) {
        Log.w(TAG, "Unable to open content: " + mUri, ex);
        mCurrentState = STATE_ERROR;
        mTargetState = STATE_ERROR;
        mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        return;
    } catch (final IllegalArgumentException ex) {
        Log.w(TAG, "Unable to open content: " + mUri, ex);
        mCurrentState = STATE_ERROR;
        mTargetState = STATE_ERROR;
        mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        return;
    } catch (final Exception ex) {
        Log.w(TAG, "Unable to open content: " + mUri, ex);
        mCurrentState = STATE_ERROR;
        mTargetState = STATE_ERROR;
        mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        return;
    }

    setSource = new setSource().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);

And lastly, when you need to kill the mediaPlayer, you'll check the setSource object to see if it is preparing before you release it. If it is preparing, you'll cancel the AsyncTask and in the AsyncTask onCancelled, you'll reset and release the object:

public void release(final boolean cleartargetstate) {
    if (mMediaPlayer != null) {
        if (setSource != null) {
            setSource.cancel(true);
        } else {
            releaseMP = new releaseMP().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
        }
    }
}

And this is my releaseMP AsyncTask (which just resets and releases the object):

private class releaseMP extends AsyncTask<String, Void, String> {

    @Override
    protected synchronized String doInBackground(final String... urls) {
        Log.i(MethodNameTest.className() + "." + MethodNameTest.methodName(), "called");
        if (mMediaPlayer != null) {
            // Release listeners to avoid leaked window crash
            mMediaPlayer.setOnPreparedListener(null);
            mMediaPlayer.setOnVideoSizeChangedListener(null);
            mMediaPlayer.setOnCompletionListener(null);
            mMediaPlayer.setOnErrorListener(null);
            mMediaPlayer.setOnBufferingUpdateListener(null);
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
        mCurrentState = STATE_IDLE;
        mTargetState = STATE_IDLE;
        return null;
    }

    @Override
    protected void onPostExecute(final String result) {
        Log.i(MethodNameTest.className() + "." + MethodNameTest.methodName(), "called");

        if (releaseMP != null)
            releaseMP = null;
    }

}



回答5:


My Media Player was stuck in Prepare because I was using non-secure URLs, (HTTP instead of HTTPS)

I had to add the following in the android manifest.

<application
    ...
    android:usesCleartextTraffic="true"
    ...
</application>


来源:https://stackoverflow.com/questions/8511210/android-mediaplayer-stuck-in-prepare

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!