Game lags when Panel is opened or song is skipped

Godot Version

godot 4.3

Question

Hello, I have an issue in my game, I have been working to improve my UI in my main scene by adding controllable music, but one issue, whenever I skip a song or check the playlist, my frame rate decreases by around 25-50% and if i spam click it, my game freezes and crashes. Here is a video showing what is happening.

Here is some important code to help with this:

UI.gd (THE MAIN SCENE)

#UI.gd
extends Control

var skip_track_cooldown: bool = false
var focusable_buttons: Array = []
var update_timer: Timer

@onready var sidebar_button: TextureButton = $SidebarButton
@onready var sidebar_panel: Panel = $SidebarPanel
@onready var tween2: Tween
@onready var sidebar_playlist_label: Label = $SidebarPanel/PlaylistLabel
@onready var playlist_scroll_container: ScrollContainer = $SidebarPanel/ScrollContainer
@onready var playlist_container: VBoxContainer = $SidebarPanel/ScrollContainer/PlaylistContainer
@onready var pause_button: Button = $PauseButton


func _ready():
	if not is_in_group("ui"):
		add_to_group("ui")
	get_viewport().size = DisplayServer.screen_get_size()
	get_window().grab_focus()
	if not $MarginContainer/VBoxContainer/LeaderboardButton.is_connected("pressed", Callable(self, "_on_Leaderboard_pressed")):
		$MarginContainer/VBoxContainer/LeaderboardButton.connect("pressed", Callable(self, "_on_Leaderboard_pressed"))
	if not credits.is_connected("pressed", Callable(self, "_on_CreditsButton_pressed")):
		credits.connect("pressed", Callable(self, "_on_CreditsButton_pressed"))
	sidebar_panel.position.y = get_viewport_rect().size.y
	if not sidebar_button.is_connected("pressed", Callable(self, "_on_sidebar_button_pressed")):
		sidebar_button.connect("pressed", Callable(self, "_on_sidebar_button_pressed"))
	sidebar_playlist_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
	sidebar_playlist_label.text = "Songs in playlist:"
	# Initialize and start the update timer
	update_timer = Timer.new()
	update_timer.wait_time = 1.0  # Update every second
	update_timer.connect("timeout", Callable(self, "_on_update_timer_timeout"))
	add_child(update_timer)
	update_timer.start()
	music_progress_bar.connect("gui_input", Callable(self, "_on_music_progress_gui_input"))
	pause_button.text = "Pause"
	print("UI: Checking music status")
	MusicManager.ensure_playing()

	
	# Add the new buttons to the focusable_buttons array
	focusable_buttons.append(ultimate_button)
	focusable_buttons.append(simple_button)
	# Set up the layout
	setup_layout()

	# Initialize AudioStreamPlayer reference and set the sound stream
	button_click_player = $ButtonClickPlayer
	button_click_player.stream = load("res://Sounds/click.ogg")

	# Register the music player with AudioManager
	AudioManager.register_music_player(MusicManager.music_player)

	# Register the button click player with AudioManager
	if button_click_player:
		AudioManager.register_sfx_player(button_click_player)

	# Load and apply audio settings
	AudioManager.load_and_apply_settings()

	# Connect the SkipTrack button
	if not skip_track.is_connected("pressed", Callable(self, "_on_SkipTrack_pressed")):
		skip_track.connect("pressed", Callable(self, "_on_SkipTrack_pressed"))
		
	if not pause_button.is_connected("pressed", Callable(self, "_on_pause_button_pressed")):
		pause_button.connect("pressed", Callable(self, "_on_pause_button_pressed"))
		
	# Set up focusable buttons in the desired order
	focusable_buttons = [play, settings, stats, leaderboard, exit, credits, skip_track, youtube_button, github_button, discord_button, sidebar_button, pause_button]
	for button in focusable_buttons:
		button.focus_mode = Control.FOCUS_ALL
		
	get_tree().root.size_changed.connect(self.on_window_resized)
	# Set initial focus
	set_initial_focus()
	update_currently_playing_label()
	update_playlist_display()
	MusicManager.music_player.connect("finished", Callable(self, "update_currently_playing_label"))
	MusicManager.music_player.connect("finished", Callable(self, "update_playlist_display"))
	var button_style = StyleBoxFlat.new()
	button_style.bg_color = Color(0.2, 0.2, 0.2)  # Greyish-blackish color
	button_style.border_width_bottom = 4
	button_style.border_color = Color(0.1, 0.1, 0.1)
	ultimate_button.add_theme_stylebox_override("normal", button_style)
	simple_button.add_theme_stylebox_override("normal", button_style)
	
	ultimate_button.text = "Ultimate Tic Tac Toe"
	simple_button.text = "Simple Tic Tac Toe"

