MediaPlayer tartamudea al inicio de la reproducción de mp3

He estado teniendo problemas para reproducir un archivo mp3 almacenado en un recurso sin procesar: cuando el archivo comienza a reproducirse, genera quizás un cuarto de segundo de sonido y luego se reinicia. (Sé que esto es básicamente un duplicado del problema descrito aquí , pero la solución ofrecida allí no me ha funcionado). He intentado varias cosas y he progresado un poco en el problema, pero no está totalmente solucionado.

Así es como estoy configurando para reproducir un archivo:

mPlayer.reset(); try { AssetFileDescriptor afd = getResources().openRawResourceFd(mAudioId); if (afd == null) { Toast.makeText(mOwner, "Could not load sound.", Toast.LENGTH_LONG).show(); return; } mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); afd.close(); mPlayer.prepare(); } catch (Exception e) { Log.d(LOG_TAG, "Could not load sound.", e); Toast.makeText(mOwner, "Could not load sound.", Toast.LENGTH_LONG) .show(); } 

Si salgo de la actividad (que llama a mPlayer.release() ) y vuelvo a ella (creando un nuevo MediaPlayer), el tartamudeo generalmente (pero no siempre) se va, siempre que cargue el mismo archivo de sonido. Intenté un par de cosas que no hicieron ninguna diferencia:

  • Cargue el archivo de sonido como un activo en lugar de como un recurso.
  • Cree el MediaPlayer usando MediaPlayer.create(getContext(), mAudioId) y omita las llamadas a setDataSource(...) y prepare() .

Luego noté que LogCat siempre muestra esta línea aproximadamente en el momento en que comienza la reproducción:

 DEBUG/AudioSink(37): bufferCount (4) is too small and increased to 12 

Me hizo preguntarme si la tartamudez se debe a la aparente rebuffering. Esto me llevó a probar algo más:

  • Después de llamar a prepare() , llame a mPlayer.start() e inmediatamente llame a mPlayer.pause() .

Para mi agradable sorpresa, esto tuvo un gran efecto. Gran parte del tartamudeo se ha ido, más ningún sonido (que pueda escuchar) se reproduce en ese momento del proceso.

Sin embargo, todavía tartamudea de vez en cuando cuando llamo mPlayer.start() de verdad. Además, esto parece un gran desafío. ¿Hay alguna manera de matar este problema completamente y limpiamente?

EDITAR Más información; no estoy seguro si está relacionado. Si llamo a pause() durante la reproducción, busco una posición anterior, y llamo a start() nuevamente, escucho un bit corto (~ 1/4 seg) de sonido adicional desde donde estaba en pausa antes de que comience a reproducirse en la nueva posición . Esto parece apuntar a más problemas de almacenamiento en búfer.

Además, los problemas de tartamudeo (y pausa del búfer) aparecen en los emuladores de 1.6 a 3.0.

AFAIK los almacenamientos intermedios que MediaPlayer crea internamente son para almacenar muestras descomprimidas, no para almacenar datos precomprimidos comprimidos. Sospecho que su tartamudeo proviene de la lentitud de E / S ya que carga más datos de MP3 para la descompresión.

Recientemente tuve que resolver un problema similar con la reproducción de video. Gracias a que MediaPlayer no puede reproducir un InputStream arbitrario (la API es extrañamente coja), la solución que obtuve fue escribir un pequeño servidor web en proceso para servir archivos locales (en la tarjeta SD) a través de HTTP. MediaPlayer luego lo carga a través de un URI con el formato http://127.0.0.1:8888/videofilename .

EDITAR:

A continuación se muestra la clase StreamProxy que uso para enviar contenido a una instancia de MediaPlayer. El uso básico es que lo instancias, lo inicias () y configuras tu reproductor de medios con algo como MediaPlayer.setDataSource("http://127.0.0.1:8888/localfilepath");

