No es capaz de lograr un loop de audio Gapless hasta ahora en Android

He intentado casi cada método pero no he podido lograr la reproducción sin audio gapless entre el lazo de una sola pista con una duración de 10-15 segundos.

Pasos que he intentado y no he podido:

  1. Diferentes formatos de archivo de audio .mp3 .wav .ogg usando setLooping(true) :

     MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1); mp1.setLooping(true); mp1.start(); 
  2. Creando dos mediaplayers y haciendo bucle uno tras otro utilizando setOnCompletionListener mismo falló en realizar bucle sin espacios.

  3. Usando setNextMediaPlayer(nextmp) algunos como funciona, pero sólo dos bucles es posible. Tenemos que preparar y comenzar de nuevo después de la finalización de los dos bucles anteriores.

     mp1.start(); mp1.setNextMediaPlayer(mp2); 
  4. Actualización: Resultado de @Jeff Mixon respuesta: Mediaplayer looping se detiene con un error de Android . Jeff Mixon funciona bien, pero sólo para 10 o 20 ciclos después de eso, debido a un problema de recolección de basura el Mediaplayers deja inmediatamente dejando los registros como se muestra a continuación. Estoy realmente atrapado aquí por dos años. Gracias por adelantado.

     E/MediaPlayer(24311): error (1, -38) E/MediaPlayer(23256): Error(1,-1007) E/MediaPlayer(23546): Error (1,-2147483648) 

De la prueba que he hecho, esta solución funciona bien, más de 150 bucles con un 13 segundos de 160 kbps MP3 sin ningún problema:

 public class LoopMediaPlayer { public static final String TAG = LoopMediaPlayer.class.getSimpleName(); private Context mContext = null; private int mResId = 0; private int mCounter = 1; private MediaPlayer mCurrentPlayer = null; private MediaPlayer mNextPlayer = null; public static LoopMediaPlayer create(Context context, int resId) { return new LoopMediaPlayer(context, resId); } private LoopMediaPlayer(Context context, int resId) { mContext = context; mResId = resId; mCurrentPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mCurrentPlayer.start(); } }); createNextMediaPlayer(); } private void createNextMediaPlayer() { mNextPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setNextMediaPlayer(mNextPlayer); mCurrentPlayer.setOnCompletionListener(onCompletionListener); } private MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.release(); mCurrentPlayer = mNextPlayer; createNextMediaPlayer(); Log.d(TAG, String.format("Loop #%d", ++mCounter)); } }; } 

Para usar LoopMediaPlayer sólo puede llamar a:

 LoopMediaPlayer.create(context, R.raw.sample); 

Código feo de prueba de concepto, pero obtendrás la idea:

 // Will need this in the callbacks final AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.sample); // Build and start first player final MediaPlayer player1 = MediaPlayer.create(this, R.raw.sample); player1.start(); // Ready second player final MediaPlayer player2 = MediaPlayer.create(this, R.raw.sample); player1.setNextMediaPlayer(player2); player1.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { // When player1 completes, we reset it, and set up player2 to go back to player1 when it's done mediaPlayer.reset(); try { mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); mediaPlayer.prepare(); } catch (Exception e) { e.printStackTrace(); } player2.setNextMediaPlayer(player1); } }); player2.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { // Likewise, when player2 completes, we reset it and tell it player1 to user player2 after it's finished again mediaPlayer.reset(); try { mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); mediaPlayer.prepare(); } catch (Exception e) { e.printStackTrace(); } player1.setNextMediaPlayer(player2); } }); // This loop repeats itself endlessly in this fashion without gaps 

Esto funcionó para mí en un dispositivo API 19 y un MP3 de 128 kbps de 5 segundos. No hay vacíos en el bucle.

Algo así debería funcionar. Guarde dos copias del mismo archivo en el directorio res.raw. Tenga en cuenta que esto es sólo un POC y no un código optimizado. Acabo de probar esto y funciona como estaba previsto. Déjame saber lo que piensas.

 public class MainActivity extends Activity { MediaPlayer mp1; MediaPlayer mp2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mp1 = MediaPlayer.create(MainActivity.this, R.raw.demo); mp2 = MediaPlayer.create(MainActivity.this, R.raw.demo2); mp1.start(); Thread thread = new Thread(new Runnable() { @Override public void run() { int duration = mp1.getDuration(); while (mp1.isPlaying() || mp2.isPlaying()) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } duration = duration - 100; if (duration < 1000) { if (mp1.isPlaying()) { mp2.start(); mp1.reset(); mp1 = MediaPlayer.create(MainActivity.this, R.raw.demo); duration = mp2.getDuration(); } else { mp1.start(); mp2.reset(); mp2 = MediaPlayer.create(MainActivity.this, R.raw.demo2); duration = mp1.getDuration(); } } } } }); thread.start(); } } 

