Skip to content

Music Player (JukeBox)

Hangman edited this page Dec 26, 2023 · 7 revisions

The music player can play music from a playlist, fade in/out and switch between different playlists as they are played. MusicPlayer would be a perfect name for it, yet I decided against it and called the whole thing JukeBox. This is not because I think it fits better, but rather to avoid confusion with the word Music in libGDX. In libGDX Music simply stands for a streaming sound source. In that sense, a music player would just be a unit that plays said source.

An overview

Class Description
JukeBox This is the music player, it needs to be updated every frame and offers methods to play Songs from PlayLists.
JukeBoxObserver If you want to be informed about status changes of the JukeBox, you can implement this interface and register as observer of the JukeBox. Classic observer design pattern.
PlayList A PlayList is a collection of any number of Songs that should be grouped together. The JukeBox plays them in order.
PlayListProvider The PlayListProvider is responsible for providing the JukeBox with PlayLists. If a PlayList was played completely, the JukeBox asks the PlayListProvider for the next PlayList. This is an interface, the implementation is entirely up to you. However, there are built-in implementations for the most common use cases.
Song A Song groups a SoundSource, SoundSettings and SongMeta.
SongSettings Contains settings how a Song should be played. For example, the volume can be specified here or the fade-in and fade-out configuration.
SongMeta In the SongMeta information about a Song can be stored, such as artist, track name, album... this is totally optional though!

Here's an example of how these classes play together: JukeBoxTest

Song

So let's start with creating a Song we can put in a PlayList later on. SongMeta and SongSettings are optional but let's create both to see what's possible. The SoundSource might be a BufferedSoundSource or a StreamedSoundSource, that's up to you.

SongMeta meta = new SongMeta().setTitle("Learning To Fly").setArtist("Tom Petty");
SongSettings settings = SongSettings.linear(volume, fadeInDuration, fadeOutDuration);
Song song = new Song(soundSource, settings, meta);

// optional: If you want to play the Song globally, don't forget to make the SoundSource relative.
soundSource.setRelative(true);

In case you are wondering how this system prevents the SoundSource from being modified while in use by the JukeBox...
It is not prevented.
The responsibility is on you to leave the source alone. At the same time this is freedom and gives you more possibilities to change the sound with effects or the like.

PlayList

A list of songs, loopable, shuffleable and that's it. Let's create one and add our Song to it:

PlayList playList = new PlayList();
playList.setLooping(true);
playList.setShuffleAfterPlaytrough(true);
playList.addSong(song1);
playList.addSong(song2);
playList.addSong(song3);

Now we have a PlayList containing three songs, it's shuffled after each playthrough and it will be played in a loop.
Though, it will only be looped if the PlayListProvider does not provide another PlayList when the JukeBox asks for one.

PlayListProvider

A PlayListProvider provides the JukeBox with PlayLists. It's an interface with just 2 methods: hasNext() and next().
It's up to the implementation how PlayLists are returned by next(). So feel free to create your own implmentation that fits your needs.
There's built-in implementations for the most common use cases:

Class Description
DefaultPlayListProvider This class holds just a list of PlayLists and provides them in order. When all PlayLists got fetched by next(), playback stops.
CircularPlayListProvider The same as DefaultPlayListProvider but fetched PlayLists won't be removed and playback will start again with the first PlayList when the last one finished playing.
ThemePlayListProvider This provider provides PlayLists based on a theme. The theme might be any integer ID you chose. The player is in a boss fight? Set the theme to boss fight and the boss music PlayList will be provided. The player wins the boss fight? Set the theme back to normal.
PlayListProvider provider = new DefaultPlayListProvider().add(playList1).add(playList2);

JukeBox

And finally let's create a JukeBox, start playback and update it properly:

private JukeBox jukeBox = new JukeBox(provider);
jukeBox.play();

// somewhere in libGDX's render loop
@Override
public void render() {
    jukeBox.update();
}

Besides pausing and stopping the JukeBox, there's also a softStop() method that allows for fading out the Song before stopping:

jukeBox.softStop(Interpolation.linear, fadeOutDuration);

And last but not least, if you want to be notified when a Song finished playing or if a new Song or PlayList starts, register as an observer:

JukeBoxObserver observer = new MyObserver();
jukeBox.addObserver(observer);

public class MyObserver implements JukeBoxObserver {
    @Override
    public void onSongStart(Song song) {
        System.out.println("Song started: " + song.getMeta().getTitle());
    }


    @Override
    public void onSongEnd(Song song) {
        System.out.println("Song ended: " + song.getMeta().getTitle());
    }


    @Override
    public void onPlayListStart(PlayList playList) {
        System.out.println("PlayList started: " + playList);
    }


    @Override
    public void onPlayListEnd(PlayList playList) {
        System.out.println("PlayList ended: " + playList);
    }


    @Override
    public void onJukeBoxEnd() {
        System.out.println("JukeBox ended");
    }


    @Override
    public void onJukeBoxStart() {
        System.out.println("JukeBox started");
    }


    @Override
    public void onJukeBoxPause() {
        System.out.println("JukeBox paused");
    }
}