func update_playlist_display():
	var playlist_size = MusicManager.playlist.size()
	var children = playlist_container.get_children()
	var child_count = children.size()
	
	for i in range(playlist_size):
		var track_path = MusicManager.playlist[i]
		var track_name = track_path.get_file().get_basename()
		var audio_stream = load(track_path)
		var duration = audio_stream.get_length() if audio_stream else 0.0
		
		var item: Button
		if i < child_count:
			item = children[i]
		else:
			item = Button.new()
			item.flat = true
			item.alignment = HORIZONTAL_ALIGNMENT_LEFT
			item.size_flags_horizontal = Control.SIZE_EXPAND_FILL
			playlist_container.add_child(item)
		
		item.text = "%s - %s" % [track_name, format_time(duration)]
		item.set_meta("track_index", i)
		
		if not item.is_connected("pressed", Callable(self, "_on_playlist_item_pressed")):
			item.connect("pressed", Callable(self, "_on_playlist_item_pressed").bind(item))
		
		if i == MusicManager.current_track_index:
			item.add_theme_color_override("font_color", Color.YELLOW)
			item.add_theme_color_override("font_outline_color", Color.BLACK)
			item.add_theme_constant_override("outline_size", 2)
		else:
			item.remove_theme_color_override("font_color")
			item.remove_theme_color_override("font_outline_color")
			item.remove_theme_constant_override("outline_size")
	
	# Remove excess children
	while child_count > playlist_size:
		var child = children[child_count - 1]
		playlist_container.remove_child(child)
		child.queue_free()
		child_count -= 1
	
	playlist_scroll_container.queue_sort()

func _on_playlist_item_pressed(item: Button):
	var track_index = item.get_meta("track_index")
	MusicManager.play_specific_track(track_index)
	pause_button.text = "Pause"
	call_deferred("update_playlist_display")
	call_deferred("update_currently_playing_label")

func _on_music_progress_gui_input(event: InputEvent):
	if event is InputEventMouseButton or (event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_LEFT):
		var ratio = clamp(event.position.x / music_progress_bar.size.x, 0, 1)
		var new_position = ratio * MusicManager.music_player.stream.get_length()
		MusicManager.music_player.seek(new_position)
		update_progress_bar(new_position)

func update_progress_bar(current_time: float):
	var track_duration = MusicManager.music_player.stream.get_length()
	start_timer_label.text = format_time(current_time)
	end_timer_label.text = format_time(track_duration)
	if track_duration > 0:
		music_progress_bar.value = (current_time / track_duration) * 100

func format_time(seconds: float) -> String:
	@warning_ignore("integer_division")
	var minutes = int(seconds) / 60
	var secs = int(seconds) % 60
	return "%02d:%02d" % [minutes, secs]

func _on_update_timer_timeout():
	if MusicManager.is_playing():
		var current_time = MusicManager.music_player.get_playback_position()
		update_progress_bar(current_time)

func _on_SkipTrack_pressed():
	if skip_track_cooldown:
		return
	
	skip_track_cooldown = true
	_play_button_click_sound()
	MusicManager.skip_and_shuffle()
	pause_button.text = "Pause"
	call_deferred("update_playlist_display")
	call_deferred("update_currently_playing_label")
	
	get_tree().create_timer(0.3).connect("timeout", func(): skip_track_cooldown = false)

