Technical reference

Developer integration guide

Choose supported integration seams, reason about synchronization and authority, extend providers, and apply safe client/server patterns.

Resource
San Andreas Sound Suite
Version
Sound 1.5.16 · Dispatch 1.0.0 · Radio 1.0.0
Updated
Updated August 21, 2026
Resource GitHub
Browse documentationSan Andreas Sound Suite
Guide pagesDeveloper guide
Sections
01 / Integration map

Choose a supported seam

GoalSupported seamContextAvoid
Play or control one positional soundSound native client exportsClient LuaInternal Sound NUI callbacks.
Ask a specific player client to play audioSound target-first server exportsServer LuaUnchecked or nil target values, which become broadcast -1.
Observe synchronized vehicle Radio stateGetVehicleRadioStateServer LuaRadio internal mutation network events.
Consume a Dispatch channelDispatch getters and local transmission eventServer LuaDirect access to scheduler internals.
Read Station Pack statePack getters and local change eventServer LuaCalculating separate program offsets on clients.
Add an audio backendRadio provider registry inside provider filesRadio source extensionTreating the registry as a cross-resource export.
Add PMA-style scanner voiceRadio voice provider registry/configRadio provider files and server configJoining scanner listeners to the talker's PMA channel.
Generate content ZIPsContent Builder UILocal desktop browserTreating loopback HTTP routes as a stable SDK.

Use client exports when the caller already runs on the player who should hear the sound. Use server relays only after the server validates recipients and input. A client entity handle has meaning only on that client; synchronize a network ID and resolve it independently.

A successful Sound server export confirms only that a client event was emitted. It cannot prove that the target exists, the URL loads, the codec is supported, or playback became audible. Use a client callback or receiving-client diagnostics when confirmation matters.

Supported public surfaces are the documented exports, local Dispatch/Station Pack events, and source-level Radio provider registries. Sound/Radio NUI callbacks, most Radio network events, and Sound transport events are internal implementation details.

02 / Architecture

Authority and data flow

OwnerAuthoritative for
san_andreas_radioVehicle eligibility, seat permissions, source/station, power, volume intent, Bluetooth transport, playlist advancement, accessory state, and scanner power.
san_andreas_dispatchActive local clip, exact duration, revision, start time, offset, gaps, and public channel metadata.
san_andreas_station_packStation definitions, deterministic program order, active segment, exact timing, and local media ownership.
san_andreas_soundCaller-requested rendering, relationship/acoustic processing, transport, diagnostics, and local cleanup.
Vehicle audio data flowtext
Player input and NUI
        |
        v
san_andreas_radio client
        | validated requests
        v
san_andreas_radio server <--- snapshots/events --- san_andreas_dispatch
        | authoritative vehicle state
        v
Radio client audio adapter
        |
        +--- san_andreas_sound provider (default)
        +--- xSound provider or another registered backend

The server synchronizes media descriptors and timestamps rather than audio bytes. Clients load and render media locally. A vehicle network ID crosses the network; a local entity handle does not.

This separation prevents the renderer from deciding station order or permissions, lets Radio switch providers without rewriting lifecycle code, and allows other resources to use Sound independently.

03 / Timing

Synchronization model

Radio's server validates the player, actual vehicle, seat, vehicle eligibility, source, station or URL, and rate limit before changing shared state. Only a validated driver publishes native GTA track metadata.

Seekable Station Pack tracks and local Dispatch clips have known durations and start times. A client calculates the current offset and seeks after media readiness. Bluetooth stores a base position, startedAt time, play state, and revision.

Most live streams cannot be meaningfully seeked. With SyncSatelliteStreams enabled, Radio provides a stable syncGroup. Sound can fan one matching live decode into separate vehicle spatial branches; providers may ignore the hint, in which case buffer offsets are possible.

Each client resolves entityNetId to its own handle. entityHandle is only a local optimization and must never cross a server boundary.

Every component cleans up only what it owns. A diagnostic or stop handler must never stop unrelated resources or issue broad native resets that can affect fuel, phone, lighting, or other vehicle systems.

SourceSeek behaviorShared identity
Local GTA RadioGTA-ownedSynchronized station/power and validated track metadata.
Satellite live streamNormally non-seekableMatching URL plus syncGroup when supported.
Station Pack segmentSeekable by exact offsetStation snapshot revision and deterministic clock.
BluetoothSeekable when renderer supports itVehicle state, startedAt, base position, and revision.
Dispatch clipSeekable by exact offsetOne channel descriptor shared by every listener.
04 / Client pattern

