refactor: move music assets to config directory

This commit is contained in:
Dominik
2026-08-09 20:05:29 +02:00
parent 49dc82499c
commit ae12cf2e30
6 changed files with 68 additions and 32 deletions
+9 -4
View File
@@ -25,8 +25,9 @@ MusicTracks = {
The standalone `Music` app plays audio only inside the current player's NUI. It never creates a
world sound, voice channel, positional event, or 3D-audio state that another player can hear.
Server-owned MP3/OGG tracks live in `frontend/public/music`. Define their stable ID, title, artist,
file, and optional artwork in `sky_phone/config/music.lua`, then run `build_frontend.bat`:
Server-owned MP3/OGG tracks and their optional artwork live directly in
`sky_phone/config/music`. Define their stable ID, title, artist, and paths in
`sky_phone/config/music.lua`:
```lua
Tracks = {
@@ -34,12 +35,16 @@ Tracks = {
Id = "night-drive",
Title = "Night Drive",
Artist = "Sky Records",
File = "music/night-drive.ogg",
Artwork = "music/night-drive.webp",
File = "config/music/night-drive.ogg",
Artwork = "config/music/night-drive.webp",
},
}
```
No frontend build is needed when tracks change. Restart the resource so FiveM republishes the
files and reloads the track configuration. Keep existing track IDs stable because playlists store
those IDs.
Players can add public YouTube video links to their own library. Metadata is requested through
YouTube's oEmbed endpoint on the server, while playback uses the embedded YouTube player only on
that player's NUI. Personal songs and playlists are stored per linked iFruit account, or per phone
-6
View File
@@ -1,6 +0,0 @@
Place server-owned .mp3 and .ogg files in this directory.
Define each published song in sky_phone/config/music.lua, then run
build_frontend.bat so the files are copied into the deployable NUI.
Optional artwork can be stored here as .png, .jpg, .jpeg or .webp.
+4 -4
View File
@@ -1,13 +1,13 @@
Config.Music = {
-- Put MP3/OGG files in frontend/public/music and rebuild the frontend.
-- File and Artwork paths are relative to the published NUI root.
-- Put server-owned audio and artwork directly in config/music.
-- Paths must stay inside that directory. Restart the resource after changes.
Tracks = {
-- {
-- Id = "night-drive",
-- Title = "Night Drive",
-- Artist = "Sky Records",
-- File = "music/night-drive.ogg",
-- Artwork = "music/night-drive.webp",
-- File = "config/music/night-drive.ogg",
-- Artwork = "config/music/night-drive.webp",
-- },
},
+11
View File
@@ -0,0 +1,11 @@
Place server-owned .mp3 and .ogg files in this directory.
Optional artwork can be stored here as .png, .jpg, .jpeg or .webp. Register
each track in ../music.lua using paths such as:
File = "config/music/night-drive.ogg"
Artwork = "config/music/night-drive.webp"
Use file names containing only letters, numbers, dots, underscores or hyphens.
Subdirectories are supported. Restart the sky_phone resource after changing
tracks or files; a frontend build is not required.
+1 -1
View File
@@ -71,7 +71,7 @@ files {
'source/html/index.html',
'source/html/assets/**',
'source/html/img/**',
'source/html/music/**',
'config/music/**',
}
ui_page 'source/html/index.html'
+43 -17
View File
@@ -1,6 +1,9 @@
Bridge.Database.AfterMigration("sky_phone", function()
local server_tracks = {}
local server_tracks_by_id = {}
local music_asset_prefix = "config/music/"
local audio_extensions = { mp3 = true, ogg = true }
local artwork_extensions = { jpeg = true, jpg = true, png = true, webp = true }
local function trim(value)
return type(value) == "string" and value:match("^%s*(.-)%s*$") or nil
@@ -24,6 +27,29 @@ local function optional_text(value)
return normalized ~= "" and normalized or nil
end
local function normalize_music_asset_path(value, allowed_extensions)
local path = trim(value)
if not path
or path:sub(1, #music_asset_prefix) ~= music_asset_prefix
or not path:match("^[%w%._%-%/]+$")
or path:find("..", 1, true)
or path:find("//", 1, true)
or path:find("\\", 1, true)
then
return nil
end
local extension = path:match("%.([%w]+)$")
if not extension or not allowed_extensions[extension:lower()] then
return nil
end
return path
end
local function music_asset_url(path)
return ("https://cfx-nui-%s/%s"):format(GetCurrentResourceName(), path)
end
local function affected_rows(result)
if type(result) == "number" then
return result
@@ -45,25 +71,25 @@ local function normalize_server_tracks()
local id = trim(configured.Id)
local title = trim(configured.Title)
local artist = trim(configured.Artist)
local file = trim(configured.File)
local artwork = trim(configured.Artwork)
local valid_file = file
and file:match("^music/[%w%._%-%/]+%.[mM][pP]3$")
or file and file:match("^music/[%w%._%-%/]+%.[oO][gG][gG]$")
local title_length = text_length(title)
local artist_length = text_length(artist)
local file = normalize_music_asset_path(configured.File, audio_extensions)
local configured_artwork = optional_text(configured.Artwork)
local artwork = configured_artwork
and normalize_music_asset_path(configured_artwork, artwork_extensions)
or nil
if not id
or not id:match("^[%w%-_]+$")
or #id > 48
or not title
or not text_length(title)
or text_length(title) > 160
or not artist
or not text_length(artist)
or text_length(artist) > 120
or not valid_file
or (artwork and not artwork:match("^music/[%w%._%-%/]+%.[pP][nN][gG]$")
and not artwork:match("^music/[%w%._%-%/]+%.[jJ][pP][eE]?[gG]$")
and not artwork:match("^music/[%w%._%-%/]+%.[wW][eE][bB][pP]$"))
or not title_length
or title_length < 1
or title_length > 160
or not artist_length
or artist_length < 1
or artist_length > 120
or not file
or (configured_artwork and not artwork)
or server_tracks_by_id[id]
then
Bridge.Debug(
@@ -78,8 +104,8 @@ local function normalize_server_tracks()
source = "server",
title = title,
artist = artist,
url = file,
artwork = artwork,
url = music_asset_url(file),
artwork = artwork and music_asset_url(artwork) or nil,
}
server_tracks[#server_tracks + 1] = track
server_tracks_by_id[id] = track