func update_currently_playing_label():
	if MusicManager.is_playing():
		var current_track = MusicManager.playlist[MusicManager.current_track_index]
		var track_name = current_track.get_file().get_basename()
		currently_playing_label.text = "Now Playing: " + track_name
	else:
		currently_playing_label.text = "No track playing"

func _on_sidebar_button_pressed():
	_play_button_click_sound()
	update_playlist_display()
	tween2 = create_tween()
	var panel_start_x = 1  # X coordinate 
	var panel_start_y = 568 # Y coordinate 
	var panel_end_y = get_viewport_rect().size.y
	
	if sidebar_panel.position.y >= panel_end_y:
		tween2.tween_property(sidebar_panel, "position", Vector2(panel_start_x, panel_start_y), 0.3).set_ease(Tween.EASE_OUT)
	else:
		tween2.tween_property(sidebar_panel, "position", Vector2(panel_start_x, panel_end_y), 0.3).set_ease(Tween.EASE_IN)

func _on_pause_button_pressed():
	_play_button_click_sound()
	if MusicManager.music_player.stream:
		if MusicManager.music_player.stream_paused:
			MusicManager.music_player.stream_paused = false
			pause_button.text = "Pause"
			print("Music resumed")
		else:
			MusicManager.music_player.stream_paused = true
			pause_button.text = "Resume"
			print("Music paused")
	else:
		MusicManager.play()
		pause_button.text = "Pause"
		print("Music started playing")

Here is my MusicManager.gd (The autoload singleton that controls part of the music:)

#MusicManager.gd
extends Node

var music_player: AudioStreamPlayer
var is_initialized: bool = false
var playlist: Array = []
var current_track_index: int = -1

func skip_and_shuffle():
	shuffle_playlist()
	play_next_track()

func _enter_tree():
	if not is_initialized:
		music_player = AudioStreamPlayer.new()
		add_child(music_player)
		is_initialized = true
		print("MusicManager: Initialized")
		
		# Initialize the playlist
		playlist = ["my music"]
		# Shuffle the playlist
		shuffle_playlist()
		# Connect the finished signal
		music_player.connect("finished", Callable(self, "_on_track_finished"))

func shuffle_playlist():
	randomize()  # Initialize random number generator
	playlist.shuffle()
	current_track_index = -1  # Reset the index
	print("MusicManager: Playlist shuffled")

func set_volume(volume_db: float):
	if music_player:
		music_player.volume_db = volume_db

func play_next_track():
	current_track_index = (current_track_index + 1) % playlist.size()
	if current_track_index == 0:
		shuffle_playlist()
	var next_track = load(playlist[current_track_index])
	music_player.stream = next_track
	music_player.play()
	print("MusicManager: Playing track - ", playlist[current_track_index])
	get_tree().call_group("ui", "update_currently_playing_label")

func play():
	if not is_playing():
		play_next_track()
	else:
		print("MusicManager: Music already playing")

func stop():
	if music_player and music_player.playing:
		music_player.stop()
		print("MusicManager: Stopping music playback")

func is_playing() -> bool:
	return music_player and music_player.playing

func ensure_playing():
	if not is_playing():
		play()
	else:
		print("MusicManager: Music already playing, no action needed")

func _on_track_finished():
	play_next_track()

func play_specific_track(index: int):
	if index >= 0 and index < playlist.size():
		current_track_index = index
		var next_track = load(playlist[current_track_index])
		music_player.stream = next_track
		music_player.play()
		print("MusicManager: Playing track - ", playlist[current_track_index])
		get_tree().call_group("ui", "update_currently_playing_label")

Here is my SceneTree for UI.tscn:

Any help would be very much appreciated! :slight_smile: Thank you, and have a great night/day!
Let me know if you need any code snippets or SceneTrees! :smile:

Your update_playlist_display seems like a heavy function, the entire scroll container UI is hard. Have you checked it in the Profiler?

1 Like

I would move some of those variables to script scope instead of function/block scope. I think that helps with memory cache but anyone can correct me if I’m wrong.

You could also try putting the code to remove the extra children at the begining.

1 Like

