Recent, în cadrul proiectului campus-book-player, m-am lovit de o problemă frustrantă: o colecție de audiobook-uri vechi refuza categoric să fie redată prin expo-av. Log-urile de sistem arătau erori de tip MEDIA_ERROR_DECODE sau MEDIA_ERROR_MALFORMED. După câteva ore de debugging, am realizat că headerele ID3v2 erau corupte, iar frame-urile VBR aveau un padding care inducea în eroare parserul nativ al Android-ului.
De ce eșuează ExpoPlayer (și MediaCodec)?
expo-av folosește în spate ExoPlayer (sau MediaCodec direct pe Android). Acestea sunt optimizate pentru viteză și eficiență, nu pentru toleranță la erori. Când întâlnesc un header ID3v2 malformat sau un sync word care nu se aliniază perfect, parserul abandonează operațiunea pentru a preveni crash-urile de memorie. Dacă fișierul are “garbage bytes” la început sau frame-uri VBR prost encodate, MediaCodec va refuza pur și simplu să inițializeze track-ul.
Soluția: Parsarea manuală și sincronizarea
Pentru a salva aceste fișiere, trebuie să sărim peste layer-ul de abstractizare și să găsim noi frame-urile MP3. Un fișier MP3 este, în esență, un stream de frame-uri care încep cu un “sync word” de 11 biți setat la 0xFFE0.
Iată cum scanez un buffer pentru a găsi punctul de intrare valid:
// Căutare sync word 0xFFE0 pentru a ignora corupția inițială
private int findSyncWord(ByteBuffer buffer) {
while (buffer.remaining() >= 2) {
int header = buffer.getShort() & 0xFFFF;
if ((header & 0xFFE0) == 0xFFE0) {
return buffer.position() - 2;
}
}
return -1;
}
Pipeline-ul de fallback: Strategia în 3 pași
Nu putem renunța la eficiența expo-av pentru fișierele sănătoase. Am implementat un pipeline în campus-book-player care arată astfel:
- Expo-AV: Încercăm redarea standard. Dacă returnează
onError, trecem la pasul 2. - Native MediaCodec: Dacă fișierul este valid dar are header corupt, forțăm decodarea brută prin
AudioTrack. - Software Decoder (mpg123): Dacă totul eșuează, folosim
libmpg123viaexpo-modulepentru decodare software.
Implementare nativă cu Expo Module
Pentru a integra mpg123 în React Native, am creat un modul nativ care extrage datele PCM și le trimite către un buffer AudioTrack:
// Inițializare AudioTrack după parsarea primului frame valid
val audioTrack = AudioTrack.Builder()
.setAudioFormat(AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build())
.build()
audioTrack.play()
// Aici trimitem frame-urile decodate manual prin mpg123_decode
Benchmark: Nativ vs Software
| Metodă | Consum CPU | Latență | Stabilitate |
|---|---|---|---|
| Expo-AV (MediaCodec) | Foarte mic | Mică | Scăzută (pe fișiere corupte) |
| Manual MediaCodec | Mediu | Medie | Ridicată |
| mpg123 (Software) | Ridicat | Mare | Maximă |
Concluzie
Deși mpg123 consumă mai multă baterie, este “colacul de salvare” pentru fișierele vechi. Strategia mea a fost să rescriu headerele ID3v2 în memorie imediat ce am găsit primul frame valid, făcând fișierul compatibil cu player-ele standard pentru redările ulterioare. Dacă lucrezi la o aplicație de tip audio-player, nu te baza doar pe framework-urile de nivel înalt; un pic de control la nivel de byte îți va salva utilizatorii de la erori frustrante.

