/ / Pas capable de réaliser une boucle audio Gapless sur Android - Android, Android-audiomanager

Pas capable de réaliser une boucle audio Gapless jusqu'à présent sur Android

J’ai essayé presque toutes les méthodes, mais j’ai échoué à réaliser une lecture audio sans intervalle entre la mise en boucle d’une piste unique d’une durée de 10 à 15 secondes.

Les étapes que j’ai essayées et qui ont échoué:

  1. Différents formats de fichiers audio .mp3 .wav .ogg en utilisant setLooping(true):

    MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1);
    mp1.setLooping(true);
    mp1.start();
    
  2. Créer deux lecteurs multimédias et les boucler l'un après l'autre à l'aide de setOnCompletionListenermême n'a pas réussi à boucler sans lacunes.

  3. En utilisant setNextMediaPlayer(nextmp) Certains fonctionnent, mais seulement deux boucles sont possibles. Nous devons nous préparer et recommencer après l’achèvement des deux boucles précédentes.

    mp1.start();
    mp1.setNextMediaPlayer(mp2);
    
  4. Mettre à jour: Résultat de la réponse de @Jeff Mixon: Mediaplayer en boucle s'arrête avec une erreur Android. Jeff Mixon fonctionne bien mais seulement pour 10 ou 20boucles après cela, en raison d’un problème de récupération de place, Mediaplayers cesse immédiatement de laisser les journaux indiqués ci-dessous. Je suis vraiment coincé ici pendant 2 ans. Merci d’avance.

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

Réponses:

28 pour la réponse № 1

D'après le test que j'ai effectué, cette solution fonctionne parfaitement, plus de 150 boucles avec un MP3 de 160 kbps à 13 secondes sans aucun problème:

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));
}
};
}

Utiliser LoopMediaPlayer vous pouvez simplement appeler:

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

12 pour la réponse № 2

Le code de la preuve de concept laide, mais vous aurez l’idée:

// 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

Cela a fonctionné pour moi sur un périphérique API 19 et un fichier MP3 à 128 kbps de 5 secondes. Pas de lacunes dans la boucle.


3 pour la réponse № 3

Au moins à partir de KitKat, Mattia Maestrini "s Answer (à cette question) est le seule solution que j’ai trouvée qui permette la boucle sans interruption d’un échantillon audio volumineux (> 1 Mo non compressé). J'ai essayé:

En incluant simplement les "Maestrini" LoopMediaPlayer classe dans mon projet, puis remplacer mon MediaPlayer.create() appelle avec LoopMediaPlayer.create() appels, je peux m'assurer que mon échantillon .OGG est bouclé de manière transparente. LoopMediaPlayer est donc une solution louable, pratique et transparente.

Mais cette transparence soulève la question: une fois que je permute mon MediaPlayer appels pour LoopMediaPlayer appels, comment mon instance appelle-t-elle MediaPlayer des méthodes telles que.isPlaying, .pause ou .setVolume? Ci-dessous ma solution à ce problème. Il est possible que quelqu'un qui maîtrise davantage le langage Java que moi (et je salue leur contribution) puisse améliorer la situation, mais jusqu'à présent, j'ai trouvé cette solution fiable.

Les seuls changements que j’apporte à la classe de Maestrini(à part quelques ajustements recommandés par Lint) sont marqués à la fin du code ci-dessous; le reste j'inclus pour le contexte. Mon ajout est de mettre en œuvre plusieurs méthodes de MediaPlayer dans LoopMediaPlayer en les appelant mCurrentPlayer.

Caveat: alors que je mets en œuvre plusieurs méthodes utiles de MediaPlayer au dessous de, Je ne les implémente pas tous. Donc, si vous attendez par exemple à appeler .attachAuxEffect vous devrez l’ajouter vous-même comme méthode de LoopMediaPlayer dans le sens de ce que j'ai ajouté. Assurez-vous de répliquer les interfaces d'origine de ces méthodes (c.-à-d. Paramètres, jets et retours):

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();
}
}

2 pour la réponse № 4

Quelque chose comme ça devrait marcher. Conservez deux copies du même fichier dans le répertoire res.raw. Veuillez noter qu'il s'agit simplement d'un POC et non d'un code optimisé. Je viens de tester cela et il fonctionne comme prévu. Laissez-moi savoir ce que vous pensez.

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();
}
}

2 pour la réponse № 5

Je vous suggère d'utiliser SoundPool API au lieu de MediaPlayer.

De la documentation officielle:

La classe SoundPool gère et lit les ressources audio pour applications.

...

Les sons peuvent être mis en boucle en définissant une valeur non nulleboucle valeur. Une valeur de -1 provoque la boucle pour le son pour toujours. Dans ce cas, l'application doit explicitement appeler la fonction stop () pour arrêter le du son. Toute autre valeur non nulle entraînera la répétition du son nombre spécifié de fois, p. ex. une valeur de 3 provoque la lecture du son un total de 4 fois.

...

Regarde ici pour un exemple pratique d'utilisation SoundPool.


1 pour la réponse № 6

Pour une raison quelconque, j'ai trouvé que mon "En complément" L'événement a toujours tiré une fraction de seconde en retard lorsque vous essayez de boucler un fichier OGG de 8 secondes. Si vous rencontrez ce type de retard, essayez ce qui suit.

Il est possible de faire la queue de force une "nextMediaPlayer" comme recommandé dans les solutions précédentes, en postant simplement un Runnable retardé à un gestionnaire pour vos MediaPlayers et éviter de boucler dans onCompletion Événement tout à fait.

Cela fonctionne parfaitement pour moi avec mon OGG de 8 secondes à 160 kbps, API 16 min.

Quelque part dans votre activité / service, créez 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();
}
}`

...commencer le fil, déclarer et configurez vos 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;
}

...créer un Méthode d'échange vos 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.");
}

...créer Runnables poster sur votre fil ...

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

private Runnable timedQueue = new Runnable(){
public void run(){
swapActivePlayers();
}
};

Dans le service onStartCommand () de votre service ou quelque part dans votre activité, démarrez le MediaPlayer ...

...
SongLooperHandler.post(startMP);
...