Не удалось достичь 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)
Ответы
Ответ 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(), тогда вы должны быть осторожны с
- Вызовите метод setOnCompletionListener ПОСЛЕ .start(), иначе он не сработает.
- Полностью .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();
}
}