Le sugiero que utilice SoundPool API en lugar de MediaPlayer .

De la documentación oficial:

La clase SoundPool gestiona y reproduce recursos de audio para aplicaciones.

Los sonidos se pueden realizar mediante un valor de bucle distinto de cero. Un valor de -1 hace que el sonido se vuelque para siempre. En este caso, la aplicación debe llamar explícitamente a la función stop () para detener el sonido. Cualquier otro valor distinto de cero hará que el sonido repita el número especificado de veces, por ejemplo, un valor de 3 hace que el sonido reproduzca un total de 4 veces.

Echa un vistazo aquí para un ejemplo práctico de cómo utilizar SoundPool .

Al menos como de KitKat, la respuesta de Mattia Maestrini (a esta pregunta) es la única solución que he encontrado que permite un loop sin gapless de una muestra de audio grande (> 1Mb sin comprimir) . He intentado:

  • .setLooping (true): da interloop ruido o pausa incluso con perfectamente recortado. WAV muestra ( publicado error en Android );
  • Formato de OGG : formato frameless, así que mejor que MP3, pero MediaPlayer todavía emite artefactos del interloop; y
  • SoundPool : puede funcionar para muestras de sonido pequeñas, pero las muestras grandes causan el desbordamiento del tamaño del montón .

Simplemente incluyendo la clase LoopMediaPlayer de Maestrini en mi proyecto y luego reemplazar mis llamadas de MediaPlayer.create() con llamadas LoopMediaPlayer.create() , puedo asegurar que mi muestra .OGG esté en bucle sin problemas. LoopMediaPlayer es, por lo tanto, una solución LoopMediaPlayer práctica y transparente.

Pero esta transparencia plantea la pregunta: una vez que cambiar mi MediaPlayer llamadas para llamadas LoopMediaPlayer , ¿cómo mi instancia llamada MediaPlayer métodos como. isPlaying , .pause o .setVolume ? A continuación se muestra mi solución para este problema . Posiblemente puede ser mejorado por alguien más experto en Java que yo (y acojo con beneplácito su opinión), pero hasta ahora he encontrado esta solución fiable.

Los únicos cambios que hago en la clase de Maestrini (aparte de algunos ajustes recomendados por Lint) están marcados al final del código de abajo; El resto lo incluyo para el contexto. Mi adición es implementar varios métodos de MediaPlayer en LoopMediaPlayer llamándolos en mCurrentPlayer .

Advertencia: mientras implemento varios métodos útiles de MediaPlayer continuación, no implementar todos ellos. Así que si usted espera por ejemplo para llamar a .attachAuxEffect tendrá que agregar esto a ti mismo como un método para LoopMediaPlayer a LoopMediaPlayer largo de las líneas de lo que he añadido. Asegúrese de replicar las interfaces originales de estos métodos (es decir, Parámetros, Tiros y Devoluciones):

 public class LoopMediaPlayer { private static final String TAG = LoopMediaPlayer.class.getSimpleName(); private Context mContext = null; private int mResId = 0; private int mCounter = 1; private MediaPlayer mCurrentPlayer = null; private MediaPlayer mNextPlayer = null; public static LoopMediaPlayer create(Context context, int resId) { return new LoopMediaPlayer(context, resId); } private LoopMediaPlayer(Context context, int resId) { mContext = context; mResId = resId; mCurrentPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mCurrentPlayer.start(); } }); createNextMediaPlayer(); } private void createNextMediaPlayer() { mNextPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setNextMediaPlayer(mNextPlayer); mCurrentPlayer.setOnCompletionListener(onCompletionListener); } private final MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.release(); mCurrentPlayer = mNextPlayer; createNextMediaPlayer(); Log.d(TAG, String.format("Loop #%d", ++mCounter)); } }; // code-read additions: public boolean isPlaying() throws IllegalStateException { return mCurrentPlayer.isPlaying(); } public void setVolume(float leftVolume, float rightVolume) { mCurrentPlayer.setVolume(leftVolume, rightVolume); } public void start() throws IllegalStateException { mCurrentPlayer.start(); } public void stop() throws IllegalStateException { mCurrentPlayer.stop(); } public void pause() throws IllegalStateException { mCurrentPlayer.pause(); } public void release() { mCurrentPlayer.release(); mNextPlayer.release(); } public void reset() { mCurrentPlayer.reset(); } } 

