forked from Simnation/Main
ed
This commit is contained in:
parent
0e29315dd1
commit
e0a3b21c61
7 changed files with 1463 additions and 0 deletions
587
resources/[tools]/nordi_dj/client/main.lua
Normal file
587
resources/[tools]/nordi_dj/client/main.lua
Normal file
|
@ -0,0 +1,587 @@
|
|||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
local PlayerData = {}
|
||||
local currentDJBooth = nil
|
||||
local isPlaying = false
|
||||
local currentVolume = Config.DefaultVolume
|
||||
local currentSong = nil
|
||||
local playlists = {}
|
||||
local currentPlaylist = nil
|
||||
local currentSongIndex = 1
|
||||
|
||||
-- Events
|
||||
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
|
||||
PlayerData = QBCore.Functions.GetPlayerData()
|
||||
end)
|
||||
|
||||
RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo)
|
||||
PlayerData.job = JobInfo
|
||||
end)
|
||||
|
||||
-- Key Mapping
|
||||
RegisterKeyMapping('opendj', 'Open DJ Menu', 'keyboard', Config.OpenMenuKey)
|
||||
|
||||
RegisterCommand('opendj', function()
|
||||
if not CanUseDJScript() then
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Du hast keine Berechtigung das DJ System zu nutzen!',
|
||||
type = 'error'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local nearbyBooth = GetNearbyDJBooth()
|
||||
if nearbyBooth then
|
||||
currentDJBooth = nearbyBooth
|
||||
OpenDJMenu()
|
||||
else
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Du bist nicht in der Nähe einer DJ Booth!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
-- Functions
|
||||
function CanUseDJScript()
|
||||
if not Config.UseJobRestriction then
|
||||
return true
|
||||
end
|
||||
|
||||
if not PlayerData.job then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, job in pairs(Config.AllowedJobs) do
|
||||
if PlayerData.job.name == job then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function GetNearbyDJBooth()
|
||||
local playerCoords = GetEntityCoords(PlayerPedId())
|
||||
|
||||
for i, booth in pairs(Config.DJBooths) do
|
||||
local distance = #(playerCoords - booth.coords)
|
||||
if distance <= 3.0 then
|
||||
return booth
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function OpenDJMenu()
|
||||
TriggerServerEvent('dj:server:getPlaylists')
|
||||
|
||||
local options = {
|
||||
{
|
||||
title = 'YouTube Song abspielen',
|
||||
description = 'Spiele einen Song von YouTube ab',
|
||||
icon = 'fab fa-youtube',
|
||||
onSelect = function()
|
||||
OpenYouTubeMenu()
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Direkte URL abspielen',
|
||||
description = 'Spiele einen Song von einer direkten URL ab',
|
||||
icon = 'play',
|
||||
onSelect = function()
|
||||
OpenDirectUrlMenu()
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Musik stoppen',
|
||||
description = 'Stoppe die aktuelle Musik',
|
||||
icon = 'stop',
|
||||
onSelect = function()
|
||||
StopMusic()
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Lautstärke ändern',
|
||||
description = 'Aktuelle Lautstärke: ' .. currentVolume .. '%',
|
||||
icon = 'volume-up',
|
||||
onSelect = function()
|
||||
OpenVolumeMenu()
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Playlists verwalten',
|
||||
description = 'Erstelle und verwalte Playlists',
|
||||
icon = 'list',
|
||||
onSelect = function()
|
||||
OpenPlaylistMenu()
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
if isPlaying and currentSong then
|
||||
table.insert(options, 2, {
|
||||
title = 'Aktueller Song',
|
||||
description = currentSong.title,
|
||||
icon = 'music',
|
||||
disabled = true
|
||||
})
|
||||
end
|
||||
|
||||
lib.registerContext({
|
||||
id = 'dj_main_menu',
|
||||
title = 'DJ System - ' .. currentDJBooth.name,
|
||||
options = options
|
||||
})
|
||||
|
||||
lib.showContext('dj_main_menu')
|
||||
end
|
||||
|
||||
function OpenYouTubeMenu()
|
||||
local input = lib.inputDialog('YouTube Song abspielen', {
|
||||
{type = 'input', label = 'Song Titel', placeholder = 'z.B. Daft Punk - One More Time'},
|
||||
{type = 'input', label = 'YouTube URL', placeholder = 'https://www.youtube.com/watch?v=...'}
|
||||
})
|
||||
|
||||
if input and input[1] and input[2] then
|
||||
if not IsValidYouTubeUrl(input[2]) then
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Ungültige YouTube URL!',
|
||||
type = 'error'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'YouTube URL wird konvertiert, bitte warten...',
|
||||
type = 'info'
|
||||
})
|
||||
|
||||
PlayMusic(input[1], input[2])
|
||||
end
|
||||
end
|
||||
|
||||
function OpenDirectUrlMenu()
|
||||
local input = lib.inputDialog('Direkte URL abspielen', {
|
||||
{type = 'input', label = 'Song Titel', placeholder = 'Titel des Songs'},
|
||||
{type = 'input', label = 'Direkte MP3/Audio URL', placeholder = 'https://example.com/song.mp3'}
|
||||
})
|
||||
|
||||
if input and input[1] and input[2] then
|
||||
PlayMusic(input[1], input[2])
|
||||
end
|
||||
end
|
||||
|
||||
function IsValidYouTubeUrl(url)
|
||||
local patterns = {
|
||||
"youtube%.com/watch%?v=",
|
||||
"youtu%.be/",
|
||||
"youtube%.com/embed/"
|
||||
}
|
||||
|
||||
for _, pattern in ipairs(patterns) do
|
||||
if string.match(url, pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function OpenVolumeMenu()
|
||||
local input = lib.inputDialog('Lautstärke einstellen', {
|
||||
{
|
||||
type = 'slider',
|
||||
label = 'Lautstärke (%)',
|
||||
description = 'Höhere Lautstärke = größere Reichweite',
|
||||
default = currentVolume,
|
||||
min = 0,
|
||||
max = Config.MaxVolume,
|
||||
step = 5
|
||||
}
|
||||
})
|
||||
|
||||
if input and input[1] then
|
||||
SetVolume(input[1])
|
||||
end
|
||||
end
|
||||
|
||||
function OpenPlaylistMenu()
|
||||
local options = {
|
||||
{
|
||||
title = 'Neue Playlist erstellen',
|
||||
description = 'Erstelle eine neue Playlist',
|
||||
icon = 'plus',
|
||||
onSelect = function()
|
||||
CreateNewPlaylist()
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
for _, playlist in pairs(playlists) do
|
||||
table.insert(options, {
|
||||
title = playlist.name,
|
||||
description = #playlist.songs .. ' Songs',
|
||||
icon = 'music',
|
||||
onSelect = function()
|
||||
OpenPlaylistOptions(playlist)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
lib.registerContext({
|
||||
id = 'playlist_menu',
|
||||
title = 'Playlist Verwaltung',
|
||||
menu = 'dj_main_menu',
|
||||
options = options
|
||||
})
|
||||
|
||||
lib.showContext('playlist_menu')
|
||||
end
|
||||
|
||||
function CreateNewPlaylist()
|
||||
local input = lib.inputDialog('Neue Playlist', {
|
||||
{type = 'input', label = 'Playlist Name', placeholder = 'Meine YouTube Playlist'}
|
||||
})
|
||||
|
||||
if input and input[1] then
|
||||
TriggerServerEvent('dj:server:createPlaylist', input[1])
|
||||
end
|
||||
end
|
||||
|
||||
function OpenPlaylistOptions(playlist)
|
||||
local options = {
|
||||
{
|
||||
title = 'Playlist abspielen',
|
||||
description = 'Spiele alle Songs der Playlist ab',
|
||||
icon = 'play',
|
||||
onSelect = function()
|
||||
PlayPlaylist(playlist)
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'YouTube Song hinzufügen',
|
||||
description = 'Füge einen YouTube Song zur Playlist hinzu',
|
||||
icon = 'fab fa-youtube',
|
||||
onSelect = function()
|
||||
AddYouTubeSongToPlaylist(playlist)
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Direkten Song hinzufügen',
|
||||
description = 'Füge einen Song per direkter URL hinzu',
|
||||
icon = 'plus',
|
||||
onSelect = function()
|
||||
AddDirectSongToPlaylist(playlist)
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Songs anzeigen',
|
||||
description = 'Zeige alle Songs in der Playlist',
|
||||
icon = 'list',
|
||||
onSelect = function()
|
||||
ShowPlaylistSongs(playlist)
|
||||
end
|
||||
},
|
||||
{
|
||||
title = 'Playlist löschen',
|
||||
description = 'Lösche diese Playlist',
|
||||
icon = 'trash',
|
||||
onSelect = function()
|
||||
DeletePlaylist(playlist)
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
lib.registerContext({
|
||||
id = 'playlist_options',
|
||||
title = playlist.name,
|
||||
menu = 'playlist_menu',
|
||||
options = options
|
||||
})
|
||||
|
||||
lib.showContext('playlist_options')
|
||||
end
|
||||
|
||||
function AddYouTubeSongToPlaylist(playlist)
|
||||
local input = lib.inputDialog('YouTube Song hinzufügen', {
|
||||
{type = 'input', label = 'Song Titel', placeholder = 'z.B. Avicii - Levels'},
|
||||
{type = 'input', label = 'YouTube URL', placeholder = 'https://www.youtube.com/watch?v=...'}
|
||||
})
|
||||
|
||||
if input and input[1] and input[2] then
|
||||
if not IsValidYouTubeUrl(input[2]) then
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Ungültige YouTube URL!',
|
||||
type = 'error'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
TriggerServerEvent('dj:server:addSongToPlaylist', playlist.id, {
|
||||
title = input[1],
|
||||
url = input[2]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function AddDirectSongToPlaylist(playlist)
|
||||
local input = lib.inputDialog('Direkten Song hinzufügen', {
|
||||
{type = 'input', label = 'Song Titel', placeholder = 'Titel des Songs'},
|
||||
{type = 'input', label = 'Direkte URL', placeholder = 'https://example.com/song.mp3'}
|
||||
})
|
||||
|
||||
if input and input[1] and input[2] then
|
||||
TriggerServerEvent('dj:server:addSongToPlaylist', playlist.id, {
|
||||
title = input[1],
|
||||
url = input[2]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function PlayMusic(title, url)
|
||||
if not currentDJBooth then return end
|
||||
|
||||
currentSong = {title = title, url = url}
|
||||
isPlaying = true
|
||||
|
||||
TriggerServerEvent('dj:server:playMusic', {
|
||||
title = title,
|
||||
url = url,
|
||||
volume = currentVolume,
|
||||
booth = currentDJBooth,
|
||||
range = CalculateRange()
|
||||
})
|
||||
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Lade: ' .. title,
|
||||
type = 'info'
|
||||
})
|
||||
end
|
||||
|
||||
function StopMusic()
|
||||
if not isPlaying then
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Es läuft gerade keine Musik!',
|
||||
type = 'error'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
isPlaying = false
|
||||
currentSong = nil
|
||||
currentPlaylist = nil
|
||||
|
||||
TriggerServerEvent('dj:server:stopMusic', currentDJBooth)
|
||||
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Musik gestoppt',
|
||||
type = 'success'
|
||||
})
|
||||
end
|
||||
|
||||
function SetVolume(volume)
|
||||
currentVolume = volume
|
||||
|
||||
if isPlaying then
|
||||
TriggerServerEvent('dj:server:setVolume', {
|
||||
volume = volume,
|
||||
booth = currentDJBooth,
|
||||
range = CalculateRange()
|
||||
})
|
||||
end
|
||||
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Lautstärke auf ' .. volume .. '% gesetzt',
|
||||
type = 'success'
|
||||
})
|
||||
end
|
||||
|
||||
function CalculateRange()
|
||||
local baseRange = currentDJBooth.range
|
||||
local maxRange = currentDJBooth.maxRange
|
||||
local volumePercent = currentVolume / 100
|
||||
|
||||
return baseRange + ((maxRange - baseRange) * volumePercent)
|
||||
end
|
||||
|
||||
function PlayPlaylist(playlist)
|
||||
if #playlist.songs == 0 then
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Playlist ist leer!',
|
||||
type = 'error'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
currentPlaylist = playlist
|
||||
currentSongIndex = 1
|
||||
|
||||
local firstSong = playlist.songs[1]
|
||||
PlayMusic(firstSong.title, firstSong.url)
|
||||
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Playlist "' .. playlist.name .. '" wird abgespielt',
|
||||
type = 'success'
|
||||
})
|
||||
end
|
||||
|
||||
function ShowPlaylistSongs(playlist)
|
||||
local options = {}
|
||||
|
||||
for i, song in pairs(playlist.songs) do
|
||||
local songType = IsValidYouTubeUrl(song.url) and "YouTube" or "Direkt"
|
||||
table.insert(options, {
|
||||
title = song.title,
|
||||
description = 'Typ: ' .. songType .. ' | Klicken zum Abspielen',
|
||||
icon = IsValidYouTubeUrl(song.url) and 'fab fa-youtube' or 'music',
|
||||
onSelect = function()
|
||||
PlayMusic(song.title, song.url)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
if #options == 0 then
|
||||
table.insert(options, {
|
||||
title = 'Keine Songs',
|
||||
description = 'Diese Playlist ist leer',
|
||||
icon = 'exclamation'
|
||||
})
|
||||
end
|
||||
|
||||
lib.registerContext({
|
||||
id = 'playlist_songs',
|
||||
title = playlist.name .. ' - Songs',
|
||||
menu = 'playlist_options',
|
||||
options = options
|
||||
})
|
||||
|
||||
lib.showContext('playlist_songs')
|
||||
end
|
||||
|
||||
function DeletePlaylist(playlist)
|
||||
local confirm = lib.alertDialog({
|
||||
header = 'Playlist löschen',
|
||||
content = 'Möchtest du die Playlist "' .. playlist.name .. '" wirklich löschen?',
|
||||
centered = true,
|
||||
cancel = true
|
||||
})
|
||||
|
||||
if confirm == 'confirm' then
|
||||
TriggerServerEvent('dj:server:deletePlaylist', playlist.id)
|
||||
end
|
||||
end
|
||||
|
||||
-- Server Events
|
||||
RegisterNetEvent('dj:client:playMusic', function(data)
|
||||
SendNUIMessage({
|
||||
type = 'playMusic',
|
||||
url = data.url,
|
||||
volume = data.volume,
|
||||
title = data.title
|
||||
})
|
||||
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Spielt ab: ' .. data.title,
|
||||
type = 'success'
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:client:stopMusic', function()
|
||||
SendNUIMessage({
|
||||
type = 'stopMusic'
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:client:setVolume', function(volume)
|
||||
SendNUIMessage({
|
||||
type = 'setVolume',
|
||||
volume = volume
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:client:updatePlaylists', function(data)
|
||||
playlists = data
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:client:notify', function(message, type)
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = message,
|
||||
type = type or 'info'
|
||||
})
|
||||
end)
|
||||
|
||||
-- Music Range Check
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1000)
|
||||
|
||||
if isPlaying and currentDJBooth then
|
||||
local playerCoords = GetEntityCoords(PlayerPedId())
|
||||
local distance = #(playerCoords - currentDJBooth.coords)
|
||||
local range = CalculateRange()
|
||||
|
||||
if distance > range then
|
||||
SendNUIMessage({
|
||||
type = 'fadeOut'
|
||||
})
|
||||
else
|
||||
SendNUIMessage({
|
||||
type = 'fadeIn'
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Song End Handler
|
||||
RegisterNUICallback('songEnded', function(data, cb)
|
||||
if currentPlaylist and currentSongIndex < #currentPlaylist.songs then
|
||||
-- Nächster Song in Playlist
|
||||
currentSongIndex = currentSongIndex + 1
|
||||
local nextSong = currentPlaylist.songs[currentSongIndex]
|
||||
PlayMusic(nextSong.title, nextSong.url)
|
||||
else
|
||||
-- Playlist beendet
|
||||
isPlaying = false
|
||||
currentSong = nil
|
||||
currentPlaylist = nil
|
||||
currentSongIndex = 1
|
||||
end
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
-- Audio Error Handler
|
||||
RegisterNUICallback('audioError', function(data, cb)
|
||||
lib.notify({
|
||||
title = 'DJ System',
|
||||
description = 'Audio Fehler: ' .. (data.error or 'Unbekannter Fehler'),
|
||||
type = 'error'
|
||||
})
|
||||
|
||||
-- Reset playing state
|
||||
isPlaying = false
|
||||
currentSong = nil
|
||||
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
-- Song Progress (optional)
|
||||
RegisterNUICallback('songProgress', function(data, cb)
|
||||
-- Du kannst hier Song-Progress verarbeiten
|
||||
-- z.B. für eine Progress-Bar im Menü
|
||||
cb('ok')
|
||||
end)
|
72
resources/[tools]/nordi_dj/config.lua
Normal file
72
resources/[tools]/nordi_dj/config.lua
Normal file
|
@ -0,0 +1,72 @@
|
|||
Config = {}
|
||||
|
||||
-- Allgemeine Einstellungen
|
||||
Config.UseJobRestriction = true
|
||||
Config.AllowedJobs = {
|
||||
'dj',
|
||||
'nightclub',
|
||||
'admin'
|
||||
}
|
||||
|
||||
Config.OpenMenuKey = 'F7'
|
||||
Config.MaxVolume = 100
|
||||
Config.DefaultVolume = 50
|
||||
|
||||
-- YouTube API Einstellungen
|
||||
Config.YouTubeAPI = {
|
||||
enabled = true,
|
||||
-- Du kannst eine kostenlose API von https://rapidapi.com/ytjar/api/youtube-mp36 bekommen
|
||||
-- Oder eine andere YouTube to MP3 API verwenden
|
||||
apiUrl = "https://youtube-mp36.p.rapidapi.com/dl",
|
||||
apiKey = "DEIN_API_KEY_HIER", -- Ersetze mit deinem API Key
|
||||
headers = {
|
||||
["X-RapidAPI-Key"] = "DEIN_API_KEY_HIER",
|
||||
["X-RapidAPI-Host"] = "youtube-mp36.p.rapidapi.com"
|
||||
}
|
||||
}
|
||||
|
||||
-- Alternative: Lokaler YouTube-DL Server (empfohlen für bessere Performance)
|
||||
Config.LocalYouTubeConverter = {
|
||||
enabled = false, -- Setze auf true wenn du einen lokalen Converter verwendest
|
||||
serverUrl = "http://localhost:3000/convert" -- URL zu deinem lokalen Converter
|
||||
}
|
||||
|
||||
-- DJ Booth Locations
|
||||
Config.DJBooths = {
|
||||
{
|
||||
name = "Vanilla Unicorn",
|
||||
coords = vector3(120.13, -1281.72, 29.48),
|
||||
range = 50.0,
|
||||
maxRange = 100.0
|
||||
},
|
||||
{
|
||||
name = "Bahama Mamas",
|
||||
coords = vector3(-1387.08, -618.52, 30.82),
|
||||
range = 50.0,
|
||||
maxRange = 100.0
|
||||
},
|
||||
{
|
||||
name = "Diamond Casino",
|
||||
coords = vector3(1549.78, 252.44, -46.01),
|
||||
range = 60.0,
|
||||
maxRange = 120.0
|
||||
}
|
||||
}
|
||||
|
||||
-- Standard Playlists mit YouTube Links
|
||||
Config.DefaultPlaylists = {
|
||||
{
|
||||
name = "Party Hits",
|
||||
songs = {
|
||||
{title = "Daft Punk - One More Time", url = "https://www.youtube.com/watch?v=FGBhQbmPwH8"},
|
||||
{title = "Calvin Harris - Feel So Close", url = "https://www.youtube.com/watch?v=dGghkjpNCQ8"}
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "Chill Music",
|
||||
songs = {
|
||||
{title = "Kygo - Firestone", url = "https://www.youtube.com/watch?v=9Sc-ir2UwGU"},
|
||||
{title = "Avicii - Levels", url = "https://www.youtube.com/watch?v=_ovdm2yX4MA"}
|
||||
}
|
||||
}
|
||||
}
|
34
resources/[tools]/nordi_dj/fxmanifest.lua
Normal file
34
resources/[tools]/nordi_dj/fxmanifest.lua
Normal file
|
@ -0,0 +1,34 @@
|
|||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
author 'YourName'
|
||||
description 'QBCore DJ Script with YouTube Support'
|
||||
version '1.0.0'
|
||||
|
||||
shared_scripts {
|
||||
'@ox_lib/init.lua',
|
||||
'config.lua'
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'client/main.lua'
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
'server/main.lua'
|
||||
}
|
||||
|
||||
ui_page 'html/index.html'
|
||||
|
||||
files {
|
||||
'html/index.html',
|
||||
'html/style.css',
|
||||
'html/script.js'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
'qb-core',
|
||||
'ox_lib',
|
||||
'oxmysql'
|
||||
}
|
34
resources/[tools]/nordi_dj/html/index.html
Normal file
34
resources/[tools]/nordi_dj/html/index.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DJ System</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="music-player">
|
||||
<audio
|
||||
id="audio-player"
|
||||
preload="auto"
|
||||
crossorigin="anonymous"
|
||||
controls="false"
|
||||
style="display: none;">
|
||||
Dein Browser unterstützt das Audio-Element nicht.
|
||||
</audio>
|
||||
|
||||
<!-- Optional: Progress indicator (hidden by default) -->
|
||||
<div id="progress-container" style="display: none;">
|
||||
<div id="progress-bar"></div>
|
||||
</div>
|
||||
|
||||
<!-- Optional: Song info display (hidden by default) -->
|
||||
<div id="song-info" style="display: none;">
|
||||
<span id="song-title"></span>
|
||||
<span id="song-time"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
401
resources/[tools]/nordi_dj/html/script.js
Normal file
401
resources/[tools]/nordi_dj/html/script.js
Normal file
|
@ -0,0 +1,401 @@
|
|||
let audioPlayer = null;
|
||||
let currentVolume = 50;
|
||||
let isPlaying = false;
|
||||
let currentSong = null;
|
||||
let fadeInterval = null;
|
||||
|
||||
// Initialize when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
audioPlayer = document.getElementById('audio-player');
|
||||
setupAudioPlayer();
|
||||
});
|
||||
|
||||
// Setup audio player with event listeners
|
||||
function setupAudioPlayer() {
|
||||
if (!audioPlayer) return;
|
||||
|
||||
// Audio Events
|
||||
audioPlayer.addEventListener('loadstart', function() {
|
||||
console.log('DJ System: Loading started');
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('canplay', function() {
|
||||
console.log('DJ System: Can start playing');
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('play', function() {
|
||||
console.log('DJ System: Playback started');
|
||||
isPlaying = true;
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('pause', function() {
|
||||
console.log('DJ System: Playback paused');
|
||||
isPlaying = false;
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('ended', function() {
|
||||
console.log('DJ System: Song ended');
|
||||
isPlaying = false;
|
||||
// Notify FiveM that song ended (for playlist functionality)
|
||||
fetch(`https://${GetParentResourceName()}/songEnded`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('error', function(e) {
|
||||
console.error('DJ System: Audio error', e);
|
||||
isPlaying = false;
|
||||
// Notify FiveM about the error
|
||||
fetch(`https://${GetParentResourceName()}/audioError`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
error: 'Audio playback error',
|
||||
code: audioPlayer.error ? audioPlayer.error.code : 'unknown'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('loadedmetadata', function() {
|
||||
console.log('DJ System: Metadata loaded, duration:', audioPlayer.duration);
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('timeupdate', function() {
|
||||
// Optional: Send progress updates
|
||||
if (isPlaying && audioPlayer.duration) {
|
||||
const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100;
|
||||
// You can use this for progress bars if needed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Main message handler from FiveM
|
||||
window.addEventListener('message', function(event) {
|
||||
const data = event.data;
|
||||
|
||||
switch(data.type) {
|
||||
case 'playMusic':
|
||||
playMusic(data.url, data.volume, data.title);
|
||||
break;
|
||||
case 'stopMusic':
|
||||
stopMusic();
|
||||
break;
|
||||
case 'setVolume':
|
||||
setVolume(data.volume);
|
||||
break;
|
||||
case 'fadeOut':
|
||||
fadeOut();
|
||||
break;
|
||||
case 'fadeIn':
|
||||
fadeIn();
|
||||
break;
|
||||
case 'pauseMusic':
|
||||
pauseMusic();
|
||||
break;
|
||||
case 'resumeMusic':
|
||||
resumeMusic();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Play music function with YouTube support
|
||||
async function playMusic(url, volume, title = 'Unknown') {
|
||||
if (!audioPlayer) {
|
||||
console.error('DJ System: Audio player not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Stop current music first
|
||||
stopMusic();
|
||||
|
||||
console.log('DJ System: Attempting to play:', title, url);
|
||||
|
||||
// Set volume
|
||||
currentVolume = volume || 50;
|
||||
audioPlayer.volume = currentVolume / 100;
|
||||
|
||||
// Handle different URL types
|
||||
let playableUrl = await processUrl(url);
|
||||
|
||||
if (!playableUrl) {
|
||||
console.error('DJ System: Could not process URL:', url);
|
||||
notifyError('Could not process audio URL');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set source and play
|
||||
audioPlayer.src = playableUrl;
|
||||
audioPlayer.load();
|
||||
|
||||
// Store current song info
|
||||
currentSong = {
|
||||
title: title,
|
||||
url: url,
|
||||
playableUrl: playableUrl
|
||||
};
|
||||
|
||||
// Attempt to play
|
||||
const playPromise = audioPlayer.play();
|
||||
|
||||
if (playPromise !== undefined) {
|
||||
playPromise
|
||||
.then(() => {
|
||||
console.log('DJ System: Successfully started playing:', title);
|
||||
isPlaying = true;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('DJ System: Play failed:', error);
|
||||
notifyError('Playback failed: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('DJ System: Error in playMusic:', error);
|
||||
notifyError('Error playing music: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Process different URL types
|
||||
async function processUrl(url) {
|
||||
try {
|
||||
// Check if it's a YouTube URL
|
||||
if (isYouTubeUrl(url)) {
|
||||
console.log('DJ System: Processing YouTube URL');
|
||||
return await convertYouTubeUrl(url);
|
||||
}
|
||||
|
||||
// Check if it's a direct audio URL
|
||||
if (isDirectAudioUrl(url)) {
|
||||
console.log('DJ System: Direct audio URL detected');
|
||||
return url;
|
||||
}
|
||||
|
||||
// Try to use URL as-is (might be pre-converted)
|
||||
console.log('DJ System: Using URL as-is');
|
||||
return url;
|
||||
|
||||
} catch (error) {
|
||||
console.error('DJ System: Error processing URL:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if URL is YouTube
|
||||
function isYouTubeUrl(url) {
|
||||
const youtubePatterns = [
|
||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
||||
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
||||
];
|
||||
|
||||
return youtubePatterns.some(pattern => pattern.test(url));
|
||||
}
|
||||
|
||||
// Check if URL is direct audio
|
||||
function isDirectAudioUrl(url) {
|
||||
const audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'];
|
||||
const lowerUrl = url.toLowerCase();
|
||||
|
||||
return audioExtensions.some(ext => lowerUrl.includes(ext)) ||
|
||||
lowerUrl.includes('audio/') ||
|
||||
lowerUrl.includes('stream');
|
||||
}
|
||||
|
||||
// Convert YouTube URL (this would be handled server-side in real implementation)
|
||||
async function convertYouTubeUrl(url) {
|
||||
try {
|
||||
// Extract video ID
|
||||
const videoId = extractYouTubeVideoId(url);
|
||||
if (!videoId) {
|
||||
throw new Error('Could not extract YouTube video ID');
|
||||
}
|
||||
|
||||
console.log('DJ System: YouTube Video ID:', videoId);
|
||||
|
||||
// In a real implementation, this would call your server-side converter
|
||||
// For now, we'll return the original URL and let the server handle it
|
||||
return url;
|
||||
|
||||
} catch (error) {
|
||||
console.error('DJ System: YouTube conversion error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract YouTube video ID
|
||||
function extractYouTubeVideoId(url) {
|
||||
const patterns = [
|
||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
||||
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
||||
];
|
||||
|
||||
for (let pattern of patterns) {
|
||||
const match = url.match(pattern);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Stop music
|
||||
function stopMusic() {
|
||||
if (!audioPlayer) return;
|
||||
|
||||
try {
|
||||
audioPlayer.pause();
|
||||
audioPlayer.currentTime = 0;
|
||||
audioPlayer.src = '';
|
||||
isPlaying = false;
|
||||
currentSong = null;
|
||||
|
||||
// Clear any fade effects
|
||||
if (fadeInterval) {
|
||||
clearInterval(fadeInterval);
|
||||
fadeInterval = null;
|
||||
}
|
||||
|
||||
console.log('DJ System: Music stopped');
|
||||
|
||||
} catch (error) {
|
||||
console.error('DJ System: Error stopping music:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Pause music
|
||||
function pauseMusic() {
|
||||
if (!audioPlayer || !isPlaying) return;
|
||||
|
||||
try {
|
||||
audioPlayer.pause();
|
||||
isPlaying = false;
|
||||
console.log('DJ System: Music paused');
|
||||
} catch (error) {
|
||||
console.error('DJ System: Error pausing music:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume music
|
||||
function resumeMusic() {
|
||||
if (!audioPlayer || isPlaying) return;
|
||||
|
||||
try {
|
||||
const playPromise = audioPlayer.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise
|
||||
.then(() => {
|
||||
isPlaying = true;
|
||||
console.log('DJ System: Music resumed');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('DJ System: Resume failed:', error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('DJ System: Error resuming music:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Set volume
|
||||
function setVolume(volume) {
|
||||
if (!audioPlayer) return;
|
||||
|
||||
try {
|
||||
currentVolume = Math.max(0, Math.min(100, volume));
|
||||
audioPlayer.volume = currentVolume / 100;
|
||||
console.log('DJ System: Volume set to', currentVolume + '%');
|
||||
} catch (error) {
|
||||
console.error('DJ System: Error setting volume:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fade out effect (when player moves away)
|
||||
function fadeOut() {
|
||||
if (!audioPlayer || !isPlaying) return;
|
||||
|
||||
if (fadeInterval) {
|
||||
clearInterval(fadeInterval);
|
||||
}
|
||||
|
||||
let currentVol = audioPlayer.volume;
|
||||
const targetVol = 0;
|
||||
const fadeStep = 0.05;
|
||||
|
||||
fadeInterval = setInterval(() => {
|
||||
currentVol -= fadeStep;
|
||||
if (currentVol <= targetVol) {
|
||||
currentVol = targetVol;
|
||||
audioPlayer.volume = currentVol;
|
||||
clearInterval(fadeInterval);
|
||||
fadeInterval = null;
|
||||
} else {
|
||||
audioPlayer.volume = currentVol;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// Fade in effect (when player moves closer)
|
||||
function fadeIn() {
|
||||
if (!audioPlayer || !isPlaying) return;
|
||||
|
||||
if (fadeInterval) {
|
||||
clearInterval(fadeInterval);
|
||||
}
|
||||
|
||||
let currentVol = audioPlayer.volume;
|
||||
const targetVol = currentVolume / 100;
|
||||
const fadeStep = 0.05;
|
||||
|
||||
fadeInterval = setInterval(() => {
|
||||
currentVol += fadeStep;
|
||||
if (currentVol >= targetVol) {
|
||||
currentVol = targetVol;
|
||||
audioPlayer.volume = currentVol;
|
||||
clearInterval(fadeInterval);
|
||||
fadeInterval = null;
|
||||
} else {
|
||||
audioPlayer.volume = currentVol;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// Notify FiveM about errors
|
||||
function notifyError(message) {
|
||||
fetch(`https://${GetParentResourceName()}/audioError`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
error: message
|
||||
})
|
||||
}).catch(err => {
|
||||
console.error('DJ System: Failed to notify error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
// Get current resource name
|
||||
function GetParentResourceName() {
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
// Debug functions (can be called from browser console)
|
||||
window.djDebug = {
|
||||
getCurrentSong: () => currentSong,
|
||||
getVolume: () => currentVolume,
|
||||
isPlaying: () => isPlaying,
|
||||
getAudioPlayer: () => audioPlayer,
|
||||
testPlay: (url) => playMusic(url, 50, 'Test Song'),
|
||||
testStop: () => stopMusic(),
|
||||
testVolume: (vol) => setVolume(vol)
|
||||
};
|
||||
|
||||
// Log when script is loaded
|
||||
console.log('DJ System: Script loaded and ready');
|
72
resources/[tools]/nordi_dj/html/style.css
Normal file
72
resources/[tools]/nordi_dj/html/style.css
Normal file
|
@ -0,0 +1,72 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: transparent;
|
||||
font-family: 'Arial', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#music-player {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#audio-player {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Optional progress bar styles */
|
||||
#progress-container {
|
||||
width: 300px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #ff6b6b, #4ecdc4);
|
||||
width: 0%;
|
||||
transition: width 0.1s ease;
|
||||
}
|
||||
|
||||
/* Optional song info styles */
|
||||
#song-info {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#song-title {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#song-time {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
#progress-container {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#song-info {
|
||||
font-size: 11px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
263
resources/[tools]/nordi_dj/server/main.lua
Normal file
263
resources/[tools]/nordi_dj/server/main.lua
Normal file
|
@ -0,0 +1,263 @@
|
|||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
|
||||
-- YouTube URL Converter
|
||||
local function ConvertYouTubeUrl(url, callback)
|
||||
if not string.match(url, "youtube%.com") and not string.match(url, "youtu%.be") then
|
||||
callback(url) -- Nicht YouTube, direkt zurückgeben
|
||||
return
|
||||
end
|
||||
|
||||
-- Extrahiere Video ID
|
||||
local videoId = ExtractYouTubeVideoId(url)
|
||||
if not videoId then
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
|
||||
if Config.LocalYouTubeConverter.enabled then
|
||||
-- Verwende lokalen Converter
|
||||
PerformHttpRequest(Config.LocalYouTubeConverter.serverUrl, function(errorCode, resultData, resultHeaders)
|
||||
if errorCode == 200 then
|
||||
local data = json.decode(resultData)
|
||||
if data and data.url then
|
||||
callback(data.url)
|
||||
else
|
||||
callback(nil)
|
||||
end
|
||||
else
|
||||
callback(nil)
|
||||
end
|
||||
end, 'POST', json.encode({videoId = videoId}), {['Content-Type'] = 'application/json'})
|
||||
|
||||
elseif Config.YouTubeAPI.enabled then
|
||||
-- Verwende externe API
|
||||
local apiUrl = Config.YouTubeAPI.apiUrl .. "?id=" .. videoId
|
||||
|
||||
PerformHttpRequest(apiUrl, function(errorCode, resultData, resultHeaders)
|
||||
if errorCode == 200 then
|
||||
local data = json.decode(resultData)
|
||||
if data and data.link then
|
||||
callback(data.link)
|
||||
else
|
||||
callback(nil)
|
||||
end
|
||||
else
|
||||
print("YouTube API Error: " .. errorCode)
|
||||
callback(nil)
|
||||
end
|
||||
end, 'GET', '', Config.YouTubeAPI.headers)
|
||||
else
|
||||
callback(nil)
|
||||
end
|
||||
end
|
||||
|
||||
function ExtractYouTubeVideoId(url)
|
||||
-- YouTube URL Patterns
|
||||
local patterns = {
|
||||
"youtube%.com/watch%?v=([%w%-_]+)",
|
||||
"youtube%.com/watch%?.*&v=([%w%-_]+)",
|
||||
"youtu%.be/([%w%-_]+)",
|
||||
"youtube%.com/embed/([%w%-_]+)"
|
||||
}
|
||||
|
||||
for _, pattern in ipairs(patterns) do
|
||||
local videoId = string.match(url, pattern)
|
||||
if videoId then
|
||||
return videoId
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Database Setup
|
||||
CreateThread(function()
|
||||
MySQL.query([[
|
||||
CREATE TABLE IF NOT EXISTS dj_playlists (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
owner VARCHAR(50) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
]])
|
||||
|
||||
MySQL.query([[
|
||||
CREATE TABLE IF NOT EXISTS dj_playlist_songs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
playlist_id INT NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
converted_url TEXT NULL,
|
||||
position INT DEFAULT 0,
|
||||
FOREIGN KEY (playlist_id) REFERENCES dj_playlists(id) ON DELETE CASCADE
|
||||
)
|
||||
]])
|
||||
|
||||
MySQL.query([[
|
||||
CREATE TABLE IF NOT EXISTS dj_url_cache (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
original_url VARCHAR(500) NOT NULL UNIQUE,
|
||||
converted_url TEXT NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
]])
|
||||
end)
|
||||
|
||||
-- Events
|
||||
RegisterNetEvent('dj:server:playMusic', function(data)
|
||||
local src = source
|
||||
|
||||
-- Prüfe Cache zuerst
|
||||
MySQL.query('SELECT converted_url FROM dj_url_cache WHERE original_url = ? AND expires_at > NOW()', {data.url}, function(cached)
|
||||
if cached[1] then
|
||||
-- Verwende gecachte URL
|
||||
local musicData = {
|
||||
title = data.title,
|
||||
url = cached[1].converted_url,
|
||||
volume = data.volume,
|
||||
booth = data.booth,
|
||||
range = data.range
|
||||
}
|
||||
TriggerClientEvent('dj:client:playMusic', -1, musicData)
|
||||
print(('[DJ System] %s spielt Musik ab (cached): %s'):format(GetPlayerName(src), data.title))
|
||||
else
|
||||
-- Konvertiere URL
|
||||
ConvertYouTubeUrl(data.url, function(convertedUrl)
|
||||
if convertedUrl then
|
||||
-- Cache die URL für 1 Stunde
|
||||
MySQL.insert('INSERT INTO dj_url_cache (original_url, converted_url, expires_at) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 1 HOUR)) ON DUPLICATE KEY UPDATE converted_url = VALUES(converted_url), expires_at = VALUES(expires_at)', {
|
||||
data.url,
|
||||
convertedUrl
|
||||
})
|
||||
|
||||
local musicData = {
|
||||
title = data.title,
|
||||
url = convertedUrl,
|
||||
volume = data.volume,
|
||||
booth = data.booth,
|
||||
range = data.range
|
||||
}
|
||||
TriggerClientEvent('dj:client:playMusic', -1, musicData)
|
||||
print(('[DJ System] %s spielt Musik ab (converted): %s'):format(GetPlayerName(src), data.title))
|
||||
else
|
||||
TriggerClientEvent('dj:client:notify', src, 'Fehler beim Konvertieren der YouTube URL!', 'error')
|
||||
print(('[DJ System] Fehler beim Konvertieren der URL: %s'):format(data.url))
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:server:stopMusic', function(booth)
|
||||
local src = source
|
||||
TriggerClientEvent('dj:client:stopMusic', -1)
|
||||
print(('[DJ System] %s hat die Musik gestoppt'):format(GetPlayerName(src)))
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:server:setVolume', function(data)
|
||||
local src = source
|
||||
TriggerClientEvent('dj:client:setVolume', -1, data.volume)
|
||||
print(('[DJ System] %s hat die Lautstärke auf %d%% gesetzt'):format(GetPlayerName(src), data.volume))
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:server:getPlaylists', function()
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
if not Player then return end
|
||||
|
||||
MySQL.query('SELECT * FROM dj_playlists WHERE owner = ?', {Player.PlayerData.citizenid}, function(playlists)
|
||||
local playlistData = {}
|
||||
|
||||
for _, playlist in pairs(playlists) do
|
||||
MySQL.query('SELECT * FROM dj_playlist_songs WHERE playlist_id = ? ORDER BY position', {playlist.id}, function(songs)
|
||||
table.insert(playlistData, {
|
||||
id = playlist.id,
|
||||
name = playlist.name,
|
||||
songs = songs
|
||||
})
|
||||
|
||||
if #playlistData == #playlists then
|
||||
TriggerClientEvent('dj:client:updatePlaylists', src, playlistData)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if #playlists == 0 then
|
||||
TriggerClientEvent('dj:client:updatePlaylists', src, {})
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:server:createPlaylist', function(name)
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
if not Player then return end
|
||||
|
||||
MySQL.insert('INSERT INTO dj_playlists (name, owner) VALUES (?, ?)', {
|
||||
name,
|
||||
Player.PlayerData.citizenid
|
||||
}, function(id)
|
||||
if id then
|
||||
TriggerClientEvent('dj:client:notify', src, 'Playlist "' .. name .. '" wurde erstellt!', 'success')
|
||||
TriggerEvent('dj:server:getPlaylists')
|
||||
else
|
||||
TriggerClientEvent('dj:client:notify', src, 'Fehler beim Erstellen der Playlist!', 'error')
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:server:addSongToPlaylist', function(playlistId, song)
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
if not Player then return end
|
||||
|
||||
MySQL.query('SELECT * FROM dj_playlists WHERE id = ? AND owner = ?', {
|
||||
playlistId,
|
||||
Player.PlayerData.citizenid
|
||||
}, function(result)
|
||||
if result[1] then
|
||||
MySQL.insert('INSERT INTO dj_playlist_songs (playlist_id, title, url) VALUES (?, ?, ?)', {
|
||||
playlistId,
|
||||
song.title,
|
||||
song.url
|
||||
}, function(id)
|
||||
if id then
|
||||
TriggerClientEvent('dj:client:notify', src, 'Song wurde zur Playlist hinzugefügt!', 'success')
|
||||
TriggerEvent('dj:server:getPlaylists')
|
||||
else
|
||||
TriggerClientEvent('dj:client:notify', src, 'Fehler beim Hinzufügen des Songs!', 'error')
|
||||
end
|
||||
end)
|
||||
else
|
||||
TriggerClientEvent('dj:client:notify', src, 'Du hast keine Berechtigung für diese Playlist!', 'error')
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dj:server:deletePlaylist', function(playlistId)
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
if not Player then return end
|
||||
|
||||
MySQL.query('DELETE FROM dj_playlists WHERE id = ? AND owner = ?', {
|
||||
playlistId,
|
||||
Player.PlayerData.citizenid
|
||||
}, function(affectedRows)
|
||||
if affectedRows > 0 then
|
||||
TriggerClientEvent('dj:client:notify', src, 'Playlist wurde gelöscht!', 'success')
|
||||
TriggerEvent('dj:server:getPlaylists')
|
||||
else
|
||||
TriggerClientEvent('dj:client:notify', src, 'Fehler beim Löschen der Playlist!', 'error')
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Cache Cleanup (läuft alle 30 Minuten)
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1800000) -- 30 Minuten
|
||||
MySQL.query('DELETE FROM dj_url_cache WHERE expires_at < NOW()')
|
||||
print('[DJ System] Cache bereinigt')
|
||||
end
|
||||
end)
|
Loading…
Add table
Add a link
Reference in a new issue