2011-12-14 8 views
6

Sto affrontando un problema serio con Media Player (MP) bloccato nel metodo prepare(). La mia app viene eseguita in un AsyncTask per evitare il blocco dell'interfaccia utente, poiché le origini provengono dal Web. Ci sono diversi pulsanti 'play' che l'utente può cliccare in qualsiasi momento, quindi ho aggiunto il prepare() all'interno di un metodo sincronizzato per controllare meglio lo stato di MP. La mia app chiama anche un release() onPause per liberare le risorse utilizzate.Android MediaPlayer bloccato in preparazione()

In questo caso, ho notato che se viene chiamato release() durante la preparazione, prepare() non viene mai restituito e quindi sono bloccato all'interno di un metodo sincronizzato. La cosa peggiore è che il thread AsyncTask si trova in un deadlock e ogni volta che l'utente fa clic su Play in quello stato, viene sprecato un altro thread poiché continua ad attendere l'acquisizione del monitor che è in possesso del sempre-prepare(). Presto tutti i miei thread AsyncTasks sono sprecati e poiché li uso estesamente, la mia app smette di funzionare.

Quindi la mia domanda è: qualcuno ha un'idea di come superare questo problema? Sto seriamente pensando di rifare tutto il mio lavoro con MediaPlayer, ma ho bisogno di sapere il modo migliore per gestire situazioni come questa in anticipo.

risposta

3

Utilizzare invece prepareAsync(). E non avrai bisogno di AsyncTask solo per la preparazione di MediaPlayer.

+0

Ci ho pensato, ma non ho smesso di sicuro che avrebbe risolto il problema. I documenti di MediaPlayer dicono che "È importante notare che lo stato di preparazione è uno stato transitorio e il comportamento di chiamare qualsiasi metodo con effetto collaterale mentre un oggetto MediaPlayer è in stato di preparazione non è definito." Quindi il problema rimarrebbe, chiamerei release() nello stato di "preparazione" e il risultato non è definito. Sospetto che all'interno della mia preparazione sincrona(), MediaPlayer passi a questo stato di "preparazione" e questo comporti gli errori che sto ricevendo. – hgm

+0

Secondo il diagramma e la tabella di stato puoi chiamare 'release()' da qualsiasi stato. Provaci. Penso che tu stia affrontando un bug relativo al multithreading. E l'esclusione di più thread dovrebbe "aggiustarlo". – inazaruk

+0

Si dice che posso chiamare release() in qualsiasi momento, ma anche così sto affrontando il problema di rimanere bloccato se lo chiamo all'interno di un prepare(). Questo non dovrebbe accadere ed è un bug in IMO Android. – hgm

0

Il problema con l'utilizzo di Asyntasks o prepareAsync() è che non viene automaticamente impostato lo stato MediaPlayer's in preparazione. Lo penseresti, ma per qualche ragione no. Sono rimasto bloccato con questo stesso problema per alcuni giorni fino a quando qualcuno mi ha suggerito di implementare lo OnPreparedListener e usarlo per usare il mio MediaPlayer.

Aggiungerlo è abbastanza semplice.

In primo luogo, implementare l'ascoltatore nella classe: (. Sto usando questo per un servizio così il vostro sarà un aspetto leggermente diverso)

public class MusicService extends Service implements OnPreparedListener 

successiva, nel vostro gioco/preparare dichiarazione, utilizzare prepareAsync, e imposta l'ascoltatore.

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

Avanti, aggiungere il metodo onPrepared e aggiungere il codice di partenza:

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

Questo dovrebbe fare il trucco.

0

Grazie per tutte le risposte, ma ho risolto questo problema non utilizzando il prepareAsync() come è stato menzionato nelle risposte precedenti. Penso che se fosse reso disponibile un metodo sincrono di preparazione, ci dovrebbe essere un modo per farlo funzionare correttamente.

La correzione, anche se non elegante, è semplice: evitare di chiamare release() all'interno di prepare(). Anche se il diagramma di stato nei documenti Media Player dice che release() può essere chiamato in qualsiasi stato, la sperimentazione ha dimostrato che chiamarla all'interno di prepare() (presumibilmente nello stato "Preparazione") appenderà la tua app. Quindi ho aggiunto un assegno per vedere se l'MP si trova in questo stato e ora lo sto chiamando avoding(). Avevo bisogno di aggiungere un booleano nel mio codice per verificarlo, dato che non esiste un metodo isPreparing() nell'MP.

Sono sicuro che questo non dovrebbe accadere ed è un bug in Android stesso.Come si può vedere nei commenti, c'è una contraddizione nei documenti: dice che release() può essere chiamato in qualsiasi stato, ma dice anche che chiamare qualsiasi comando di modifica dello stato mentre è in stato di preparazione ha un risultato indefinito. Spero che questo aiuti le altre persone.

0

Ho risolto questo problema nella mia app come questo:

creare oggetti per i AsyncTasks (in modo da poter verificare se sono in corso):

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

Crea un AsyncTask per la chiamata preparare :

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

    } 

} 

Ora il vostro codice di preparare:

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

Infine, quando occorre uccidere MediaPlayer, si controlla l'oggetto setSource per verificare se si sta preparando prima di rilasciarlo. Se si sta preparando, si annulla l'AsyncTask e nel AsyncTask onCancelled, si resetta e rilasciare l'oggetto:

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

E questa è la mia releaseMP AsyncTask (che ha appena ripristina e rilascia l'oggetto):

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

}