NR_DJ

A feature-rich DJ booth script with a custom React tablet UI, YouTube music playback via xSound, a song queue system with auto-advance, database-persisted history, configurable themes, and job/gang access control.

Features

Dependencies

Resource Description Required
ox_lib Callbacks and notifications Yes
ox_target Interaction system Yes
xsound 3D sound system for YouTube audio Yes
oxmysql MySQL database (queue & history persistence) Yes
ESX / QBCore / QBX Any supported framework (auto-detected) Yes

Installation

Step 1: Download

Download NR_DJ from your Tebex purchase and extract it to your resources folder:

server/
  └── resources/
      └── [echo]/
          └── NR_DJ/

Step 2: server.cfg

Add the resource to your server configuration. xsound and oxmysql must start before NR_DJ:

ensure oxmysql
ensure xsound
ensure ox_lib
ensure ox_target
ensure NR_DJ

Step 3: Database

The database table is created automatically when the resource starts. No manual SQL setup is required. The script creates an nr_dj table in your database to store queue and history data per booth.

⚠️ Auto-Install

The nr_dj table is created via CREATE TABLE IF NOT EXISTS on every resource start. Your oxmysql connection string must be configured correctly in server.cfg for this to work.

Step 4: Configuration

Edit config.lua to add your DJ booth locations, set the theme color, and configure job/gang restrictions.

Step 5: Restart

Restart your server to load the resource.

⚠️ Important

xsound must be started before NR_DJ in your server.cfg. Without xsound, no audio will play.

How It Works

1. Approaching a Booth

Walk up to a configured DJ booth location. ox_target shows a "DJ Booth" interaction option. The option only appears if the player has the required job/gang (if configured).

2. Opening the UI

Use third eye (ox_target) to interact. A tablet-style NUI opens in the center of the screen showing the booth name, current playback state, and any queued songs loaded from the database.

3. Playing a Song

Enter a YouTube URL in the input field and click "Play Now" to play immediately, or "Add to Queue" to queue it. Press Enter to play now, or Shift+Enter to add to queue. The song starts playing for all players in range via 3D positional audio.

4. Queue System

The Queue tab shows all upcoming songs (up to 20). From the queue you can:

When a song ends, the next song in the queue plays automatically. If the queue is empty, playback stops.

5. Song History

The History tab shows the last 50 played songs (most recent first). Duplicate entries are automatically removed — replaying a song moves it to the top. From history you can replay any song instantly or add it back to the queue.

6. Playback Controls

7. Closing the UI

Press ESC or click the close button to exit the DJ booth interface. NUI focus is released and the player can move normally.

8. Audio Experience

Music plays in 3D space from the booth's soundLoc position. Volume fades based on distance, and players beyond the configured radius cannot hear the music.

Configuration

General Settings

Config.Framework = 'auto'    -- 'auto', 'esx', 'qbcore', or 'qbx'
Config.Debug = false         -- Enable debug mode (visual markers, console logs)
Config.Language = 'en'       -- Locale language
Config.Theme = '#00FFFF'     -- UI accent color (any hex code)

Theme Color

Set Config.Theme to any hex color code. The entire tablet UI updates to use that accent color:

Config.Theme = '#00FFFF'  -- Cyan (default)
Config.Theme = '#FF6B6B'  -- Red
Config.Theme = '#9b59b6'  -- Purple
Config.Theme = '#2ecc71'  -- Green
Config.Theme = '#e67e22'  -- Orange

The theme color is applied to buttons, progress bars, tab indicators, hover effects, and glow accents throughout the UI.

DJ Booth Location Structure

{
    name = "Booth Name",        -- Descriptive name shown in the UI header
    enableBooth = true,         -- Enable/disable this location

    -- Access Control
    job = "public",             -- "public", specific job, or table of jobs
    gang = nil,                 -- nil, specific gang, or table of gangs

    -- Location Settings
    coords = vec3(x, y, z),    -- Interaction point (ox_target zone)
    soundLoc = vec3(x, y, z),  -- Where sound originates from

    -- Audio Settings
    DefaultVolume = 0.15,       -- Starting volume (0.0 - 1.0)
    radius = 60,                -- Hearing distance in GTA units

    -- Optional Prop
    prop = {
        model = "prop_dj_deck_01",
        coords = vec4(x, y, z, heading)
    },
}

Example: Public Booth

{
    name = "Nightclub",
    enableBooth = true,
    job = "public",
    gang = nil,
    coords = vec3(119.52, -1299.95, 29.22),
    soundLoc = vec3(119.52, -1299.95, 29.22),
    DefaultVolume = 0.15,
    radius = 60,
    prop = {
        model = "prop_dj_deck_01",
        coords = vec4(119.52, -1299.95, 29.22, 180.0)
    },
}

Example: Job-Locked Booth

{
    name = "Police Station",
    enableBooth = true,
    job = "police",
    gang = nil,
    coords = vec3(441.0, -982.0, 30.68),
    soundLoc = vec3(441.0, -982.0, 30.68),
    DefaultVolume = 0.10,
    radius = 40,
    prop = nil,
}

Example: Gang-Locked Booth

{
    name = "Gang Hideout",
    enableBooth = true,
    job = nil,
    gang = "ballas",
    coords = vec3(100.0, -1900.0, 21.0),
    soundLoc = vec3(100.0, -1900.0, 21.0),
    DefaultVolume = 0.20,
    radius = 50,
    prop = nil,
}