Ok thank you so much, I just checked it in the profiler


It seems that format_time is making 29 calls per 1-2 clicks, and update_playlist_display seems to take 354 ms to load or something, but I will experiment with it and see what works, thank you again! :slight_smile:

1 Like

Ok so thank you so much! I tried putting the extra children in the beginning of the update_playlist_display and that seemed to do nothing, and i tried moving the script scope instead of the function/block scope but it didnt really seem to do anything, maybe it helped the memory cache i couldn’t seem to tell. But i will keep experimenting to see if anything happens, thanks again! :slight_smile:

1 Like

format time may have 29 calls, but it completes in 0.29ms, it’s a rather optimized function.

Your update_playlist_display is only called once yet it takes 354.39ms. If you split this function into parts it may be easier to tell where exactly that time comes from.

I would suggest limiting re-loading tracks, durations, and editing the scene tree.

As more concrete advice I recommend adding code timing, the profiler page has a section on Measuring manually in microseconds.

Here I’ve added prints after every few segments, you could look for a large gap in time inside the output log.

func update_playlist_display():
	var update_start := Time.get_ticks_usec()

	var playlist_size = MusicManager.playlist.size()
	var children = playlist_container.get_children()
	var child_count = children.size()

	print(Time.get_ticks_usec() - update_start, ": Got children")
	
	for i in range(playlist_size):
		var track_path = MusicManager.playlist[i]
		var track_name = track_path.get_file().get_basename()
		print(Time.get_ticks_usec() - update_start, ": Got track_name")

		var audio_stream = load(track_path)
		print(Time.get_ticks_usec() - update_start, ": loaded track ", track_path)

		var duration = audio_stream.get_length() if audio_stream else 0.0
		print(Time.get_ticks_usec() - update_start, ": calculated duration", track_path)
		
		var item: Button
		if i < child_count:
			item = children[i]
			print(Time.get_ticks_usec() - update_start, ": got existing button")
		else:
			item = Button.new()
			item.flat = true
			item.alignment = HORIZONTAL_ALIGNMENT_LEFT
			item.size_flags_horizontal = Control.SIZE_EXPAND_FILL
			playlist_container.add_child(item)
			print(Time.get_ticks_usec() - update_start, ": created new button")
		
		item.text = "%s - %s" % [track_name, format_time(duration)]
		print(Time.get_ticks_usec() - update_start, ": formatted time")
		item.set_meta("track_index", i)
		
		if not item.is_connected("pressed", Callable(self, "_on_playlist_item_pressed")):
			item.connect("pressed", Callable(self, "_on_playlist_item_pressed").bind(item))
		
		if i == MusicManager.current_track_index:
			item.add_theme_color_override("font_color", Color.YELLOW)
			item.add_theme_color_override("font_outline_color", Color.BLACK)
			item.add_theme_constant_override("outline_size", 2)
			print(Time.get_ticks_usec() - update_start, ": added theme override")
		else:
			item.remove_theme_color_override("font_color")
			item.remove_theme_color_override("font_outline_color")
			item.remove_theme_constant_override("outline_size")
			print(Time.get_ticks_usec() - update_start, ": removed theme override")

	print(Time.get_ticks_usec() - update_start, ": for playlist_size ended")
	
	# Remove excess children
	while child_count > playlist_size:
		var child = children[child_count - 1]
		playlist_container.remove_child(child)
		child.queue_free()
		child_count -= 1

	print(Time.get_ticks_usec() - update_start, ": removed excess children")
	
	playlist_scroll_container.queue_sort()
	print(Time.get_ticks_usec() - update_start, ": queued sort, function finished")
2 Likes

The one time I had an issue like this, I had created a memory leak. Every time I played a sound effect, I was spawning a new AudioPlayer and I wasn’t cleaning them up. I found this out by running my game and then clicking on the Remote tab on the Scene tab when I ran my game. It turned out I wasn’t cleaning them up because by the time I got around to that, I had lost the reference to the player, and so my queue+free() was doing nothing.