Native Sound integration pattern

fxmanifest.lualua
fx_version 'cerulean'
game 'gta5'
dependency 'san_andreas_sound'
client_script 'client.lua'
client.lualua
local RESOURCE = GetCurrentResourceName()
local ownedSounds = {}

local function playVehicleAlert(vehicle)
    local netId = NetworkGetNetworkIdFromEntity(vehicle)
    local name = ('%s:alert:%d'):format(RESOURCE, netId)
    ownedSounds[name] = true

    return exports.san_andreas_sound:PlayPositional({
        name = name,
        url = 'https://cdn.example.com/authorized-alert.ogg',
        volume = 0.35,
        entityHandle = vehicle,
        entityNetId = netId,
        maxDistance = 55.0,
        trunkAffectsEnclosure = true,
        relationshipOverrides = {
            inside = { volumeMultiplier = 0.65, maxDistance = 25.0 },
            outside = { volumeMultiplier = 1.0, maxDistance = 55.0 }
        },
        loop = false,
        destroyOnFinish = true,
        onPlayStart = function(info)
            print(('started %s'):format(info.id))
        end
    })
end

AddEventHandler('onClientResourceStop', function(resourceName)
    if resourceName ~= RESOURCE then return end
    for name in pairs(ownedSounds) do
        exports.san_andreas_sound:Destroy(name)
    end
end)
  • Use stable resource-prefixed sound names to prevent collisions and make cleanup ownership auditable.
  • Supply both the local handle and network ID only when the caller already owns the client entity; the network ID is the shared contract.
  • Relationship override profile names normalize camel/Pascal aliases. Each override can set volumeMultiplier from 0.0-2.0 and maxDistance of at least 0.1.
  • PlayPositional returning true means local validation queued the request. Use onPlayStart for renderer confirmation.
  • Destroy only names owned by the current resource when that same resource stops.
05 / Server pattern

Target one validated client

server.lualua
RegisterNetEvent('my_resource:server:requestNotice', function(x, y, z)
    local target = tonumber(source)
    if not target or target <= 0 then return end

    -- Validate authorization and coordinates for this resource here.
    exports.san_andreas_sound:PlayUrlPos(
        target,
        ('my_resource:notice:%d'):format(target),
        'https://cdn.example.com/authorized-notice.ogg',
        0.30,
        {
            x = tonumber(x) or 0.0,
            y = tonumber(y) or 0.0,
            z = tonumber(z) or 0.0
        },
        false,
        { maxDistance = 30.0, destroyOnFinish = true }
    )
end)

Server relays can serialize position, entityNetId, relationship options, and the documented playback fields. They cannot send callback functions or a client-local entity handle.

06 / Server consumers

Consume Dispatch, Station Pack, and Radio state

Dispatch without Radioserver.lualua
local tx = exports.san_andreas_dispatch:GetChannel('police')
if tx and tx.offset < tx.duration then
    print(tx.label, tx.url, tx.offset, tx.duration)
end

AddEventHandler(
    'san_andreas_dispatch:server:transmissionChanged',
    function(channelId, descriptor)
        if channelId ~= 'police' then return end
        -- Choose recipients and relay only required fields.
    end
)
Inspect a Station Pack programserver.lualua
for _, station in ipairs(
    exports.san_andreas_station_pack:GetStations()
) do
    print(station.id, station.label)
end

local snapshot = exports.san_andreas_station_pack:GetSnapshot()
local source = snapshot['SERVER_VINEWOOD_DEMO']
if source and source.track then
    print(source.track.kind, source.track.artist, source.track.title, source.offset)
end
Read Radio state without mutating itserver.lualua
local state = exports.san_andreas_radio:GetVehicleRadioState(vehicleNetId)
if not state then return end

local summary = {
    powered = state.powered == true,
    mode = state.mode,
    station = state.station,
    volume = state.volume,
    revision = state.revision
}

if state.bluetooth then
    local position = state.bluetooth.position or 0.0
    if state.bluetooth.playing then
        position = position + math.max(0, os.time() - state.bluetooth.startedAt)
    end
    summary.bluetoothPosition = position
end
  • Dispatch's local event marks transmission start only. offset >= duration means the channel is in its synchronized silent gap.
  • Station Pack getters return safe copied definitions and current program sources.
  • Radio returns a raw internal state table. Copy required fields immediately; do not mutate or retain it and do not call internal player-authenticated events as setters.
  • If a server-owned feature needs a Radio mutation, add a validated public server export to Radio and document its authority rules rather than bypassing validation.
