Not able to achieve Gapless audio looping so far on Android

后端 未结 9 1860
走了就别回头了
走了就别回头了 2020-11-28 06:05

I have tried almost every method but I\'ve failed to achieve gapless audio playback between looping a single track with a duration of 10-15 seconds.

Steps I\'ve trie

相关标签:
9条回答
  • 2020-11-28 06:32

    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);
    
    0 讨论(0)
  • 2020-11-28 06:33

    I suggest you to use SoundPool API instead of MediaPlayer.

    From the official documentation:

    The SoundPool class manages and plays audio resources for applications.

    ...

    Sounds can be looped by setting a non-zero loop value. A value of -1 causes the sound to loop forever. In this case, the application must explicitly call the stop() function to stop the sound. Any other non-zero value will cause the sound to repeat the specified number of times, e.g. a value of 3 causes the sound to play a total of 4 times.

    ...

    Take a look here for a practical example of how to use SoundPool.

    0 讨论(0)
  • 2020-11-28 06:40

    Ugly proof-of-concept code, but you'll get the 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
    

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

    0 讨论(0)
  • 2020-11-28 06:46

    In using Mattia Maestrini's answer, I was able to get the audio looping the way I wanted but, since I was using this for Android Auto, discovered that the audio only played over my phones speakers instead of my car speakers. I eventually found this answer which points out a bug which makes it important in this context to use the new MediaPlayer() constructor with the setDataSource method. I was already using Uris in my code so I used that variant, so I'm not 100% sure how important that is, I would assume any of the other setDataSource variants would be sufficient if it matters for your code.

    Here's what ultimately ended up working for me:

    public class LoopMediaPlayer extends MediaPlayer {
        private static final String TAG = LoopMediaPlayer.class.getSimpleName();
    
        private Context mContext = null;
        private Uri mMediaUri = null;
        private int mCounter = 1;
    
        private MediaPlayer mCurrentPlayer = null;
        private MediaPlayer mNextPlayer = null;
    
        private Float mLeftVolume;
        private Float mRightVolume;
    
        public static LoopMediaPlayer create(Context context, Uri mediaUri) {
            try {
                return new LoopMediaPlayer(context, mediaUri);
            }
            catch (Exception e) {
                throw new RuntimeException("Unable to create media player", e);
            }
        }
    
        private LoopMediaPlayer(Context context, Uri mediaUri) throws IOException {
            mContext = context;
            mMediaUri = mediaUri;
    
            mCurrentPlayer = new MediaPlayer();
            mCurrentPlayer.setDataSource(mContext, mMediaUri);
            mCurrentPlayer.prepare();
    
            createNextMediaPlayer();
        }
    
        private void createNextMediaPlayer() {
            try {
                mNextPlayer = new MediaPlayer();
                mNextPlayer.setDataSource(mContext, mMediaUri);
                if (mLeftVolume != null && mRightVolume != null) {
                    mNextPlayer.setVolume(mLeftVolume, mRightVolume);
                }
                mNextPlayer.prepare();
    
                mCurrentPlayer.setNextMediaPlayer(mNextPlayer);
                mCurrentPlayer.setOnCompletionListener(onCompletionListener);
            }
            catch (Exception e) {
                Log.e(TAG, "Problem creating next media player", e);
            }
        }
    
        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));
            }
        };
    
        @Override
        public void prepare() throws IllegalStateException {
            // no-op, internal media-players are prepared when they are created.
        }
    
        @Override
        public boolean isPlaying() throws IllegalStateException {
            return mCurrentPlayer.isPlaying();
        }
    
        @Override
        public void setVolume(float leftVolume, float rightVolume) {
            mCurrentPlayer.setVolume(leftVolume, rightVolume);
            mNextPlayer.setVolume(leftVolume, rightVolume);
            mLeftVolume = leftVolume;
            mRightVolume = rightVolume;
        }
    
        @Override
        public void start() throws IllegalStateException {
            mCurrentPlayer.start();
        }
    
        @Override
        public void stop() throws IllegalStateException {
            mCurrentPlayer.stop();
        }
    
        @Override
        public void pause() throws IllegalStateException {
            mCurrentPlayer.pause();
        }
    
        @Override
        public void release() {
            mCurrentPlayer.release();
            mNextPlayer.release();
        }
    
        @Override
        public void reset() {
            mCurrentPlayer.reset();
        }
    }
    
    0 讨论(0)
  • 2020-11-28 06:51

    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:

    • .setLooping(true): gives interloop noise or pause even with perfectly trimmed .WAV sample (published bug in Android);
    • OGG format: frameless format, so better than MP3, but MediaPlayer still emits interloop artifacts; and
    • SoundPool: may work for small sound samples but large samples cause heap size overflow.

    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();
        }
    }
    
    0 讨论(0)
  • 2020-11-28 06:53

    CODE-REad's LoopMediaPlayer example is great, but if you use the new MediaPlayer() method of creating the MediaPlayer (like I do for using File or AssetFileDescriptor datasources) rather than the MediaPlayer.Create() method then you must be careful to

    1. Call the setOnCompletionListener method AFTER .start() or it will not fire.
    2. Fully .prepare() or .prepareAsync() the mNextPlayer before calling .setNextMediaPlayer on the mCurrentPlayer or it will fail to play the mNextPlayer. This means calling .start, setOnCompletionListener, and .setNextMediaPlayer in the onPreparedListener as shown below.

    I have modified his code to use the new MediaPlayer() method to create the player and also added the ability to set datasource from AssetFileDescriptor and a File. I hope this saves someone some time.

    public class LoopMediaPlayer {
    
        private static final String TAG = LoopMediaPlayer.class.getSimpleName();
    
        private Context mContext = null;
        private int mResId   = 0;
        private int mCounter = 1;
        private AssetFileDescriptor mAfd = null;
        private File mFile = null;
    
        private MediaPlayer mCurrentPlayer = null;
        private MediaPlayer mNextPlayer    = null;
    
        public static LoopMediaPlayer create(Context context, int resId) {
            return new LoopMediaPlayer(context, resId);
        }
    
        public LoopMediaPlayer(Context context, File file){
            mContext = context;
            mFile = file;
    
            try {
                mCurrentPlayer = new MediaPlayer();
                mCurrentPlayer.setLooping(false);
                mCurrentPlayer.setDataSource(file.getAbsolutePath());
                mCurrentPlayer.prepareAsync();
                mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mediaPlayer) {
                        mCurrentPlayer.start();
                        mCurrentPlayer.setOnCompletionListener(onCompletionListener);
                        createNextMediaPlayer();
                    }
                });
            } catch (Exception e) {
                Log.e("media", e.getLocalizedMessage());
            }
        }
    
        public LoopMediaPlayer(Context context, AssetFileDescriptor afd){
            mAfd =  afd;
            mContext = context;
    
            try {
                mCurrentPlayer = new MediaPlayer();
                mCurrentPlayer.setLooping(false);
                mCurrentPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
                mCurrentPlayer.prepareAsync();
                mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mediaPlayer) {
                        mCurrentPlayer.start();
                        mCurrentPlayer.setOnCompletionListener(onCompletionListener);
                        createNextMediaPlayer();
                    }
                });
    
            } catch (Exception e) {
                Log.e("media", e.getLocalizedMessage());
            }
        }
    
        private LoopMediaPlayer(Context context, int resId) {
            mContext = context;
            mResId   = resId;
    
            mCurrentPlayer = MediaPlayer.create(mContext, mResId);
            mCurrentPlayer.setLooping(false);
            mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mediaPlayer) {
                    mCurrentPlayer.start();
                    mCurrentPlayer.setOnCompletionListener(onCompletionListener);
                    createNextMediaPlayer();
                }
            });
            mCurrentPlayer.prepareAsync();
        }
    
        private void createNextMediaPlayer() {
            try{
                if(mAfd != null){
                    mNextPlayer = new MediaPlayer();
                    mNextPlayer.setDataSource(mAfd.getFileDescriptor(), mAfd.getStartOffset(), mAfd.getLength());
                    mNextPlayer.prepareAsync();
                    mNextPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            mCurrentPlayer.setNextMediaPlayer(mNextPlayer);
                        }
                    });
                }
                else if(mFile!=null){
                    mNextPlayer = new MediaPlayer();
                    mNextPlayer.setDataSource(mFile.getAbsolutePath());
                    mNextPlayer.prepareAsync();
                    mNextPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            mCurrentPlayer.setNextMediaPlayer(mNextPlayer);
                        }
                    });
                }
                else {
                    mNextPlayer = MediaPlayer.create(mContext, mResId);
                    mNextPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            mCurrentPlayer.setNextMediaPlayer(mNextPlayer);
                        }
                    });
                }
            } catch (Exception e) {
    
            }
        }
    
        private final MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                mediaPlayer.release();
                mCurrentPlayer = mNextPlayer;
                mCurrentPlayer.setOnCompletionListener(onCompletionListener);
                createNextMediaPlayer();
                Log.d("LoopMediaPlayer", 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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题