Por alguna razón, encontré que mi evento "OnCompletion" siempre estaba disparando una fracción de segundo tarde al intentar realizar un bucle en un archivo OGG de 8 segundos. Para cualquiera que tenga este tipo de retraso, intente lo siguiente.

Es posible hacer cola forzosa de un "nextMediaPlayer" como recomendamos en soluciones anteriores, simplemente publicando un Runnable retrasado a un Handler para tus MediaPlayers y evitando el bucle en onCompletion Event por completo.

Esto funciona perfectamente para mí con mi 160kbps 8-segundo OGG, min API 16.

En algún lugar de su Actividad / Servicio, cree un HandlerThread & Handler

 private HandlerThread SongLooperThread = new HandlerThread("SongLooperThread"); private Handler SongLooperHandler; public void startSongLooperThread(){ SongLooperThread.start(); Looper looper = SongLooperThread.getLooper(); SongLooperHandler = new Handler(looper){ @Override public void handleMessage(Message msg){ //do whatever... } } } public void stopSongLooperThread(){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){ SongLooperThread.quit(); } else { SongLooperThread.quitSafely(); } }` 

iniciar el hilo , declarar y configurar sus MediaPlayers

 @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); startSongLooperThread(); activeSongResID = R.raw.some_loop; activeMP = MediaPlayer.create(getApplicationContext(), activeSongResID); activeSongMilliseconds = activeMP.getDuration(); queuedMP = MediaPlayer.create(getApplicationContext(),activeSongResID); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); stopSongLooperThread(); activeMP.release(); queuedMP.release(); activeMP = null; queuedMP = null; } 

… crear un método para intercambiar tus MediaPlayers …

 private void swapActivePlayers(){ Log.v("SongLooperService","MediaPlayer swap started...."); queuedMP.start(); //Immediately get the Duration of the current track, then queue the next swap. activeSongMilliseconds = queuedMP.getDuration(); SongLooperHandler.postDelayed(timedQueue,activeSongMilliseconds); Log.v("SongLooperService","Next call queued..."); activeMP.release(); //Swap your active and queued MPs... Log.v("SongLooperService","MediaPlayers swapping...."); MediaPlayer temp = activeMP; activeMP = queuedMP; queuedMP = temp; //Prepare your now invalid queuedMP... queuedMP = MediaPlayer.create(getApplicationContext(),activeSongResID); Log.v("SongLooperService","MediaPlayer swapped."); } 

… crear Runnables para publicar en tu hilo …

 private Runnable startMP = new Runnable(){ public void run(){ activeMP.start(); SongLooperHandler.postDelayed(timedQueue,activeSongMilliseconds); } }; private Runnable timedQueue = new Runnable(){ public void run(){ swapActivePlayers(); } }; 

En el servicio onStartCommand () o en algún lugar de su actividad, inicie el MediaPlayer …

 ... SongLooperHandler.post(startMP); ... 
  • Detección de auriculares conectados - isWiredHeadsetOn () sólo funciona con auriculares
  • Controlar las teclas de volumen
  • ¿Por qué AudioManager.isStreamMuted no está disponible?
  • La transmisión de audio se detiene después de 5 segundos
  • ¿Cuál es la diferencia entre los tipos de flujo de AudioManager a bajo nivel?
  • AudioManager.isWiredHeadsetOn () no funciona en android
  • Recuperación del nombre de la aplicación que tiene cambio de enfoque de audio
  • Cómo proporcionar vibraciones personalizadas en las llamadas entrantes específicas
  • Android AudioManager.setMode (MODE_NORMAL) falla
  • Volumen de auriculares de enlace de Android al volumen principal
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.