I took a look at your code, and I don’t see an obvious cause, but the effect you are seeing sound like a classic memory leak. I recommend you do exactly what you did in your video, but with your Remote tab showing and see what is getting added to your node tree.

1 Like

Thank you so much for trying to help! I am not sure if it is a memory leak because nothing seems to be duplicating or glitching out in the Remote tab, so it may not be a memory leak or it could be something being called too much, I have been experimenting with a bunch of things and nothing has seemed to work so far but i am always up for suggestions if needed, thank you so much again! :slight_smile:

1 Like

Thanks again! This is very helpful, here is what comes out in the debugger whenever I click a song in the playlist when your code is used:
This is when the game starts:


And here is all of the text that is shown in the output if you wanna see it:

MusicManager: Initialized
MusicManager: Playlist shuffled
SFX volume set to: 1 (0dB)
UI: Checking music status
MusicManager: Playlist shuffled
MusicManager: Playing track - res://Music/Lukrembo - Autumn.ogg
Gamepad scheme loaded.
8: Got children
23: Got track_name
7940: loaded track res://Music/Epic Spectrum - Wandering.ogg
7982: calculated durationres://Music/Epic Spectrum - Wandering.ogg
8612: created new button
8659: formatted time
9389: removed theme override
9418: Got track_name
29252: loaded track res://Music/Lukrembo - Everyday.ogg
29332: calculated durationres://Music/Lukrembo - Everyday.ogg
29654: created new button
29681: formatted time
30025: removed theme override
30043: Got track_name
42019: loaded track res://Music/SunsetDream.ogg
42070: calculated durationres://Music/SunsetDream.ogg
42381: created new button
42406: formatted time
42775: removed theme override
42797: Got track_name
62866: loaded track res://Music/LURE - The Inevitable Cycle of Procrastination.ogg
62929: calculated durationres://Music/LURE - The Inevitable Cycle of Procrastination.ogg
63247: created new button
63275: formatted time
63724: removed theme override
63746: Got track_name
81119: loaded track res://Music/Lukrembo - Break Up.ogg
81172: calculated durationres://Music/Lukrembo - Break Up.ogg
81471: created new button
81497: formatted time
81845: removed theme override
81864: Got track_name
92104: loaded track res://Music/Lukrembo - Cafe.ogg
92154: calculated durationres://Music/Lukrembo - Cafe.ogg
92448: created new button
92479: formatted time
92792: removed theme override
92807: Got track_name
103639: loaded track res://Music/Lukrembo - Apricity.ogg
103693: calculated durationres://Music/Lukrembo - Apricity.ogg
104014: created new button
104038: formatted time
104394: removed theme override
104412: Got track_name
113978: loaded track res://Music/Lukrembo - Bread.ogg
114031: calculated durationres://Music/Lukrembo - Bread.ogg
114378: created new button
114409: formatted time
114725: removed theme override
114741: Got track_name
130234: loaded track res://Music/David Renda - Vibes.ogg
130281: calculated durationres://Music/David Renda - Vibes.ogg
130592: created new button
130617: formatted time
130961: removed theme override
130978: Got track_name
143528: loaded track res://Music/Unknown Creator - Bobbin.ogg
143582: calculated durationres://Music/Unknown Creator - Bobbin.ogg
143901: created new button
143929: formatted time
144273: removed theme override
144291: Got track_name
155357: loaded track res://Music/Lukrembo - Chocolate.ogg
155417: calculated durationres://Music/Lukrembo - Chocolate.ogg
155749: created new button
155776: formatted time
156137: removed theme override
156156: Got track_name
168691: loaded track res://Music/Lukrembo - I Always Love You.ogg
168739: calculated durationres://Music/Lukrembo - I Always Love You.ogg
169037: created new button
169064: formatted time
169461: removed theme override
169480: Got track_name
180026: loaded track res://Music/Lukrembo - Branch.ogg
180076: calculated durationres://Music/Lukrembo - Branch.ogg
180409: created new button
180440: formatted time
180791: removed theme override
180807: Got track_name
191468: loaded track res://Music/David Renda - Lazy Day.ogg
191521: calculated durationres://Music/David Renda - Lazy Day.ogg
191824: created new button
191851: formatted time
192205: removed theme override
192222: Got track_name
200578: loaded track res://Music/Lukrembo - Daily.ogg
200626: calculated durationres://Music/Lukrembo - Daily.ogg
200920: created new button
200945: formatted time
201257: removed theme override
201272: Got track_name
212604: loaded track res://Music/Lukrembo - Tower.ogg
212647: calculated durationres://Music/Lukrembo - Tower.ogg
212952: created new button
212980: formatted time
213298: removed theme override
213314: Got track_name
223850: loaded track res://Music/Lukrembo - Imagine.ogg
223909: calculated durationres://Music/Lukrembo - Imagine.ogg
224396: created new button
224448: formatted time
224830: removed theme override
224850: Got track_name
235367: loaded track res://Music/Lukrembo - Afternoon.ogg
235413: calculated durationres://Music/Lukrembo - Afternoon.ogg
235747: created new button
235775: formatted time
236125: removed theme override
236144: Got track_name
246887: loaded track res://Music/Lukrembo - Sunset.ogg
246943: calculated durationres://Music/Lukrembo - Sunset.ogg
247257: created new button
247287: formatted time
247616: removed theme override
247636: Got track_name
259687: loaded track res://Music/David Renda - Down Days.ogg
259735: calculated durationres://Music/David Renda - Down Days.ogg
260070: created new button
260099: formatted time
260479: removed theme override
260497: Got track_name
285809: loaded track res://Music/Jarico - Island.ogg
285847: calculated durationres://Music/Jarico - Island.ogg
286129: created new button
286153: formatted time
286483: removed theme override
286502: Got track_name
310002: loaded track res://Music/Lukrembo - Kitchen.ogg
310049: calculated durationres://Music/Lukrembo - Kitchen.ogg
310378: created new button
310407: formatted time
310798: removed theme override
310824: Got track_name
322260: loaded track res://Music/Lukrembo - Onion.ogg
322311: calculated durationres://Music/Lukrembo - Onion.ogg
322692: created new button
322748: formatted time
323366: removed theme override
323437: Got track_name
342500: loaded track res://Music/David Cutter Music - Decay.ogg
342720: calculated durationres://Music/David Cutter Music - Decay.ogg
343426: created new button
343518: formatted time
344111: removed theme override
344160: Got track_name
369067: loaded track res://Music/Kevin MacLeod - Carefree.ogg
369144: calculated durationres://Music/Kevin MacLeod - Carefree.ogg
369569: created new button
369602: formatted time
369955: removed theme override
369972: Got track_name
391202: loaded track res://Music/Lukrembo - Forest.ogg
391277: calculated durationres://Music/Lukrembo - Forest.ogg
391710: created new button
391770: formatted time
392350: removed theme override
392387: Got track_name
401797: loaded track res://Music/Lukrembo - Waiting.ogg
401847: calculated durationres://Music/Lukrembo - Waiting.ogg
402144: created new button
402174: formatted time
402496: removed theme override
402512: Got track_name
412094: loaded track res://Music/Lukrembo - Holiday.ogg
412161: calculated durationres://Music/Lukrembo - Holiday.ogg
412478: created new button
412504: formatted time
412855: removed theme override
412874: Got track_name
413936: loaded track res://Music/Lukrembo - Autumn.ogg
413969: calculated durationres://Music/Lukrembo - Autumn.ogg
414219: created new button
414239: formatted time
414518: removed theme override
414528: for playlist_size ended
414534: removed excess children
414540: queued sort, function finished

