Files
MusicPlayer/data/playlist.gd
T
Sansan 222d231b1b
Build / test (push) Successful in 1m2s
Build / build (push) Successful in 1m12s
Add offline mode: play from a music folder instead of the server
Settings has a Music folder row. A folder with subfolders is played as
soundtracks: Next picks a random subfolder and a random song in it, the
subfolder is the answer's soundtrack and the file name without its type the
song. The inspiration list and Search show the subfolders.

A folder with songs and no subfolders is a prepared quiz, played once in order
(natural sort) or at random, showing only the song title. It has no winning
score: after the last song Next becomes Show winner, and the players with the
most points win, ties included.

While a folder is used nothing but the characters is asked from the server:
Sync, QR, Statistics, the update check and play counting are off. Characters
fall back to the cached pictures when the server can't be reached. mp3, ogg and
wav play; other files and broken ones are skipped.

The old local playback code, the hidden Open button and the hidden Local toggle
are removed. Next now waits for the song to start before preparing the next one.
2026-10-07 23:07:47 +02:00

186 lines
5.6 KiB
GDScript

extends Node
## The songs of the current quiz, in the order they were added, and which of them are
## played, answered and playing.
##
## Registered as the [code]Playlist[/code] autoload.
##
## Downloaded songs are kept on disk in the [code]songs[/code] folder next to the
## settings, not in memory. The folder only holds the current playlist: it is emptied when
## the game starts and when the playlist is reset.
const SONGS_FOLDER_NAME: String = "songs"
var song_object_array: Array[SongObject] = []
var latest_played_song: int = 0
var currently_playing_song: int = -1
var first_song_downloaded: bool = false
var first_song_played: bool = false
var number_of_tracks_to_preload: int = 1
var is_song_downloading: bool = false
## The first song of the current game. Songs before it belong to earlier games, which
## ended with Reset points.
var game_start_song: int = 0
# How many song files have been written, so each gets a name of its own
var _song_files_written: int = 0
func _ready() -> void:
clear_song_files()
## The folder downloaded songs are kept in.
static func songs_folder() -> String:
return Settings.config_dir().path_join(SONGS_FOLDER_NAME)
func get_next_song() -> SongObject:
return song_object_array[latest_played_song]
func set_latest_played_song() -> void:
latest_played_song += 1
if latest_played_song > song_object_array.size() - 1:
latest_played_song = song_object_array.size() - 1
func update_last_played_song() -> void:
latest_played_song = song_object_array.size()
func get_preloaded_songs() -> int:
var number_of_tracks_preloaded: int = 0
for song: SongObject in song_object_array:
if !song.has_played:
number_of_tracks_preloaded += 1
return number_of_tracks_preloaded
func should_preload_more_songs() -> bool:
return number_of_tracks_to_preload > get_preloaded_songs()
func song_is_answered(song_no: int = currently_playing_song) -> void:
# Nothing is playing yet (-1); a negative index would answer the last, unplayed song
if song_no < 0 || song_no >= song_object_array.size():
return
GameLog.debug("show_answer_pressed song_object: ", song_object_array[song_no]._to_string())
song_object_array[song_no].is_answered = true
if Settings.add_to_stats && !Settings.is_offline():
GameLog.debug("add to stats: ", song_no)
MusicServerClient.mark_song_played(song_no)
func add_to_playlist(new_song_object: SongObject) -> void:
song_object_array.append(new_song_object)
func get_playlist() -> Array[SongObject]:
if Settings.is_debug:
return song_object_array
else:
var temp_list: Array[SongObject] = []
for s: SongObject in song_object_array:
if s.has_played:
temp_list.append(s)
return temp_list
func reset_playlist() -> void:
currently_playing_song = -1
latest_played_song = 0
game_start_song = 0
clear_song_list()
clear_song_files()
func clear_song_list() -> void:
song_object_array.clear()
func unset_is_playing() -> void:
for song_object: SongObject in song_object_array:
song_object.is_playing = false
func set_currently_playing_song(value: int) -> void:
currently_playing_song = value
func get_current_soundtrack_title() -> String:
return song_object_array[currently_playing_song].get_soundtrack_title()
func get_current_song_title() -> String:
return song_object_array[currently_playing_song].get_song_title()
func add_point(player_id: String) -> void:
song_object_array[currently_playing_song].add_point(player_id)
func remove_point(player_id: String) -> void:
song_object_array[currently_playing_song].remove_point(player_id)
## Removes every player's points from every song, for a new game.
func clear_points() -> void:
for song_object: SongObject in song_object_array:
song_object.players_given_point.clear()
## Removes a player's points from every song.
func remove_player_from_list(player_id: String) -> void:
for song_object: SongObject in song_object_array:
song_object.remove_point(player_id)
## The song of [param song_no], read from its file. Returns null if the file is gone, so
## the song can be downloaded again.
func get_song(song_no: int) -> AudioStream:
var song_object: SongObject = song_object_array[song_no]
if song_object.song_path.is_empty():
return song_object.song
return AudioFile.load(song_object.song_path)
func add_song_to_song_object(song_no: int, mp3: PackedByteArray) -> void:
store_song(song_object_array[song_no], mp3)
## Keeps a downloaded song in a file in [method songs_folder]. If the file can't be
## written, the song is kept in memory instead.
func store_song(song_object: SongObject, mp3: PackedByteArray) -> void:
DirAccess.make_dir_recursive_absolute(songs_folder())
var path: String = songs_folder().path_join("song_%d.mp3" % _song_files_written)
_song_files_written += 1
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
if file == null:
GameLog.warning("Could not save a song to ", path, ", keeping it in memory: ",
error_string(FileAccess.get_open_error()))
var sound: AudioStreamMP3 = AudioStreamMP3.new()
sound.data = mp3
song_object.song = sound
song_object.song_path = ""
return
file.store_buffer(mp3)
file.close()
song_object.song = null
song_object.song_path = path
## Deletes the files of the songs downloaded so far.
func clear_song_files() -> void:
if !DirAccess.dir_exists_absolute(songs_folder()):
return
for file_name: String in DirAccess.get_files_at(songs_folder()):
DirAccess.remove_absolute(songs_folder().path_join(file_name))
func song_has_played(song_no: int = latest_played_song) -> void:
song_object_array[song_no].has_played = true
func song_is_playing(song_no: int = latest_played_song) -> void:
song_object_array[song_no].is_playing = true