07 / Provider extension

Add a Radio audio provider

Radio core calls SanAndreasRadioAudio only. Provider-specific normalization belongs in client/audio/providers and server/audio/providers, loaded before Radio core scripts.

Client provider interface

registerProvider

functionClient

Register the provider table under the configured lowercase provider name.

Signaturelua
SanAndreasRadioAudio.registerProvider(name, provider)

isAvailable

functionClient

Report whether the selected provider can be used.

Signaturelua
isAvailable()

isProviderResource

functionClient

Identify whether a resource lifecycle event belongs to this provider.

Signaturelua
isProviderResource(resourceName)

playPositional

functionClient

Replace a same-name sound and apply its initial distance atomically.

Signaturelua
playPositional(options)
  • Call options.onPlayEnd only after non-looping natural completion.

destroy

functionClient

Remove one provider-owned sound.

Signaturelua
destroy(name)

setPosition

functionClient

Move a provider sound.

Signaturelua
setPosition(name, position)

setDistance

functionClient

Update its audible radius.

Signaturelua
setDistance(name, distance)

setVolume

functionClient

Update provider volume intent.

Signaturelua
setVolume(name, volume)

reset

functionClient

Optional provider cleanup hook.

Signaturelua
reset()

setTime

functionClient

Optional seek operation. Return exact false when unsupported.

Signaturelua
setTime(name, position)

setPlaying

functionClient

Optional transport operation. Return exact false when unsupported.

Signaturelua
setPlaying(name, playing)

setSpectrumTarget

functionClient

Optional spectrum callback registration. Return exact false when unsupported.

Signaturelua
setSpectrumTarget(name, callback, slot)
Client provider capability
FieldType and effect
supportsRelationshipOverridesBoolean. When true, Radio delegates relationship shaping to the provider instead of applying it itself.

Server provider interface

registerProvider

functionServer

Register the matching server provider table under the same name used on the client.

Signaturelua
SanAndreasRadioAudio.registerProvider(name, provider)

isAvailable

functionServer

Report provider readiness to the Radio server adapter.

Signaturelua
isAvailable()

isProviderResource

functionServer

Identify provider lifecycle ownership.

Signaturelua
isProviderResource(resourceName)
  1. Audit which Radio adapter operations the backend genuinely supports.
  2. Create matching client and server provider files and register the same provider name on both sides.
  3. Normalize provider-specific options inside the adapter without changing Radio station or lifecycle code.
  4. Load provider files before core scripts, map the provider resource in Config.Audio.Resources, and select Config.Audio.Provider.
  5. Test same-name replacement, initial distance, seek, pause/resume, natural completion, destroy, resource restart, vehicle deletion, and two-client synchronization.
  6. Document unsupported optional operations and return false rather than pretending success.
08 / Voice provider

Extend the live scanner voice provider

Client voice provider

isAvailable

functionClient

Report client provider availability.

isProviderResource

functionClient

Identify provider resource lifecycle events.

addRecipients

functionClient

Apply the receive-only recipient routing selected by Radio.

setTalkerActive

functionClient

Update one talker's active voice path.

reset

functionClient

Optional provider cleanup.

Server voice provider

isAvailable

functionServer

Report provider availability.

isProviderResource

functionServer

Identify lifecycle ownership.

getPlayerRadioChannel

functionServer

Read the player's current radio channel for mapped talker and duplicate-path checks.

getPlayerCallChannel

functionServer

Read the call channel for duplicate-path avoidance.

isPlayerInRadioChannel

functionServer

Test whether a listener already shares the talker channel.

The voice registry is an internal Radio source-extension seam, not a public cross-resource export. Preserve receive-only routing: listeners must not be joined to or transmit on the monitored channel.

09 / API index

Public API index by component