And here is what happens when a song is clicked or I skip a song:

It shows 179 things in the debugger when one specific song is clicked.


And its the same for all of the songs I have (20+) and when a song is skipped it does same exact thing in debugger, but I will experiment with this lol.

Once again, thanks for helping! If you need anything else or have any suggestions, let me know! :slight_smile:

Seems like the biggest jumps is each time a track is loaded. Maybe you can avoid loading tracks until they are played?

23: Got track_name
7940: loaded track res://Music/Epic Spectrum - Wandering.ogg

7,917 us on loading

9418: Got track_name
29252: loaded track res://Music/Lukrembo - Everyday.ogg

19,834us on loading

42797: Got track_name
62866: loaded track res://Music/LURE - The Inevitable Cycle of Procrastination.ogg

20,069us on loading

These are massive hits for every track, loading everything every time the UI is toggled.

2 Likes

I got the issue fixed:

So for anyone who needs it for anyone who experiences this issue: I added the variables in MusicManager.gd:

var preloaded_streams: Array = []  # Separate array for preloaded streams
var streams_preloaded: bool = false

Then I updated my _enter_tree():

and added a new function named

preload_streams and put that in _enter_tree():

here is preload_streams:

func preload_streams():
	# Use a separate thread to preload streams to avoid main thread blocking
	var thread = Thread.new()
	thread.start(func():
		for track_path in playlist:
			var stream = load(track_path)
			preloaded_streams.append(stream)
		streams_preloaded = true
	)

