5

I'm using libGDX and face the problem that background music does not flawlessly loop on various Android devices (Nexus 7 running Lollipop for example). Whenever the track loops (i.e. jumps from the end to the start) a clearly noticeable gap is hearable. Now I wonder how the background music can be played in a loop without the disturbing gap?

I've already tried various approaches like:

  • Ensuring the number of Samples in the track are an exact multiple of the tracks sample rate (as mentioned somewhere here on SO).
  • Various audio formats like .ogg, .m4a, .mp3 and .wav (.ogg seems to be the solution of choice here at SO, but unfortunately it does not work in my case).
  • Used Androids MediaPlayer with setLooping(true) instead of libGDX Music class.
  • Used Androids MediaPlayer.setNextMediaPlayer(). The code looks like the following, and it plays the two tracks without a gap in between, but unfortunately, as soon as the second MediaPlayer finishes, the first does not start again!

        /* initialization */
        afd = context.getAssets().openFd(filename);
        firstBackgroundMusic.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
        firstBackgroundMusic.prepare();
        firstBackgroundMusic.setOnCompletionListener(this);
    
        secondBackgroundMusic.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
        secondBackgroundMusic.prepare();
        secondBackgroundMusic.setOnCompletionListener(this);
    
        firstBackgroundMusic.setNextMediaPlayer(secondBackgroundMusic);
        secondBackgroundMusic.setNextMediaPlayer(firstBackgroundMusic);
    
        firstBackgroundMusic.start();
    
    
    
        @Override
        public void onCompletion(MediaPlayer mp) {
            mp.stop();
            try {
                mp.prepare();
            } catch (IOException e) { e.printStackTrace(); }
        }
    

Any ideas what's wrong with the code snippet?

SePröbläm
  • 5,142
  • 6
  • 31
  • 45
  • have a look at this: http://www.badlogicgames.com/forum/viewtopic.php?f=11&t=3902 – BradleyIW Apr 21 '15 at 19:37
  • ExoPlayer. See here on a duplicate question: https://stackoverflow.com/a/56925333/11533635 – Arunex Jul 07 '19 at 19:17
  • Wow, 2021 and the question got an up-vote. That's surprising. Well, libGDX and RoboVM were fun in 2015, but in 2021 you're better off with Godot: https://godotengine.org/ – SePröbläm Mar 11 '21 at 14:14

2 Answers2

3

Just for the records: It tuned out to be unsolvable. At the end, we looped the background music various times inside the file. This way the gap appears less frequently. It's no real solution to the problem, but the best workaround we could find.

SePröbläm
  • 5,142
  • 6
  • 31
  • 45
  • How did you come to the conclusion that this is unsolvable? Anything specific or just because you tried everything and nothing worked? – Acapulco Jul 08 '15 at 21:59
  • 1
    There's an issue in the Android bug tracker - and yes, I tried every possible option to loop background music before giving it up. – SePröbläm Jul 09 '15 at 23:21
0

This is an old question but I will give my solution in case anyone has the same problem.

The solution requires the use of the Audio-extension(deprecated but works just fine),if you cant find the link online here are the jars that I am using, also requires some external storage space.

The abstract is the following

  1. Extract the raw music data with a decoder(VorbisDecoder class for ogg or Mpg123Decoder for mp3)and save them to the external storage(you can make a check to see if it already exists so that it only needs to be extracted once, cause it takes some time).
  2. Create a RandomAccessFile using the file you just saved to the external storage
  3. While playing set the RandomAccessFile pointer to the correct spot in the file and read a data segment
  4. Play the above data segment with the AudioDevice class

Here is some code

Extract the music file and save it to the external storage,file is the FileHandle of the internal music file,here is an ogg and thats why we use VorbisDecoder

    FileHandle external=Gdx.files.external("data/com.package.name/music/"+file.name());
    file.copyTo(external);
    VorbisDecoder decoder = new VorbisDecoder(external);
    FileHandle extreactedDataFile=Gdx.files.external("data/com.package.name/music/"+file.nameWithoutExtension()+".mdata");
    if(extreactedDataFile.exists())extreactedDataFile.delete();
    ShortBuffer sbuffer=ByteBuffer.wrap(shortBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
    while(true){
        if(LogoScreen.shouldBreakMusicLoad)break;
        int num=decoder.readSamples(samples, 0,samples.length);
        sbuffer.put(samples,0,num);
        sbuffer.position(0);
        extreactedDataFile.writeBytes(shortBytes,0,num*2, true);
        if(num<=0)break;
    }
    external.delete();

Create an RandomAccessFile pointing to the file we just created

    if(extreactedDataFile.exists()){
        try {
            raf=new RandomAccessFile(Gdx.files.external(extreactedDataFile.path()).file(), "r");
            raf.seek(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Create a Buffer so we can translate the bytes read from the file to a short array that gets feeded to the AudioDevice

public byte[] rafbufferBytes=new byte[length*2];
public short[] rafbuffer=new short[length];
public ShortBuffer sBuffer=ByteBuffer.wrap(rafbufferBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();

When we want to play the file we create an AudioDevice and a new thread where we read constantly read from the raf file and feed it to the AudioDevice

    device = Gdx.audio.newAudioDevice((int)rate/*the hrz of the music e.g 44100*/,isthemusicMONO?);
    currentBytes=0;//set the file to the beggining
    playbackThread = new Thread(new Runnable() {
        @Override
        public synchronized  void run() {
                while (playing) {
                        if(raf!=null){
                            int length=raf.read(rafbufferBytes);
                            if(length<=0){
                                ocl.onCompletion(DecodedMusic.this);
                                length=raf.read(rafbufferBytes);
                            }
                            sBuffer.get(rafbuffer);
                            sBuffer.position(0);
                            if(length>20){
                                try{
                                    device.writeSamples(rafbuffer,0,length/2);
                                    fft.spectrum(rafbuffer, spectrum);
                                    currentBytes+=length;
                                }catch(com.badlogic.gdx.utils.GdxRuntimeException ex){
                                    ex.printStackTrace();
                                    device = Gdx.audio.newAudioDevice((int)(rate),MusicPlayer.mono);
                                }
                            }
                        }
                    }
                }
    });
    playbackThread.setDaemon(true);
    playbackThread.start();

And when we want to seek at a position

public void seek(float pos){
    currentBytes=(int) (rate*pos);
    try {
        raf.seek(currentBytes*4);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
SteveL
  • 3,331
  • 4
  • 32
  • 57
  • Thanks for your help. I haven't tried it but will accept your answer anyway. Sorry for the five and a half years delay ;-) – SePröbläm Mar 11 '21 at 14:20