ComponentPublic surfaceCanonical detail
Sound clientPlayPositional, PlayUrlPos, PlayUrl, destroy/mutations, transport, spectrum, readiness, diagnostics, and streamer-mode exports plus three integration events.Sound component page: Native client API and callbacks.
Sound serverTarget-first play, destroy, position, entity network ID, distance, volume, timestamp, playing, pause, and resume relays.Sound component page: Server relay API.
xSound compatibilityPlayback, mutations, information getters, callbacks, fades, state event, and completion event on the documented tested surface.Sound component page: Legacy compatibility.
InteractSound compatibilityCL/SV PlayOnOne, PlayOnAll, PlayWithinDistance variants, PlayOneShot, and StopOneShot.Sound component page: Legacy compatibility and security warning.
DispatchGetSnapshot, GetChannel, GetCatalogCounts, and local transmissionChanged event.Dispatch component page: Public server API.
Station PackGetStations, GetSnapshot, GetStation, and local stationPackChanged event.Station Pack component page: Public server API.
RadioGetVehicleRadioState only.Radio component page: Public cross-resource contract.
10 / Production readiness

Security, privacy, and operations

  • Keep Bluetooth HTTP and direct URLs disabled unless the server has an explicit trusted requirement. Restrict hosts and length, reject credentials and control characters, and revalidate everything on the server.
  • Retain Radio state-change and metadata rate limits and test repeated rejected requests as well as normal use.
  • Before replacing Dispatch media, listen to every file and document permission, privacy review, source, review date, license, and a recoverable staging plan.
  • Distinguish private server playback, rebroadcasting, inclusion in a downloadable resource, and public redistribution; permission for one does not imply the others.
  • Treat successful Builder validation as a packaging result, never proof of ownership, consent, privacy compliance, or runtime playback.
  1. Use at least two clients and two vehicles on a staging server.
  2. Exercise all direct, fallback, native, server-hosted, Bluetooth, scanner, and optional voice paths.
  3. Move between world/inside/outside/other-vehicle profiles, doors/windows/trunk, boats, and occlusion while inspecting diagnostics.
  4. Stop and start each component independently and confirm no unrelated resource is stopped and no audio remains orphaned.
  5. Measure client/server frame time, bandwidth, NUI memory, active sound count, renderer mode, and errors in an area with representative nearby vehicles.
A stream works but is not spatial.

Likely causes

  • CORS forced HTML fallback
  • The source is YouTube

Checks

  1. Inspect renderer and source permission
  2. Use an authorized direct Web Audio endpoint

Expected result

Compatible direct sources report webaudio; fallback limitations remain explicit.

Nearby vehicles drift on one Satellite station.

Likely causes

  • Provider ignored syncGroup
  • URL/group differs
  • HTML fallback

Checks

  1. Confirm SyncSatelliteStreams, identical URL/group, and renderer

Expected result

Compatible providers share one live transport with separate spatial branches.

Dispatch or Bluetooth begins too loudly or advances incorrectly.

Likely causes

  • Startup fade removed
  • Initial provider gain applied too early
  • onPlayEnd called on replacement

Checks

  1. Restore ScannerStartupFadeMs
  2. Review provider atomic start and natural-completion semantics

Expected result

Dispatch fades after seek; Bluetooth advances exactly once after natural completion.

A diagnostic experiment causes skipping or duplicates.

Likely causes

  • Temporary owned sounds remain
  • The stack restarted mid-experiment

Checks

  1. Destroy only experiment names
  2. Remove the test resource
  3. Restart only affected Suite components

Expected result

Normal playback resumes without broad resets or unrelated resource impact.

11 / Reference

Terminology and source paths

TermMeaning in the Suite
CEFFiveM's embedded Chromium browser used by NUI and media.
CORSBrowser permission that determines whether a media source exposes samples to Web Audio.
DescriptorSynchronized URL, start time, duration, revision, offset, and public metadata.
Entity handleClient-local GTA entity reference; never relay between clients.
Entity network IDNetwork-safe identifier resolved independently on each client.
HRTFHeadphone-oriented directional 3D panning.
OcclusionAdditional gain/filter shaping when geometry blocks a source.
RevisionChanging value used to reject stale or duplicate state.
syncGroupProvider hint that matching non-seekable live media can share one client connection.
TaskRepository path
Sound configurationsan_andreas_sound/config.lua
Sound native client APIsan_andreas_sound/client/main.lua
Sound legacy bridgessan_andreas_sound/client/compat and server/compat
Dispatch metadatasan_andreas_dispatch/config.lua
Dispatch catalog/mediasan_andreas_dispatch/shared/catalog.lua and audio/<channel>
Radio configurationsan_andreas_radio/config.lua
Scanner contractsan_andreas_radio/shared/scanners.lua
Radio provider adapterssan_andreas_radio/client/audio and server/audio
Radio artworksan_andreas_radio/web/assets/stations
Station Pack media/catalogsan_andreas_station_pack/audio, artwork, and config.lua