/ / Do tej pory nie można uzyskać pętli audio Gapless na Androidzie - Android, Android-Audiomanager

Nie można uzyskać do tej pory pętli audio Gapless w systemie Android - Android, Android-Audiomanager

Wypróbowałem prawie każdą metodę, ale nie udało mi się uzyskać bezproblemowego odtwarzania dźwięku między zapętleniem pojedynczej ścieżki o czasie trwania 10-15 sekund.

Kroki, które próbowałem i nie powiodły się:

  1. Różne formaty plików audio .mp3 .wav .ogg za pomocą setLooping(true):

    MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1);
    mp1.setLooping(true);
    mp1.start();
    
  2. Tworzenie dwóch mediaplayerów i zapętlanie jeden po drugim za pomocą setOnCompletionListenerto samo nie zapętliło się bez przerw.

  3. Za pomocą setNextMediaPlayer(nextmp) niektóre jak to działa, ale możliwe są tylko dwie pętle. Musimy się przygotować i zacząć od nowa po zakończeniu poprzednich dwóch pętli.

    mp1.start();
    mp1.setNextMediaPlayer(mp2);
    
  4. Aktualizacja: Wynik odpowiedzi @Jeff Mixon: Pętla Mediaplayer zatrzymuje się z błędem Androida. Jeff Mixon działa dobrze, ale tylko dla 10 lub 20Pętle po tym, z powodu pewnych problemów związanych ze zbieraniem pamięci, Mediaplayers przestaje natychmiast zostawiać dzienniki, jak pokazano poniżej. Utknąłem tu od 2 lat. Z góry dzięki.

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

Odpowiedzi:

28 dla odpowiedzi № 1

Z testu, który wykonałem, to rozwiązanie działa dobrze, ponad 150 pętli z 13 sekundowym 160 kb / s MP3 bez żadnego problemu:

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

Używać LoopMediaPlayer możesz po prostu zadzwonić:

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

12 dla odpowiedzi nr 2

Brzydki kod proof-of-concept, ale wpadniesz na pomysł:

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

To działało dla mnie na urządzeniu API 19 i 5-sekundowym MP3 128 kbps. Brak przerw w pętli.


3 dla odpowiedzi nr 3

Przynajmniej od KitKat, Odpowiedź Mattii Maestrini (na to pytanie) jest jedyne rozwiązanie, które znalazłem, które pozwala na bezproblemowe zapętlanie dużej (> 1 Mb nieskompresowanej) próbki audio. Próbowałem:

Po prostu włączając Maestrini LoopMediaPlayer zajęcia w moim projekcie, a następnie zastąpienie mojego MediaPlayer.create() połączenia z LoopMediaPlayer.create() połączeń, mogę upewnić się, że moja próbka .OGG jest płynnie zapętlona. LoopMediaPlayer jest zatem godnym pochwały praktycznym i przejrzystym rozwiązaniem.

Ale ta przejrzystość nasuwa pytanie: kiedy już wymienię MediaPlayer wymaga LoopMediaPlayer połączeń, jak dzwoni moja instancja MediaPlayer metody takie jak.isPlaying, .pause lub .setVolume? Poniżej znajduje się moje rozwiązanie tego problemu. Być może może być ulepszony przez kogoś bardziej znającego Javę niż ja (i cieszę się z ich wkładu), ale jak dotąd uważam to za niezawodne rozwiązanie.

Jedyne zmiany, które wprowadzam w klasie Maestriniego(oprócz niektórych poprawek zalecanych przez Lint) są oznaczone na końcu kodu poniżej; resztę uwzględniam w kontekście. Moim dodatkiem jest wdrożenie kilku metod MediaPlayer w ciągu LoopMediaPlayer dzwoniąc do nich mCurrentPlayer.

Zastrzeżenie: podczas gdy wdrażam kilka przydatnych metod MediaPlayer poniżej, Nie wdrażam ich wszystkich. Więc jeśli spodziewasz się na przykład zadzwonić .attachAuxEffect musisz dodać to sam jako metodę LoopMediaPlayer zgodnie z tym, co dodałem. Pamiętaj, aby powielić oryginalne interfejsy tych metod (tj. Parametry, rzuty i zwroty):

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

2 dla odpowiedzi № 4

Coś takiego powinno działać. Zachowaj dwie kopie tego samego pliku w katalogu res.raw. Pamiętaj, że jest to tylko POC, a nie zoptymalizowany kod. Właśnie to przetestowałem i działa zgodnie z przeznaczeniem. Powiedz mi co myślisz.

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

2 dla odpowiedzi № 5

Proponuję ci użyć SoundPool API zamiast MediaPlayer.

Z oficjalnej dokumentacji:

Klasa SoundPool zarządza i odtwarza zasoby audio Aplikacje.

...

Dźwięki można zapętlać, ustawiając wartość niezerowąpętla wartość. Wartość -1 powoduje, że dźwięk zapętla się na zawsze. W tym przypadku, aplikacja musi jawnie wywołać funkcję stop (), aby zatrzymać dźwięk. Każda inna niezerowa wartość spowoduje powtórzenie dźwięku określoną liczbę razy, np. wartość 3 powoduje odtwarzanie dźwięku w sumie 4 razy.

...

Spójrz tutaj praktyczny przykład użycia SoundPool.


1 dla odpowiedzi № 6

Z jakiegoś powodu znalazłem, że mój "Po zakończeniu" Wydarzenie zawsze strzelało ułamek sekundy późno podczas próby zapętlenia 8-sekundowego pliku OGG. Dla każdego, kto doświadcza tego rodzaju opóźnienia, spróbuj wykonać następujące czynności.

Możliwe jest przymusowa kolejka za „nextMediaPlayer” zgodnie z zaleceniami poprzednich rozwiązań, po prostu zamieszczając opóźniony Runnable do modułu obsługi odtwarzaczy MediaPlayers i unikanie zapętlania się w onCompletion Wydarzenie w ogóle.

Działa to dla mnie bezbłędnie z moim 8-sekundowym OGG 160kbps, min API 16.

Gdzieś w swojej działalności / usłudze utwórz 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();
}
}`

...rozpocząć wątek, oświadczam i skonfiguruj 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;
}

...Stwórz Metoda zamiany twoi 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.");
}

...Stwórz Runnables publikować w swoim wątku ...

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

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

W usłudze onStartCommand () lub gdzieś w swojej działalności, uruchom MediaPlayer ...

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