Not able to achieve Gapless audio looping so far on Android Not able to achieve Gapless audio looping so far on Android android android

Not able to achieve Gapless audio looping so far on Android


From the test that I have done, this solution works fine, over 150 loops with a 13 seconds 160 kbps MP3 without any problem:

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

To use LoopMediaPlayer you can just call:

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


Ugly proof-of-concept code, but you'll get the idea:

// Will need this in the callbacksfinal AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.sample);// Build and start first playerfinal MediaPlayer player1 = MediaPlayer.create(this, R.raw.sample);player1.start();// Ready second playerfinal 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

This worked for me on an API 19 device and a 5-second 128 kbps MP3. No gaps in the loop.


At least as of KitKat, Mattia Maestrini's Answer (to this question) is the only solution I've found that allows gapless looping of a large (> 1Mb uncompressed) audio sample. I've tried:

By simply including Maestrini's LoopMediaPlayer class in my project and then replacing my MediaPlayer.create() calls with LoopMediaPlayer.create() calls, I can ensure my .OGG sample is looped seamlessly. LoopMediaPlayer is therefore a commendably practical and transparent solution.

But this transparency begs the question: once I swap my MediaPlayer calls for LoopMediaPlayer calls, how does my instance call MediaPlayer methods such as .isPlaying, .pause or .setVolume? Below is my solution for this issue. Possibly it can be improved upon by someone more Java-savvy than myself (and I welcome their input), but so far I've found this a reliable solution.

The only changes I make to Maestrini's class (aside from some tweaks recommended by Lint) are as marked at the end of the code below; the rest I include for context. My addition is to implement several methods of MediaPlayer within LoopMediaPlayer by calling them on mCurrentPlayer.

Caveat: while I implement several useful methods of MediaPlayer below, I do not implement all of them. So if you expect for example to call .attachAuxEffect you will need to add this yourself as a method to LoopMediaPlayer along the lines of what I have added. Be sure to replicate the original interfaces of these methods (i.e., Parameters, Throws, and Returns):

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