Closes #38, #39 and #40; adds the client side of #41. - Every request goes to /api/v1. MusicServerClient gets a token with Settings.api_password the first time one is needed, reuses it, and on a 401 gets a new one and sends the request once more. Switching server gets a new token too. Callers never see the token. - Sync is started with POST, as MusicServer 5.0 requires. - The client has every endpoint in the server's OpenAPI spec, including statistics, soundtracks and songs by UUID, version, downloads and tokens. - tools/generate_api_types.gd generates a typed class per schema in server/openapi.json into server/generated/. Song info, statistics and versions are passed to callers as these classes. A list the server sends as null is an empty list. - FakeHttpTransport hands out tokens and answers 401 without one, so every test goes through the token flow. New tests cover the token handling, every endpoint and each generated class.
99 lines
3.7 KiB
GDScript
99 lines
3.7 KiB
GDScript
class_name SongLoader
|
|
extends Node
|
|
## Downloads songs from the server into the Playlist ahead of time, so the next song
|
|
## can start at once.
|
|
##
|
|
## How many songs it keeps ready is the cache size in the settings
|
|
## ([code]Playlist.number_of_tracks_to_preload[/code]).
|
|
|
|
## Emitted when the songs already in the server playlist have been loaded into the
|
|
## Playlist.
|
|
|
|
signal song_list_loaded
|
|
|
|
## Emitted when a new song has been downloaded and added to the Playlist.
|
|
signal song_added
|
|
|
|
## Seconds to wait before retrying a download step that failed.
|
|
var retry_seconds: float = 5.0
|
|
|
|
|
|
## Loads the songs already in the server playlist, then starts preloading new ones.
|
|
## Preloading waits for the list, because loading the list clears the playlist.
|
|
func load_song_list() -> void:
|
|
GameLog.debug("load_song_list")
|
|
var song_list_fetched: Callable = func song_list_fetched(songs: Array[SongInfo]) -> void:
|
|
Playlist.clear_song_list()
|
|
for info: SongInfo in songs:
|
|
var new_song_object: SongObject = SongObject.new()
|
|
new_song_object.song_number = info.song_no
|
|
new_song_object.soundtrack_title = info.soundtrack
|
|
new_song_object.song_title = info.song
|
|
new_song_object.is_answered = true
|
|
new_song_object.has_played = true
|
|
Playlist.add_to_playlist(new_song_object)
|
|
Playlist.update_last_played_song()
|
|
song_list_loaded.emit()
|
|
fetch_song_if_idle()
|
|
MusicServerClient.fetch_song_list(song_list_fetched, _retry_later.bind(load_song_list))
|
|
|
|
|
|
## Starts downloading one more song, which keeps downloading until the cache is full.
|
|
func fetch_song() -> void:
|
|
GameLog.debug("fetch_song")
|
|
Playlist.is_song_downloading = true
|
|
_download_random_song(SongObject.new())
|
|
|
|
|
|
## Starts downloading a song unless one is already downloading.
|
|
func fetch_song_if_idle() -> void:
|
|
if !Playlist.is_song_downloading:
|
|
fetch_song()
|
|
|
|
|
|
## Starts downloading if nothing is downloading and the cache is not full, e.g. after
|
|
## the cache size was raised.
|
|
func fill_cache() -> void:
|
|
if !Playlist.is_song_downloading && Playlist.should_preload_more_songs():
|
|
fetch_song()
|
|
|
|
|
|
# Fetching a song takes three requests. If one fails, only that step is retried, so
|
|
# the server and the game keep the same songs in their playlists.
|
|
func _download_random_song(new_song_object: SongObject) -> void:
|
|
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
|
GameLog.debug("song_downloaded")
|
|
var sound: AudioStream = AudioStreamMP3.new()
|
|
sound.data = body
|
|
new_song_object.song = sound
|
|
_add_song_to_queue(new_song_object)
|
|
MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded,
|
|
_retry_later.bind(_download_random_song.bind(new_song_object)))
|
|
|
|
|
|
func _add_song_to_queue(new_song_object: SongObject) -> void:
|
|
MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object),
|
|
_retry_later.bind(_add_song_to_queue.bind(new_song_object)))
|
|
|
|
|
|
func _fetch_song_info(new_song_object: SongObject) -> void:
|
|
var song_info_fetched: Callable = func song_info_fetched(info: SongInfo) -> void:
|
|
GameLog.debug("song_info_fetched")
|
|
new_song_object.song_number = info.song_no
|
|
new_song_object.soundtrack_title = info.soundtrack
|
|
new_song_object.song_title = info.song
|
|
GameLog.debug("new_song_object: ", new_song_object.to_string())
|
|
Playlist.add_to_playlist(new_song_object)
|
|
if Playlist.should_preload_more_songs():
|
|
fetch_song()
|
|
else:
|
|
Playlist.is_song_downloading = false
|
|
song_added.emit()
|
|
MusicServerClient.fetch_song_info(song_info_fetched,
|
|
_retry_later.bind(_fetch_song_info.bind(new_song_object)))
|
|
|
|
|
|
func _retry_later(step: Callable) -> void:
|
|
GameLog.warning("Song download failed, retrying in %s seconds" % retry_seconds)
|
|
get_tree().create_timer(retry_seconds).timeout.connect(step)
|