Music player logic help

Hi, I’m new Lua/Defold game developer coming from C#/Unity.

I’m testing music player for my game and ran into a problem I cant seem to figure out.
When track is done playing the [complete_function] is called and next track starts playing fine.
But when I manually press next track button it goes through the tracks instantly and really fast.
Help would be much appreciated!

Below is parts of the code

-- Music playback status table
local music = {
	playing = true,
	repeating = false,
	shuffle = false,
	index = nil,
}

-- Track component table
local track = {}
for i = 1, 8 do
	track[i] = "#track_" .. i
end

-- Next track
local function forward()
	if music.index < #track then
		sound.stop(track[music.index])
		music.index = music.index + 1
		sound.play(track[music.index], nil, forward)
		music.playing = true
	else
		sound.stop(track[music.index])
		music.index = 1
		sound.play(track[music.index], nil, forward)
		music.playing = true
	end
end

What about the input code? I think the problem might lay there.

I’m using Gooey and the input comes from music.gui_script

local function update_button(button)
	if button.pressed_now then
		gui.set_color(button.node, COLOR_PRESSED)
	elseif button.released_now then
		gui.set_color(button.node, COLOR_DEFAULT)
	elseif not button.pressed and button.out_now then
		gui.set_color(button.node, COLOR_DEFAULT)
	end
end

local function on_pressed(button)
	if button.node_id == hash("button_music_forward") then
		msg.post("main:/music", "music_forward")
	end
end

function on_input(self, action_id, action)
	gooey.button("button_music_forward", action_id, action, on_pressed, update_button)
end

Then in music.script I react to the message like this

function on_message(self, message_id, message, sender)
	if message_id == hash("music_forward") then
		forward()
	end
end

Initial post code snippet is also from music.script

Thanks. The reason I asked is because I was thinking that you were calling forward() constantly in on_input(), but assuming you have Gooey set up correctly (I’m not familiar with it) then the fact that it is on_press should invalidate that theory.

Having run a quick check, I think it is because calling sound.stop() triggers the complete_function. This isn’t clear in the sound.play(), sound_stop() or sound_done API refs.

In my opinion, the most problematic part of the documentation is this line:

This message is sent back to the sender of a play_sound message, if the sound could be played to completion.

I think the documentation might benefit from an update clarifying this.

2 Likes

Thanks for the help! How would I rewrite the logic now?

One idea would be to remove sound.stop() from forward(). And instead of calling forward when pushing the button, just stop the sound instead.

4 Likes

Yes now it works as expected. Thank you so much for your help!

EDIT: Okay still have problems :sweat_smile:

2 Likes

I moved this music player fixing for later to do other stuff but now I’m back to finish this. :wrench:

I’m trying to do very basic in-game music player with the usual buttons like, Play/Pause, previous track, next track, shuffle and repeat.

Using sound.play() with complete_function to play next track after current one finishes.
Pressing next track (>>) button calls sound.stop() and that triggers the complete_function so this is working great.

But with previous track (<<) button it should stop current track without triggering complete_function and then start playing previous track that does trigger complete_function like normal.

I feel like this should be simple thing to figure out but I’m struggling with this still :sweat_smile:

Could you set a temporary flag to change how the on_complete works?

When you press previous:

previous_requested = true

In on_complete:

if previous_requested then
     --code to select previous track
    previous_requested = nil --reset flag
else
   --code to select next track
end
1 Like

Here are relevant parts of the music player code currently.

Local variables and tables

-- Music player status
local is_playing = false
local is_repeating = false
local is_shuffle = false
local index = nil

local previous_requested = nil

-- Track table
local track = {}
for i = 1, 8 do
	track[i] = "#track_"..i
end

Init

-- Start playing from track 1
index = 1
sound.play(track[index], nil, track_selection)
is_playing = true

Track selection func

local function track_selection()
	if is_repeating then
		sound.play(track[index], nil, track_selection)
		is_playing = true
	elseif is_shuffle then
		index = math.random(1, 8) --TODO: proper shuffle
		sound.play(track[index], nil, track_selection)
		is_playing = true
	else
		if previous_requested then
			-- Previous track (<<)
			previous_requested = nil --reset flag
			index = index - 1
			if index < 1 then index = #track end
		else
			-- Next track (>>)
			index = index + 1
			if index > #track then index = 1 end
		end
		sound.play(track[index], nil, track_selection)
		is_playing = true
	end
end

On message

...
if message_id == hash("next_track") then
	if is_playing then
		sound.stop(track[index])
	else
		track_selection()
	end
elseif message_id == hash("previous_track") then
	if is_playing then
		sound.stop(track[index])
	else
		previous_requested = true
		track_selection()
	end
elseif message_id == hash("toggle_music") then
	if is_playing then
		sound.pause(track[index], true)
		is_playing = false
	else
		sound.pause(track[index], false)
		is_playing = true
	end
elseif message_id == hash("toggle_shuffle") then
	is_shuffle = not is_shuffle -- toggle bool
elseif message_id == hash("toggle_repeat") then
	is_repeating = not is_repeating -- toggle bool
end
...

Using msg.post() to play sound, what does play_id actually do? Not much docs about it.
I know its needed to receive sound_done message.

msg.post(track[index], "play_sound", { play_id = 1 })

Maybe assign the track index as play_id?

msg.post(track[index], "play_sound", { play_id = index })

Should sound_done come only when sound could be played to completion? Because I’m receiving it even if I use sound.stop().

1 Like