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
- Custom tablet UI - Dark glassmorphism React interface with a realistic tablet bezel
- Song queue - Queue up to 20 songs with auto-advance when the current song ends
- Song history - Last 50 played songs stored per booth, replay any song instantly
- Database persistence - Queue and history are saved to MySQL and survive server restarts
- Configurable theme - Server owners can set any hex accent color via config
- Multiple booth locations - Configure unlimited DJ booth locations across the map
- Job & gang restrictions - Limit booth access to specific jobs, gangs, or make them public
- YouTube integration - Play any YouTube video's audio via xSound
- Playback controls - Play, pause, resume, stop, skip, previous, and volume slider
- Real-time progress - Live progress bar with current timestamp and total duration
- 3D positional audio - Sound originates from booth location with configurable radius
- Network synced - All players see the same queue and hear the same music simultaneously
- Auto-framework detection - Works with ESX, QBCore, and QBX automatically
- Optional props - Spawn visual DJ equipment props at booth locations
- ox_target integration - Clean third-eye interaction system
- Debug mode - Visual markers and console logging for development
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.
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.
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:
- Play any song from the queue immediately
- Remove individual songs from the queue
- Clear the entire queue
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
- Play/Pause - Toggle playback
- Stop - Stop and clear the current song
- Skip Next - Skip to the next song in queue
- Volume slider - Adjust from 0-100%
- Progress bar - Shows real-time playback position (updates every 2 seconds)
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
- On resource start, the table is created if it doesn't exist, then saved data is loaded into memory
- Every time the booth state changes (play, queue, skip, remove, clear, etc.), the data is saved to the database
- Each booth is stored as a separate row keyed by its zone label
- Queue stores up to 20 URLs, history stores up to 50 URLs per booth
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:
- Two players at the same booth see the same queue and history
- Volume changes, queue modifications, and playback controls are reflected for all players
- The UI polls xSound every 2 seconds for real-time progress bar updates
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
- Verify xsound is started before NR_DJ
- Check YouTube URL is valid
- Ensure player is within booth radius
- Check if video is region-locked or age-restricted
ox_target not showing
- Verify player has required job/gang
- Check if
enableBooth = true - Confirm coords are correct
- Ensure ox_target is running
UI not opening
- Check F8 console for errors
- Verify the
html/dist/folder exists and contains built files - Ensure ox_lib is loaded (required for callbacks)
- Check that your framework is supported (ESX, QBCore, QBX)
Player stuck after closing UI
- This indicates NUI focus was not properly released
- Ensure you're running the latest version of NR_DJ
- Press
ESCto close the UI — this triggers the proper close handler - If the issue persists, restart the resource
Volume not working
- Ensure xsound is functioning
- Try stopping and restarting the song
- Check if value is between 0.01-1.0
Queue/history not saving
- Verify oxmysql is running and your connection string is correct
- Check server console for database errors on resource start
- Confirm the
nr_djtable exists in your database - Enable
Config.Debug = trueand check for load/save messages in server console
Props not spawning
- Verify prop model name is correct
- Check if coords include heading (vec4)
- Use
/coordsto verify prop placement
Multiple booths interfering
- Each booth has a unique identifier based on job/gang and index number
- Ensure job or gang differs between nearby booths
- Check booth numbers are sequential in the config
Best Practices
- Place
soundLocat the center of the desired audio coverage - Use larger radius for outdoor areas, smaller for indoor venues
- Use
"public"for general entertainment venues - Job-lock for faction-specific locations, gang-lock for territory RP
- Test audio range with
Config.Debug = true - Props are purely cosmetic — consider performance with many props
- Use a theme color that matches your server's branding
Support
For paid script support, open a ticket in our Discord server for priority assistance.