Debo señalar que es bastante experimental y probablemente no completamente libre de errores. Fue escrito para resolver un problema similar al suyo, a saber, que MediaPlayer no puede reproducir un archivo que también se está descargando. Transmitir un archivo localmente de esta manera funciona alrededor de esa restricción (es decir, tengo un hilo que descarga el archivo mientras StreamProxy lo envía al reproductor de medios).

 import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import android.os.AsyncTask; import android.os.Looper; import android.util.Log; public class StreamProxy implements Runnable { private static final int SERVER_PORT=8888; private Thread thread; private boolean isRunning; private ServerSocket socket; private int port; public StreamProxy() { // Create listening socket try { socket = new ServerSocket(SERVER_PORT, 0, InetAddress.getByAddress(new byte[] {127,0,0,1})); socket.setSoTimeout(5000); port = socket.getLocalPort(); } catch (UnknownHostException e) { // impossible } catch (IOException e) { Log.e(TAG, "IOException initializing server", e); } } public void start() { thread = new Thread(this); thread.start(); } public void stop() { isRunning = false; thread.interrupt(); try { thread.join(5000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { Looper.prepare(); isRunning = true; while (isRunning) { try { Socket client = socket.accept(); if (client == null) { continue; } Log.d(TAG, "client connected"); StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client); if (task.processRequest()) { task.execute(); } } catch (SocketTimeoutException e) { // Do nothing } catch (IOException e) { Log.e(TAG, "Error connecting to client", e); } } Log.d(TAG, "Proxy interrupted. Shutting down."); } private class StreamToMediaPlayerTask extends AsyncTask { String localPath; Socket client; int cbSkip; public StreamToMediaPlayerTask(Socket client) { this.client = client; } public boolean processRequest() { // Read HTTP headers String headers = ""; try { headers = Utils.readTextStreamAvailable(client.getInputStream()); } catch (IOException e) { Log.e(TAG, "Error reading HTTP request header from stream:", e); return false; } // Get the important bits from the headers String[] headerLines = headers.split("\n"); String urlLine = headerLines[0]; if (!urlLine.startsWith("GET ")) { Log.e(TAG, "Only GET is supported"); return false; } urlLine = urlLine.substring(4); int charPos = urlLine.indexOf(' '); if (charPos != -1) { urlLine = urlLine.substring(1, charPos); } localPath = urlLine; // See if there's a "Range:" header for (int i=0 ; i0 && !client.isClosed()) { // See if there's more to send File file = new File(localPath); fc++; int cbSentThisBatch = 0; if (file.exists()) { FileInputStream input = new FileInputStream(file); input.skip(cbSkip); int cbToSendThisBatch = input.available(); while (cbToSendThisBatch > 0) { int cbToRead = Math.min(cbToSendThisBatch, buff.length); int cbRead = input.read(buff, 0, cbToRead); if (cbRead == -1) { break; } cbToSendThisBatch -= cbRead; cbToSend -= cbRead; output.write(buff, 0, cbRead); output.flush(); cbSkip += cbRead; cbSentThisBatch += cbRead; } input.close(); } // If we did nothing this batch, block for a second if (cbSentThisBatch == 0) { Log.d(TAG, "Blocking until more data appears"); Thread.sleep(1000); } } } catch (SocketException socketException) { Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly"); } catch (Exception e) { Log.e(TAG, "Exception thrown from streaming task:"); Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); e.printStackTrace(); } // Cleanup try { if (output != null) { output.close(); } client.close(); } catch (IOException e) { Log.e(TAG, "IOException while cleaning up streaming task:"); Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); e.printStackTrace(); } return 1; } } } 

¿Sería mejor utilizar prepareAsync y responder al setOnPreparedListener ? Según el flujo de trabajo de su actividad, cuando se inicializa por primera vez el MediaPlayer , puede configurar el oyente de preparación y luego llamar a mPlayer.prepareAsync() más tarde una vez que está cargando realmente el recurso, y luego comenzar la reproducción allí. Uso algo similar, aunque para un recurso de transmisión en red:

 MediaPlayer m_player; private ProgressDialog m_progressDialog = null; ... try { if (m_player != null) { m_player.reset(); } else { m_player = new MediaPlayer(); } m_progressDialog = ProgressDialog .show(this, getString(R.string.progress_dialog_please_wait), getString(R.string.progress_dialog_buffering), true); m_player.setOnPreparedListener(this); m_player.setAudioStreamType(AudioManager.STREAM_MUSIC); m_player.setDataSource(someSource); m_player.prepareAsync(); } catch (Exception ex) { } ... public void onPrepared(MediaPlayer mp) { if (m_progressDialog != null && m_progressDialog.isShowing()) { m_progressDialog.dismiss(); } m_player.start(); } 

Obviamente, hay más en una solución completa (manejo de errores, etc.) pero creo que esto debería funcionar como un buen ejemplo para empezar a partir del cual se puede extraer la transmisión.