2011-12-14 22 views
6

Tengo un problema grave con el Reproductor multimedia (MP) bloqueado en el método prepare(). Mi aplicación ejecuta prepare() en un AsyncTask para evitar el bloqueo de la IU, ya que las fuentes proceden de la web. Hay varios botones de "reproducción" en los que el usuario puede hacer clic en cualquier momento, así que agregué el prepare() dentro de un método sincronizado para controlar mejor el estado del MP. Mi aplicación también llama a release() onPause para liberar los recursos utilizados.Android MediaPlayer atorado en prepare()

La cosa es, me di cuenta de que si se llama release() mientras se prepara, prepare() nunca regresa, y entonces estoy atrapado dentro de un método sincronizado. Lo peor de todo es que el subproceso AsyncTask está en un punto muerto, y cada vez que el usuario hace clic en reproducir en ese estado, se pierde otro subproceso, ya que sigue esperando adquirir el monitor que está en posesión del prepare() que nunca regresa. Pronto, todos mis hilos AsyncTasks se desperdician y, como los uso ampliamente, mi aplicación deja de funcionar.

Así que mi pregunta es: ¿alguien tiene una idea de cómo superar este problema? Estoy pensando seriamente en volver a hacer todo mi trabajo con MediaPlayer, pero necesito saber la mejor manera de manejar situaciones como esta de antemano.

Respuesta

3

En su lugar, debe usar prepareAsync(). Y no necesitará AsyncTask solo por el bien de la preparación MediaPlayer.

+0

Pensé en eso, pero no estoy seguro de que resolvería el problema. Los documentos de MediaPlayer dicen que "es importante tener en cuenta que el estado de Preparación es un estado transitorio, y el comportamiento de llamar a cualquier método con efecto secundario mientras un objeto MediaPlayer está en el estado Preparación no está definido". Entonces el problema se mantendría, yo llamaría a release() en el estado de 'preparación' y el resultado no está definido. Sospecho que dentro de mi synchronous prepare(), el MediaPlayer cambia a este estado de 'preparación' y eso da como resultado los errores que estoy recibiendo. – hgm

+0

Según el diagrama y la tabla de estados, puede llamar a 'release()' desde cualquier estado. Darle una oportunidad. Creo que solo estás enfrentando errores relacionados con subprocesos múltiples. Y excluir múltiples hilos debería "arreglarlo". – inazaruk

+0

Dice que puedo llamar a release() en cualquier momento, pero aun así estoy enfrentando el problema de estar atascado si llamo esto dentro de prepare(). Esto no debería suceder y es un error en Android IMO. – hgm

0

El problema con el uso de Asyntasks o prepareAsync() es que no se activa automáticamente el estado MediaPlayer's. Creerías que sí, pero por alguna razón no es así. Me quedé atrapado con el mismo problema durante unos días hasta que alguien me sugirió que implementara el OnPreparedListener, y lo usara para usar mi MediaPlayer.

Agregarlo es bastante simple.

En primer lugar, poner en práctica el oyente en su clase: (. Estoy usando esto para un servicio tan será la suya ligeramente diferente)

public class MusicService extends Service implements OnPreparedListener 

A continuación, en el juego/declaración de preparar, utilizar prepareAsync, y establecer el oyente.

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

A continuación, agregue el método onPrepared y añadir el código de partida:

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

Que debe hacer el truco.

0

Gracias por todas las respuestas, pero he solucionado esto no usando el prepareAsync() como se mencionó en las respuestas anteriores. Creo que si un método sincrónico de preparación estuviera disponible, debería haber una forma de hacerlo funcionar correctamente.

La corrección, aunque no elegante IMO, es simple: evite llamar a release() dentro de un prepare(). Aunque el diagrama de estado en los documentos de Media Player dice que la versión() se puede llamar en cualquier estado, la experimentación demostró que llamarla dentro de un estado de preparación() (presumiblemente en el estado "Preparación") bloqueará su aplicación. Así que agregué un cheque para ver si el MP está en este estado y ahora estoy usando el lanzamiento de llamada() si es así. Necesitaba agregar un booleano en mi código para verificar esto, ya que no hay un método isPreparing() en el MP.

Estoy seguro de que esto no debería suceder y es un error en el propio Android.Como se puede ver en los comentarios, hay una contradicción en los documentos: dice que se puede llamar a release() en cualquier estado, pero también dice que llamar a cualquier comando de cambio de estado mientras se está en el estado Preparación tiene un resultado indefinido. Espero que esto ayude a otras personas.

0

He resuelto este problema en mi aplicación como esta:

Crear objetos de las AsyncTasks (para que pueda comprobar si están en curso):

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

Crear una AsyncTask para preparar la llamada :

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() { 

    } 

} 

Ahora su código de preparar:

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); 

Y, por último, cuando tenga que matar al mediaPlayer, comprobará el objeto setSource para ver si se está preparando antes de lanzarlo. Si se prepara, se le cancela la AsyncTask y en el AsyncTask onCancelled, podrás restablecer y suelta el objeto:

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

y este es mi releaseMP AsyncTask (que simplemente se reinicia y libera el objeto):

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; 
    } 

} 
Cuestiones relacionadas