到目前为止,无法在Android上实现Gapless音频循环

时间:2014-10-09 08:51:41

标签: android android-audiomanager

我几乎尝试了所有方法,但是我无法在循环单个音轨之间实现无间隙音频播放,持续时间为10-15秒。

我尝试过的步骤失败了:

  1. 使用不同的音频文件格式.mp3 .wav .ogg setLooping(true)

    MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1);
    mp1.setLooping(true);
    mp1.start();
    
  2. 创建两个媒体播放器并使用一个接一个地循环 setOnCompletionListener同样无法无间隙地循环。

  3. 使用setNextMediaPlayer(nextmp)一些如何工作,但只有两个循环是可能的。在完成前两个循环后,我们必须准备并重新开始。

    mp1.start();
    mp1.setNextMediaPlayer(mp2);
    
  4. 更新 结果@Jeff Mixon回答: Mediaplayer looping stops with an error Android。 Jeff Mixon工作正常,但之后只有10或20个循环,由于一些垃圾收集问题,Mediaplayers会立即停止离开日志,如下所示。我真的有点困在这里2年了。提前谢谢。

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

9 个答案:

答案 0 :(得分:29)

从我所做的测试来看,这个解决方案运行良好,超过150个循环,13秒160 kbps MP3没有任何问题:

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

要使用LoopMediaPlayer,您只需致电:

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

答案 1 :(得分:13)

丑陋的概念证明代码,但你会得到这个想法:

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

这适用于API 19设备和5秒128 kbps MP3。循环中没有间隙。

答案 2 :(得分:5)

至少从KitKat开始,Mattia Maestrini's Answer(对于这个问题)是 唯一的解决方案我发现允许无间隙循环大型(> 1Mb未压缩)音频样本 即可。我试过了:

只需在我的项目中包含Maestrini的LoopMediaPlayer课程,然后使用MediaPlayer.create()来电替换我的LoopMediaPlayer.create()来电,我就可以确保我的.OGG示例无缝循环。因此LoopMediaPlayer是一个值得称赞的实用且透明的解决方案。

但这种透明度引出了一个问题:一旦我调用MediaPlayer来电LoopMediaPlayer次来电,我的实例如何调用MediaPlayer方法,例如。isPlaying,{{ 1}}或.pause 以下是此问题的解决方案 。可能它可以由比我更熟悉Java的人改进(我欢迎他们的意见),但到目前为止,我发现这是一个可靠的解决方案。

我对Maestrini的课程所做的唯一更改(除了Lint推荐的一些调整)在下面的代码末尾标记;其余的我包括上下文。我的补充是通过在.setVolume上调用MediaPlayer来实现LoopMediaPlayermCurrentPlayer的几种方法。

警告: ,当我实施以下MediaPlayer的几个有用方法时, 我没有实现所有这些方法。 因此,如果您希望例如调用.attachAuxEffect,则需要将自己添加为LoopMediaPlayer的方法,与我添加的内容相同。一定要复制这些方法的原始接口(即参数,抛出和返回):

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

答案 3 :(得分:2)

这样的事情应该有效。在res.raw目录中保留同一文件的两个副本。请注意,这只是一个POC而不是优化的代码。我刚测试了它,它按预期工作。让我知道你的想法。

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

答案 4 :(得分:2)

我建议您使用SoundPool API代替MediaPlayer

来自官方文件:

  

SoundPool类管理和播放音频资源   应用

     

...

     

可以通过设置非零循环来循环声音   值。值为-1会导致声音永远循环。在这种情况下,   应用程序必须显式调用stop()函数来停止   声音。任何其他非零值将导致声音重复   指定的次数,例如值为3会导致声音播放   共4次。

     

...

请查看here,了解如何使用SoundPool

的实际示例

答案 5 :(得分:1)

出于某种原因,我发现我的&#34; OnCompletion&#34; 事件在尝试循环8秒时总是触发第二的一小部分OGG文件。对于遇到此类延迟的任何人,请尝试以下操作。

可以强制排队 &#34; nextMediaPlayer&#34; ,就像以前的解决方案一样,只需发布​​延迟的Runnable 对于MediaPlayers的处理程序和完全避免在onCompletion 事件中循环。

使用我的160kbps 8秒OGG,最低API 16,这对我来说完美无瑕。

在您的活动/服务中的某个位置,创建一个 HandlerThread&amp;处理 ...

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

... 启动帖子,声明并设置您的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;
}

...创建交换 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.");
}

...创建 Runnables 以发布到您的帖子...

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

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

在您的服务的onStartCommand()或您的Activity中的某个位置,启动MediaPlayer ...

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

答案 6 :(得分:0)

我已经尝试了这里和其他地方建议的所有内容,唯一起作用的是ExoPlayer而不是Music类。您可以使用以下命令访问libgdx文件:

Uri.parse("file:///android_asset/" + path)

您还需要platform specific code

答案 7 :(得分:0)

CODE-REad的LoopMediaPlayer示例很好,但是如果您使用创建MediaPlayer的新MediaPlayer()方法(就像我为使用File或AssetFileDescriptor数据源所做的那样)而不是MediaPlayer.Create()方法,则必须小心

  1. 在.start()之后调用setOnCompletionListener方法,否则它将 不开火。
  2. 在mNextPlayer之前完全.prepare()或.prepareAsync() 在mCurrentPlayer上调用.setNextMediaPlayer,否则将无法 播放mNextPlayer。这意味着在onPreparedListener中调用.start,setOnCompletionListener和.setNextMediaPlayer,如下所示。

我已经修改了他的代码,以使用新的MediaPlayer()方法创建播放器,并且还添加了从AssetFileDescriptor和File设置数据源的功能。我希望这可以节省一些时间。

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

答案 8 :(得分:0)

使用Mattia Maestrini's answer时,我能够按照自己想要的方式进行音频循环播放,但是,由于我将其用于Android Auto,因此发现音频只能通过手机扬声器而不是汽车扬声器播放。最终,我发现this answer指出了一个错误,该错误使得在这种情况下将new MediaPlayer()构造函数与setDataSource方法一起使用很重要。我已经在代码中使用Uri了,所以我使用了该变体,因此我不确定100%的重要性,我假设其他任何setDataSource变体就足够了对您的代码很重要。

这是最终为我工作的东西:

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