Подтвердить что ты не робот

Не удалось достичь Gapless аудио-цикла до сих пор на Android

Я пробовал почти каждый метод, но мне не удалось добиться беспроблемного воспроизведения звука между циклом одного трека продолжительностью 10-15 секунд.

Шаги, которые я пробовал и не выполнял:

  • Различные форматы аудиофайлов .mp3 .wav .ogg, используя setLooping(true):

    MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1);
    mp1.setLooping(true);
    mp1.start();
    
  • Создание двух медиаплееров и циклирование один за другим с использованием setOnCompletionListener то же самое не удалось выполнить без пробелов.

  • Используя setNextMediaPlayer(nextmp), как это работает, но возможны только две петли. Мы должны подготовиться и начать снова после завершения предыдущих двух циклов.

    mp1.start();
    mp1.setNextMediaPlayer(mp2);
    
  • Update: Результат ответа @Jeff Mixon: Цикл Mediaplayer останавливается с ошибкой Android. Джефф Миксон работает нормально, но только за 10 или 20 циклов после этого, из-за некоторых проблем с сборкой мусора Mediaplayers останавливается сразу же, оставляя журналы, как указано ниже. Я действительно застрял здесь два года. Спасибо заранее.

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

Ответ 1

Из теста, который я сделал, это решение работает нормально, более 150 циклов с 13-секундным MP3-плеером на 160 Кбит/с без проблем:

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

Ответ 2

Уродливый код концептуального кода, но вы получите идею:

// 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 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 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 Кбит/с. Отсутствие пробелов в цикле.

Ответ 3

По крайней мере, от KitKat, Mattia Maestrini Отвечать (на этот вопрос) является единственным решением, которое я нашел, что позволяет беззаботное зацикливание большого ( > 1 Мб несжатого) аудио-образца. Я пробовал:

Просто включив класс Maestrini LoopMediaPlayer в мой проект, а затем заменив мои вызовы MediaPlayer.create() на вызовы LoopMediaPlayer.create(), я могу обеспечить, чтобы мой пример .OGG был без проблем зациклен. LoopMediaPlayer, следовательно, является похвально практичным и прозрачным решением.

Но эта прозрачность задает вопрос: как только я поменяю свои вызовы MediaPlayer на вызовы LoopMediaPlayer, как мой экземпляр вызывает методы MediaPlayer, такие как. isPlaying, .pause или .setVolume? Ниже мое решение для этой проблемы. Возможно, это может улучшить кто-то более здравомыслящий Java, чем я (и я приветствую их вклад), но до сих пор я нашел это надежным решением.

Единственные изменения, которые я внес в класс Maestrini (помимо некоторых настроек, рекомендованных Lint), отмечены в конце кода ниже; остальное я включаю для контекста. Моим дополнением является реализация нескольких методов MediaPlayer внутри LoopMediaPlayer путем их вызова на mCurrentPlayer.

Предостережение:, в то время как я реализую несколько полезных методов 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();
    }
}

Ответ 4

Что-то вроде этого должно работать. Храните две копии одного и того же файла в каталоге 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();
}
}

Ответ 5

Я предлагаю вам использовать SoundPool вместо MediaPlayer.

Из официальной документации:

Класс SoundPool управляет и воспроизводит аудио ресурсы для приложения.

...

Звуки могут быть закодированы путем установки ненулевого цикла стоимость. Значение -1 заставляет звук зацикливаться навсегда. В этом случае, приложение должно явно вызвать функцию stop(), чтобы остановить звук. Любое другое ненулевое значение приведет к тому, что звук повторит указанное количество раз, например. значение 3 заставляет звук играть всего 4 раза.

...

Посмотрите здесь для практического примера использования SoundPool.

Ответ 6

По какой-то причине я обнаружил, что мой "OnCompletion" Event всегда стрелял во вторую секунду поздно при попытке зацикливать 8-секундный файл OGG. Для тех, кто испытывает такой тип задержки, попробуйте следующее.

Можно принудительно поставить очередь a nextMediaPlayer, как рекомендуется в предыдущих решениях, просто отправив задержанный Runnable в обработчик для ваших MediaPlayers и избегая looping in onCompletion Событие вообще.

Это безупречно работает для меня с моим 8-секундным OGG с пропускной способностью 160 Кбит/с, минимальным API 16.

Где-то в вашей деятельности/службе создайте 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();
    }
}`

... запустите Thread, объявите и настройте 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;
}

... создайте метод для замены ваших медиаплееров...

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() или где-нибудь в вашей деятельности запустите MediaPlayer...

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

Ответ 7

Я перепробовал все предложенное здесь и в других местах, и единственное, что сработало, это ExoPlayer вместо класса Music. Вы можете получить доступ к своим файлам libgdx с помощью:

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

Вам также понадобится код для конкретной платформы.

Ответ 8

CODE-REad Пример LoopMediaPlayer великолепен, но если вы используете новый метод MediaPlayer() для создания MediaPlayer (как я это делаю для использования источников данных File или AssetFileDescriptor), а не метод MediaPlayer.Create(), тогда вы должны быть осторожны с

  1. Вызовите метод setOnCompletionListener ПОСЛЕ .start(), иначе он не сработает.
  2. Полностью .prepare() или .prepareAsync() mNextPlayer перед вызовом .setNextMediaPlayer на mCurrentPlayer, иначе он не сможет воспроизвести mNextPlayer. Это означает вызов .start, setOnCompletionListener и .setNextMediaPlayer в onPreparedListener, как показано ниже.

Я изменил его код, чтобы использовать новый метод MediaPlayer() для создания проигрывателя, а также добавил возможность устанавливать источник данных из AssetFileDescriptor и файла. Надеюсь, это сэкономит кому-то время.

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