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:
- Android 5.0+ AudioManager setMode no funciona
- Comprobar constantemente el cambio de volumen en los servicios de Android
- SetStreamMute nunca interrumpe
- Android - Cómo obtener la lista de parámetros de AudioManager / AudioSystem
- ¿Qué es el enfoque de audio en la clase Android AudioManager?
-
Diferentes formatos de archivo de audio
.mp3
.wav
.ogg
usandosetLooping(true)
:MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1); mp1.setLooping(true); mp1.start();
-
Creando dos mediaplayers y haciendo bucle uno tras otro utilizando
setOnCompletionListener
mismo falló en realizar bucle sin espacios. -
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);
-
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)
- Android: Get Notified cuando el modo actual dentro de AudioManager se cambia
- Android recibe difusión en el cambio de enfoque de audio
- Buscando un ejemplo de la nueva API de Android setMediaButtonReceiver
- ¿Existe alguna función equivalente de requestAudioFocus en Android API nivel 7?
- La pista de audio no funciona a través de Bluetooth en Samsung S6
- SetVolumeControlStream no funciona en ICS
- ¿Cómo reproducir audio a través del altavoz incluso cuando el auricular está conectado?
- Android - ¿es posible saber qué aplicación tiene audiofocus?
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); ...
- ¿Por qué INSERTs y UPDATEs son más lentos en Android 4.0?
- Realm y Comportamiento de incremento automático (Android)