Autore Topic: [Android] Classe MediaPlayer e gestione errori  (Letto 1560 volte)

Offline TheKaneB

  • Human Debugger
  • *****
  • Post: 5292
  • Karma: +20/-23
    • Mostra profilo
    • http://www.antoniobarba.org
[Android] Classe MediaPlayer e gestione errori
« il: 13 Febbraio 2012, 17:03:35 »
Quasi tutte le applicazioni che ho sviluppato sono dotate di un player custom, che serve per erogare vari tipi di pubblicità inframmezzata al video (banner, videoclip, ecc...).

Android fornisce una classe MediaPlayer per lo scopo, che si occupa di decodificare i flussi audio/video e si aggancia ad una SurfaceView (cioè un pezzo di schermo) a tua scelta. Nel mio caso si aggancia ad una classe chiamata SimplePlayer che deriva da RelativeLayout.

Questa classe MediaPlayer offre una modalità di funzionamento asincrono, cioè effettua il processo su un thread in background e si aggancia ad un Listener per notificare vari tipi di eventi.

Tra questi c'è l'evento onError che sistematicamente sputa fuori error code non documentati, così vado un po' a naso e tramite ricerche varie ed esperimenti assortiti sono riuscito a scrivere questo sistema sofisticatissimo che risolve brillantemente il 90% degli errori. Rimane scoperto solo il caso in cui muore il server o cade la connessione. In quel caso semplicemente l'applicazione da errore e l'utente può riprovare a cliccare sul video una seconda volta, se gli va.

Ecco il codice di gestione degli errori che ho sapientemente implementato:
Codice: [Seleziona]
@Override
public boolean onError(MediaPlayer mp, int what, int extra)
{
    if (what == -38) // meh!
        return true;

    ///showLoader(false);
    //Toast.makeText(mContext, R.string.playerError, 2000).show();
    return false;
}

Non chiedetemi cosa vuol dire quel -38, so solo che è l'errore più comune e inutile, infatti basta ignorarlo e il video funziona tranquillamente.  :lol:

Il secondo errore riguarda un bug di Android nelle versioni 2.x, che è stato invece fixato da 3.x in poi. In sostanza la gestione dei thread è cambiata, ma le API sono le stesse (quindi c'è una chiara violazione del "contratto d'uso delle API").
In parole povere, su Android 2.x i thread vanno tutti in parallelo e si creano race conditions come se piovesse (non nel mio codice, che è accuratamente sincronizzato, ma nelle librerie interne di Android).

Capita quindi che il MediaPlayer lanci l'evento onPrepared ("we cumpà, sono pronto") e l'evento onVideoSizeChanged("la dimensione del video è X,Y") in ordine totalmente casuale. A causa di ciò, se lancio il programma con un bel breakpoint su onPrepared, il video si vede correttamente, perchè onVideoSizeChanged fa in tempo a venir chiamata. Se invece lo lancio senza breakpoint, a volte il video si vede e a volte rimane lo schermo nero e si sente solo l'audio.
Questo accade perchè il codec parte per i cazzi suoi senza che ci sia alcun display dove outputtare il video decodificato.
Su Android 3.x invece le funzioni sono chiamate in ordine, in modo deterministico, e funziona sempre.
Come risolvere la questione?

Se provo a sincronizzare a mano le due chiamate ottengo un bel deadlock (non so il motivo, bisognerebbe leggersi i sorgenti di MediaPlayer), quindi mi rimane solo la soluzione della nonna: un bel thread rescheduling!

Codice: [Seleziona]
// Forces threads rescheduling (prevents a race condition in Android 2.x)
try
{
    Thread.sleep(0);
}
catch (InterruptedException e)
{
    e.printStackTrace();
}

Con questo bel pezzo di codice il thread di onPrepare viene rificcato in fondo al ThreadPoolExecutor (che opera uno scheduling di tipo RoundRobin) e così l'altro thread viene correttamente completato e il video compare correttamente a schermo.
Questa perla di arte moderna non inficia il funzionamento su Android 3.x che continua a funzionare normalmente.

Cosa curiosa: se compilo il progetto con l'SDK per Android 1.5 la race condition scompare (però saltano in aria molte librerie che mi servono per cui ho bisogno almeno della versione 2.1), quindi sto cesso di bug l'hanno creato sul ramo 1.6 / 2.x e poi l'hanno risolto nuovamente su 3.x.

Toh!! Guardate cosa ho trovato scavando nel doc di Android, è sempre un piacere trovare queste differenze FONDAMENTALI nascoste dentro un paragrafetto:
Citazione
public final AsyncTask<Params, Progress, Result> execute (Params... params)

Since: API Level 3
Executes the task with the specified parameters. The task returns itself (this) so that the caller can keep a reference to it.
Note: this function schedules the task on a queue for a single background thread or pool of threads depending on the platform version. When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. After HONEYCOMB, it is planned to change this back to a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the executeOnExecutor(Executor, Params...) version of this method with THREAD_POOL_EXECUTOR; however, see commentary there for warnings on its use.
Ecco spiegata la race condition... quindi il problema è che nemmeno tra di loro (gli ingegneri di google) riescono a mettersi d'accordo su come usare sti cacchio di AsyncTask ("thread for dummies"), e hanno implementato una classe FONDAMENTALE come MediaPlayer basandosi su una libreria di threading bacata e implementata a cacchio, senza nemmeno preoccuparsi delle conseguenze che può comportare il cambiamento della semantica di un metodo!
Quindi mi aspetto che TUTTI i componenti di Android che facciano uso di AsyncTask presentino potenziali problemi di race conditions su 1.6 / 2.x mentre funzionino bene su 1.5 / 3.x o viceversa.

Vomito.

Buona prosecuzione di giornata, vado a debuggare qualcos'altro... :lol:
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Offline cdimauro

  • Human Debugger
  • *****
  • Post: 4291
  • Karma: +7/-95
    • Mostra profilo
Re: [Android] Classe MediaPlayer e gestione errori
« Risposta #1 il: 13 Febbraio 2012, 18:18:57 »
Me lo segno, perché quello che scrivi per me è oro... o cacca, a seconda dei punti di vista. :lol:

Offline Allanon

  • Administrator
  • Synthetic Voodoo
  • *****
  • Post: 3498
  • Karma: +17/-4
    • Mostra profilo
    • http://www.a-mc.biz
Re: [Android] Classe MediaPlayer e gestione errori
« Risposta #2 il: 13 Febbraio 2012, 19:13:43 »
Non sono sicuro se sono più allibito o pietrificato da quanto ho letto...
« Ultima modifica: 01 Gennaio 1970, 02:00:00 da Guest »

Tags: