Program ownership and synchronization
A Station Pack owns the local station catalog, deterministic order, identifiers, current program, metadata, and timestamps. Radio owns the vehicle, permissions, selection, power, volume, UI, and playback lifecycle; Sound renders the selected local media.
| Source | Media location | Synchronization model |
|---|---|---|
| Local GTA Radio | Game files | GTA-owned playback. |
| Satellite Radio | External live URL | Shared live stream. |
| Server Radio | Station Pack local files | Deterministic server program clock. |
| Bluetooth | Player-supplied link | Vehicle queue and transport state. |
A Server Radio station does not begin when the first player tunes in. With the broadcast clock active, the program is already running and late listeners seek into the current segment.
Do not place local station tracks in Radio's Config.Streams. That table is only for external Satellite Radio URLs; local media belongs to a separately ensured Station Pack.
Installation and Radio registration
ensure san_andreas_sound
ensure san_andreas_dispatch
ensure san_andreas_station_pack
ensure san_andreas_radioConfig.ServerRadio = {
Enabled = true,
AcousticClass = 'server_music',
VolumeMultiplier = 0.70,
ExteriorVolumeMultiplier = 0.75,
Resources = {
'san_andreas_station_pack'
}
}- Keep the exact san_andreas_station_pack folder name and start it before Radio so the initial catalog is discoverable.
- Keep fxmanifest.lua, config.lua, server/, audio/, artwork/, and ASSET_PROVENANCE.md together.
- The release manifest packages files directly beneath audio/ and artwork/. Add manifest patterns if custom media uses subfolders or additional supported extensions.
- When more than one pack is installed, append each exact folder name to Config.ServerRadio.Resources and keep every station ID unique across packs.
- The server validates the invoking configured resource and local media prefix; a pack cannot publish another resource's files or take ownership of its stations.
Prepare music, artwork, and durations
| Asset | Supported release formats | Preparation |
|---|---|---|
| Tracks and identifiers | MP3, M4A, OGG, WAV | Use lowercase ASCII filenames, consistent loudness with headroom, clean edges, and exact final durations. |
| Artwork | JPG, JPEG, PNG | A 3:2 landscape image such as 960x640 fits well; keep important content away from responsive crop edges. |
- Begin only with media that may be redistributed through the FiveM resource packfile.
- Trim unwanted silence and add very short fades where hard edges click.
- Keep perceived loudness consistent and preserve headroom; MP3 around 128 kbps at 44.1 or 48 kHz is a practical music balance.
- Test the loudest track at maximum managed Radio volume.
- Measure the final encoded file, not a source copy from before conversion, and retain the decimal seconds.
- Record creator, source, license, modification notes, and required attribution in ASSET_PROVENANCE.md.
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 audio/track-name.mp3Configure the broadcast clock
| Key | Type | Default | Description |
|---|---|---|---|
StationPackConfig.Debug | boolean | false | Adds station validation and scheduler logging. |
StationPackConfig.TickMs | number | 250 | Scheduler update interval. |
Broadcast.Enabled | boolean | true | Enables the continuous deterministic program clock. |
Broadcast.Anchor | Unix timestamp | 1767225600 | Stable program origin. |
Broadcast.Seed | integer | 9142026 | Stable deterministic station-order seed. |
Keep Anchor and Seed unchanged across normal restarts. The same values let the pack reconstruct the active track and offset rather than restarting from track one. Change either value only when intentionally beginning a different program rotation.
Define stations and tracks
{
id = 'SERVER_EXAMPLE_ROCK',
label = 'Vinewood Rock Works',
short = 'VRW',
genre = 'Server-owned rock',
accent = '#d95f4d',
artwork = 'artwork/vinewood-rock-works.jpg',
shuffle = true,
tracks = {
{
id = 'vrw:track-one',
artist = 'Artist Name',
title = 'Track Name',
file = 'audio/track-name.mp3',
duration = 214.437
}
}
}| Key | Type | Default | Accepted values | Required | Description |
|---|---|---|---|---|---|
id | string | — | Up to 64 uppercase letters, digits, underscore, or hyphen | Yes | Unique station identity. A SERVER_ prefix is recommended for clarity. |
label | string | — | — | Yes | Full player-facing station name. |
short | string | — | — | — | Compact source/display label. |
genre | string | — | — | — | Short station description. |
accent | #RRGGBB | — | — | — | Station color; invalid values use the pack fallback. |
artwork | relative path | — | — | — | Safe JPG, JPEG, or PNG path owned by this pack. |
shuffle | boolean | — | — | — | Deterministic shuffle when true; configured order when false. |
tracks | array | — | — | Yes | Non-empty array of validated track entries. |
| Key | Type | Default | Accepted values | Required | Description |
|---|---|---|---|---|---|
id | string | — | — | Yes | Unique inside the station. Globally descriptive IDs are easier to audit. |
artist | string | — | — | — | Optional but strongly recommended player-facing artist. |
title | string | — | — | Yes | Player-facing track title. |
file | relative path | — | — | Yes | Safe local MP3, M4A, OGG, or WAV path. |
duration | number | — | >= 1 second | Yes | Exact final encoded duration. |
Do not reuse an existing track ID for unrelated media unless it should intentionally remain the same logical catalog item. Stable descriptive IDs make revisions and support logs easier to interpret.
Schedule identifiers and multiple stations
Identifiers = {
Enabled = true,
EveryTracks = 2,
Randomize = true,
Clips = {
{
id = 'vrw:station-ident-01',
artist = 'Vinewood Rock Works',
title = 'Station Identification',
file = 'audio/vrw-ident-01.ogg',
duration = 3.428
}
}
}Identifiers, sweepers, and jingles are normal program segments inserted between songs, not overlays. This keeps every client on one synchronized source.
EveryTracks must be at least one and no greater than the music-track count. Every identifier needs its own ID, metadata, safe local file, and exact duration. Randomize uses deterministic seeded order.
Add more station tables to the same Stations array, or configure multiple pack resources in Radio. Station IDs must be unique across all configured packs; each pack must expose the same public server contract.
Synchronization and volume balance
Radio requests station snapshot
-> receives active track and shared timestamp
-> creates the vehicle sound
-> seeks to the calculated offset
-> fades to the intended volume- Normalize track loudness and retain headroom before changing configuration gain.
- Test the loudest track at full managed head-unit volume.
- Lower Config.ServerRadio.VolumeMultiplier when local stations overpower Satellite Radio inside the vehicle.
- Lower ExteriorVolumeMultiplier when local media overdrives exterior bass or filtering.
- Do not raise the global Sound profile to compensate for one quiet file.
For each station, the server derives a deterministic sequence from the track list, shuffle flag, Seed, Anchor, and exact durations. A snapshot identifies the current segment, start time, offset, revision, metadata, and local media URL.
Public server API
GetStations
exportServerReturn copied UI-safe station definitions.
GetStations()- Returns
- station definition array
GetSnapshot
exportServerReturn current synchronized source data by station ID.
GetSnapshot()- Returns
- station-source map
GetStation
exportServerReturn one copied station definition or false.
GetStation(stationId)- Returns
- station definition | false
san_andreas_radio:server:stationPackChanged
eventServerLocal server event emitted after the active program segment advances.
(stationId, source)| Object | Fields |
|---|---|
| Station definition | id, label, short, genre, accent, artwork, streamed=true, server=true |
| Current source | stationId, local url, duration, startedAt, offset, revision, loop=false, syncGroup, track |
| Track metadata | id, artist, title, source='server', kind ('track' or 'identifier') |
A custom compatible pack must publish network-safe metadata and browser-playable local https://cfx-nui-... URLs that it owns. It never calls an audio provider or mutates vehicle state.
Verification, troubleshooting, and distribution
- Start Sound, Station Pack, and Radio in order and open the Server Radio source.
- Confirm every station's label, description, accent, artwork, artist, and title.
- Tune one vehicle, wait at least 20 seconds, then tune a second and compare offsets.
- Restart only the Station Pack; unchanged Anchor and Seed should reconstruct the current program.
- Listen across several music and identifier boundaries for silence, overlap, clicks, metadata delay, or restart at zero.
- Test maximum managed volume inside and outside, then verify radio-off, vehicle deletion, and resource-stop cleanup.
The Server Radio source does not appear.
Likely causes
- Pack not running before Radio
- Folder missing from Resources
- Pack exports invalid
Checks
- Check Config.ServerRadio.Enabled and exact name
- Inspect pack and Radio consoles
Expected result
Radio discovers every valid configured pack and shows its stations.
Metadata advances but audio is silent.
Likely causes
- Local file missing or unpackaged
- Codec unsupported
- Provider failed
Checks
- Check exact path/case and manifest pattern
- Inspect Sound diagnostics
Expected result
The local CFX NUI URL starts through the selected provider.
A late listener starts at zero or stations drift after restart.
Likely causes
- Incorrect duration
- Anchor or Seed changed
- Provider seek unavailable
Checks
- Measure the final files
- Restore stable clock values
- Inspect snapshot offset and Sound start
Expected result
All listeners enter the current program offset before and after restarts.
Track boundaries pop or occur early/late.
Likely causes
- Hard source-file edge
- Full-scale peak
- Duration mismatch
Checks
- Trim/fade final files
- Normalize loudness
- Re-measure duration
Expected result
Boundaries are clean and align with metadata.
The client download is excessive.
Likely causes
- High bitrate or uncompressed media
- One oversized pack contains optional libraries
Checks
- Measure total resource size
- Encode efficiently
- Split optional formats into separate packs
Expected result
Every connecting client downloads only intentional, practical media.