Example: Multiple Jobs Allowed

{
    name = "Staff Room",
    enableBooth = true,
    job = {"police", "ambulance", "mechanic"},
    gang = nil,
    coords = vec3(300.0, -580.0, 43.26),
    soundLoc = vec3(300.0, -580.0, 43.26),
    DefaultVolume = 0.12,
    radius = 35,
    prop = nil,
}

Database

NR_DJ uses oxmysql to persist queue and history data across server restarts.

Table Structure

The nr_dj table is created automatically on resource start:

CREATE TABLE IF NOT EXISTS `nr_dj` (
    `zone_label` VARCHAR(50) NOT NULL,
    `queue` LONGTEXT DEFAULT NULL,
    `history` LONGTEXT DEFAULT NULL,
    PRIMARY KEY (`zone_label`)
)
Column Type Description
zone_label VARCHAR(50) Unique booth identifier (e.g. public1, police3)
queue LONGTEXT JSON array of queued YouTube URLs
history LONGTEXT JSON array of previously played YouTube URLs

How It Works

Booth Identification System

Each booth is identified by: (job or gang or "public") + zoneNum

Example Identifier
Public booth #1 public1
Police booth #3 police3
Ballas booth #2 ballas2

This identifier is used as the xSound sound name, the database key, and ensures multiple booths operate independently.

Audio Settings

Volume System

Config Value UI Display xSound Value
DefaultVolume = 0.15 15% 0.15
DefaultVolume = 0.50 50% 0.50

Volume is adjustable in real-time using the slider in the UI. The volume persists for the current session and resets to DefaultVolume after a server restart or when the song is stopped.

Recommended Volume Levels

Venue Type Volume Range
Nightclubs 0.15 - 0.25
Bars 0.10 - 0.15
Outdoor events 0.20 - 0.30
Background music 0.05 - 0.10

Radius & Distance

Venue Size Radius
Small room 25
Medium venue 50
Large outdoor area 100
Entire block 150

Sound Location vs Interaction Point

coords = vec3(x, y, z)    -- Where player clicks (ox_target zone)
soundLoc = vec3(x, y, z) -- Where audio emanates from

Usually these are the same, but you can offset soundLoc for better audio positioning (e.g., center of dance floor vs booth location).

YouTube URL Formats

The following formats are supported:

-- Full URL
https://www.youtube.com/watch?v=dQw4w9WgXcQ

-- Short URL
https://youtu.be/dQw4w9WgXcQ

-- Video ID only
dQw4w9WgXcQ

The script auto-corrects partial URLs by prepending https://www.youtube.com/watch?v=. YouTube thumbnails are displayed automatically in the now-playing section and in queue/history lists.

Props & Visual Elements

DJ Equipment Props

Prop Description
prop_dj_deck_01 Standard DJ deck
prop_speaker_01 Large speaker
prop_speaker_02 Small speaker
prop_speaker_03 Floor speaker
prop_studio_light_01 Stage light

Set prop = nil to disable props for a booth. Props are purely cosmetic.

Localization

The script supports full localization. Edit Config.Locale in config.lua:

Config.Locale = {
    ['en'] = {
        target = {
            dj_booth = "DJ Booth",
        },
        menu = {
            play = "Play Song",
            youtube_url = "Enter YouTube URL",
            history = "Song History",
            no_song = "No Song Playing",
            cur_playing = "Currently Playing",
            cur_paused = "Currently Paused",
        },
        notify = {
            load_link = "Loading song: ",
            new_volume = "Volume set to: ",
        },
    }
}

Add new languages by copying the ['en'] block and translating strings.

Advanced Features

Dynamic Booth Creation

Add booths at runtime using the export:

exports['NR_DJ']:AddLocation({
    name = "New Venue",
    enableBooth = true,
    job = "public",
    gang = nil,
    coords = vec3(100.0, 200.0, 30.0),
    soundLoc = vec3(100.0, 200.0, 30.0),
    DefaultVolume = 0.15,
    radius = 50,
    prop = nil,
})

This broadcasts to all clients and creates the booth immediately. Dynamically added booths are not persisted across restarts — add them to Config.Locations for permanent booths.

Queue Limits

Limit Value
Max queue size per booth 20 songs
Max history size per booth 50 songs

When the queue is full, players are notified and the song is not added. History automatically trims older entries when the limit is reached.

Auto-Advance

When a song finishes playing, the client detects the end via xSound's onPlayEnd callback and notifies the server. The server then automatically plays the next song in the queue. If the queue is empty, playback stops and all clients are updated.

State Synchronization

All booth state changes are broadcast to every connected client. This means:

Performance

Metric Value
Resmon impact ~0.00ms (idle), ~0.01ms (UI open)
Network traffic Minimal (event-based synchronization)
Database writes On state change only (not polling)
xSound load Depends on active songs (handled by xSound)
UI polling Every 2 seconds (progress bar only, while UI is open)

Troubleshooting

No sound playing

ox_target not showing

UI not opening

Player stuck after closing UI

Volume not working

Queue/history not saving

Props not spawning

Multiple booths interfering

Best Practices

Support

For paid script support, open a ticket in our Discord server for priority assistance.