then I updated play_next_track to use the streams_preloaded and the preloaded_streams functions

func play_next_track():
	current_track_index = (current_track_index + 1) % playlist.size()
	if current_track_index == 0:
		shuffle_playlist()
	
	# Use preloaded stream if available
	if streams_preloaded and preloaded_streams.size() > current_track_index:
		music_player.stream = preloaded_streams[current_track_index]
	else:
		music_player.stream = load(playlist[current_track_index])
	
	music_player.play()
	get_tree().call_group("ui", "update_currently_playing_label")

Then i also updated play_specific_track because it was giving lots of issues beforehand to use the streams_preloaded and the preloaded_streams functions:

func play_specific_track(index: int):
	if index >= 0 and index < playlist.size():
		current_track_index = index
		# Use preloaded stream if available
		if streams_preloaded and preloaded_streams.size() > index:
			music_player.stream = preloaded_streams[index]
		else:
			music_player.stream = load(playlist[current_track_index])
		
		music_player.play()
		get_tree().call_group("ui", "update_currently_playing_label")

Then in UI.gd

I updated my update_playlist_display to be a bit simpler and not run all the songs at once like that:

func update_playlist_display():
	var playlist_size = MusicManager.playlist.size()
	var children = playlist_container.get_children()
	var child_count = children.size()
	
	for i in range(playlist_size):
		var track_path = MusicManager.playlist[i]
		var track_name = track_path.get_file().get_basename()

		# Use preloaded stream for duration if possible
		var duration = 0.0
		if i < MusicManager.preloaded_streams.size():
			duration = MusicManager.preloaded_streams[i].get_length()
		else:
			var audio_stream = load(track_path)
			duration = audio_stream.get_length() if audio_stream else 0.0
		
		var item: Button
		if i < child_count:
			item = children[i]
		else:
			item = Button.new()
			item.flat = true
			item.alignment = HORIZONTAL_ALIGNMENT_LEFT
			item.size_flags_horizontal = Control.SIZE_EXPAND_FILL
			playlist_container.add_child(item)
		
		item.text = "%s - %s" % [track_name, format_time(duration)]
		item.set_meta("track_index", i)
		
		if not item.is_connected("pressed", Callable(self, "_on_playlist_item_pressed")):
			item.connect("pressed", Callable(self, "_on_playlist_item_pressed").bind(item))
		
		if i == MusicManager.current_track_index:
			item.add_theme_color_override("font_color", Color.YELLOW)
			item.add_theme_color_override("font_outline_color", Color.BLACK)
			item.add_theme_constant_override("outline_size", 2)
		else:
			item.remove_theme_color_override("font_color")
			item.remove_theme_color_override("font_outline_color")
			item.remove_theme_constant_override("outline_size")

	# Remove excess children
	while child_count > playlist_size:
		var child = children[child_count - 1]
		playlist_container.remove_child(child)
		child.queue_free()
		child_count -= 1
	
	playlist_scroll_container.queue_sort()

And now it runs smoothly while changing songs and skipping songs, thanks everybody for helping, have a great day/night! :slight_smile:

2 Likes