diff --git a/resources/[tools]/nordi_dj/client/main.lua b/resources/[tools]/nordi_dj/client/main.lua
index f94ec0982..55069d83a 100644
--- a/resources/[tools]/nordi_dj/client/main.lua
+++ b/resources/[tools]/nordi_dj/client/main.lua
@@ -622,7 +622,7 @@ local function extractVideoId(url)
return nil
end
--- Aktualisiere die PlayMusic Funktion
+-- Aktualisierte PlayMusic Funktion
function PlayMusic(title, url, volume)
if not title or not url then
lib.notify({
@@ -633,29 +633,31 @@ function PlayMusic(title, url, volume)
return
end
- -- Bereinige YouTube URL
- local cleanedUrl = cleanYouTubeUrl(url)
- local videoId = extractVideoId(cleanedUrl)
-
- if videoId then
+ -- Bereinige URL von Playlist-Parametern
+ local cleanUrl = url
+ if string.find(url, "youtube") then
+ cleanUrl = string.gsub(url, "&list=.-$", "")
+ cleanUrl = string.gsub(cleanUrl, "&start_radio=.-$", "")
+ cleanUrl = string.gsub(cleanUrl, "&index=.-$", "")
+
lib.notify({
title = 'DJ System',
- description = 'YouTube Video wird geladen: ' .. title,
+ description = 'YouTube Video wird gestreamt: ' .. title,
type = 'info'
})
-
- print('[DJ System] YouTube Video ID: ' .. videoId)
- print('[DJ System] Bereinigte URL: ' .. cleanedUrl)
end
+ print('[DJ System] Streaming: ' .. title .. ' | Clean URL: ' .. cleanUrl)
+
-- Sende an Server
- TriggerServerEvent('dj:playMusic', title, cleanedUrl, volume or 50)
+ TriggerServerEvent('dj:playMusic', title, cleanUrl, volume or 50)
-- Update lokale Variablen
isPlaying = true
currentSong = {
title = title,
- url = cleanedUrl,
+ url = cleanUrl,
volume = volume or 50
}
end
+
diff --git a/resources/[tools]/nordi_dj/html/index.html b/resources/[tools]/nordi_dj/html/index.html
index f7aef6b10..7f3fa6d63 100644
--- a/resources/[tools]/nordi_dj/html/index.html
+++ b/resources/[tools]/nordi_dj/html/index.html
@@ -3,30 +3,21 @@
- DJ System
+ DJ System - YouTube Streaming
+
-
-
-
-
-
-
-
-
+
+
diff --git a/resources/[tools]/nordi_dj/html/script.js b/resources/[tools]/nordi_dj/html/script.js
index dd7ca2526..9bc2164f9 100644
--- a/resources/[tools]/nordi_dj/html/script.js
+++ b/resources/[tools]/nordi_dj/html/script.js
@@ -4,78 +4,152 @@ let isPlaying = false;
let currentSong = null;
let fadeInterval = null;
+// YouTube Player API laden
+let youtubeAPIReady = false;
+let youtubePlayer = null;
+
// Initialize when page loads
document.addEventListener('DOMContentLoaded', function() {
- audioPlayer = document.getElementById('audio-player');
+ loadYouTubeAPI();
setupAudioPlayer();
});
-// Setup audio player with event listeners
-function setupAudioPlayer() {
- if (!audioPlayer) return;
+// YouTube API laden
+function loadYouTubeAPI() {
+ // YouTube IFrame API Script laden
+ const tag = document.createElement('script');
+ tag.src = 'https://www.youtube.com/iframe_api';
+ const firstScriptTag = document.getElementsByTagName('script')[0];
+ firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+}
+
+// YouTube API Ready Callback
+window.onYouTubeIframeAPIReady = function() {
+ console.log('DJ System: YouTube API ready');
+ youtubeAPIReady = true;
- // Audio Events
- audioPlayer.addEventListener('loadstart', function() {
- console.log('DJ System: Loading started');
- });
+ // Erstelle versteckten YouTube Player
+ createYouTubePlayer();
+};
+
+// YouTube Player erstellen
+function createYouTubePlayer() {
+ // Container für YouTube Player
+ const playerContainer = document.createElement('div');
+ playerContainer.id = 'youtube-player-container';
+ playerContainer.style.position = 'absolute';
+ playerContainer.style.top = '-9999px';
+ playerContainer.style.left = '-9999px';
+ playerContainer.style.width = '1px';
+ playerContainer.style.height = '1px';
+ playerContainer.style.opacity = '0';
+ document.body.appendChild(playerContainer);
- 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
+ youtubePlayer = new YT.Player('youtube-player-container', {
+ height: '1',
+ width: '1',
+ playerVars: {
+ 'autoplay': 0,
+ 'controls': 0,
+ 'disablekb': 1,
+ 'fs': 0,
+ 'modestbranding': 1,
+ 'playsinline': 1,
+ 'rel': 0,
+ 'showinfo': 0,
+ 'iv_load_policy': 3,
+ 'cc_load_policy': 0,
+ 'start': 0,
+ 'origin': window.location.origin
+ },
+ events: {
+ 'onReady': onYouTubePlayerReady,
+ 'onStateChange': onYouTubePlayerStateChange,
+ 'onError': onYouTubePlayerError
}
});
}
-// Main message handler from FiveM
+function onYouTubePlayerReady(event) {
+ console.log('DJ System: YouTube Player ready');
+}
+
+function onYouTubePlayerStateChange(event) {
+ switch(event.data) {
+ case YT.PlayerState.PLAYING:
+ console.log('DJ System: YouTube playing');
+ isPlaying = true;
+ break;
+ case YT.PlayerState.PAUSED:
+ console.log('DJ System: YouTube paused');
+ isPlaying = false;
+ break;
+ case YT.PlayerState.ENDED:
+ console.log('DJ System: YouTube ended');
+ isPlaying = false;
+ notifyFiveM('songEnded', {});
+ break;
+ case YT.PlayerState.BUFFERING:
+ console.log('DJ System: YouTube buffering');
+ break;
+ }
+}
+
+function onYouTubePlayerError(event) {
+ console.error('DJ System: YouTube error:', event.data);
+ let errorMessage = 'YouTube Fehler';
+
+ switch(event.data) {
+ case 2:
+ errorMessage = 'Ungültige Video ID';
+ break;
+ case 5:
+ errorMessage = 'HTML5 Player Fehler';
+ break;
+ case 100:
+ errorMessage = 'Video nicht gefunden';
+ break;
+ case 101:
+ case 150:
+ errorMessage = 'Video nicht verfügbar (Einbettung deaktiviert)';
+ break;
+ }
+
+ notifyFiveM('audioError', { error: errorMessage });
+}
+
+// Setup normaler Audio Player für direkte URLs
+function setupAudioPlayer() {
+ audioPlayer = document.getElementById('audio-player');
+ if (!audioPlayer) {
+ audioPlayer = document.createElement('audio');
+ audioPlayer.id = 'audio-player';
+ audioPlayer.style.display = 'none';
+ document.body.appendChild(audioPlayer);
+ }
+
+ audioPlayer.addEventListener('play', () => {
+ isPlaying = true;
+ console.log('DJ System: Audio playing');
+ });
+
+ audioPlayer.addEventListener('pause', () => {
+ isPlaying = false;
+ console.log('DJ System: Audio paused');
+ });
+
+ audioPlayer.addEventListener('ended', () => {
+ isPlaying = false;
+ notifyFiveM('songEnded', {});
+ });
+
+ audioPlayer.addEventListener('error', (e) => {
+ console.error('DJ System: Audio error', e);
+ notifyFiveM('audioError', { error: 'Audio playback error' });
+ });
+}
+
+// Main message handler
window.addEventListener('message', function(event) {
const data = event.data;
@@ -89,12 +163,6 @@ window.addEventListener('message', function(event) {
case 'setVolume':
setVolume(data.volume);
break;
- case 'fadeOut':
- fadeOut();
- break;
- case 'fadeIn':
- fadeIn();
- break;
case 'pauseMusic':
pauseMusic();
break;
@@ -104,90 +172,158 @@ window.addEventListener('message', function(event) {
}
});
-// Play music function with YouTube support
+// Musik abspielen - YouTube oder direkte URL
async function playMusic(url, volume, title = 'Unknown') {
- if (!audioPlayer) {
- console.error('DJ System: Audio player not initialized');
- return;
- }
-
try {
- // Stop current music first
+ console.log('DJ System: Playing:', title, url);
+
+ // Stoppe aktuelle Musik
stopMusic();
- console.log('DJ System: Attempting to play:', title, url);
-
- // Set volume
+ // Setze Lautstärke
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;
+ // Prüfe ob YouTube URL
+ if (isYouTubeUrl(url)) {
+ await playYouTubeMusic(url, volume, title);
+ } else {
+ await playDirectMusic(url, volume, title);
}
- // Set source and play
- audioPlayer.src = playableUrl;
- audioPlayer.load();
-
- // Store current song info
+ // Speichere aktuelle Song Info
currentSong = {
title: title,
url: url,
- playableUrl: playableUrl
+ volume: volume
};
- // 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);
+ console.error('DJ System: Error playing music:', error);
+ notifyFiveM('audioError', { error: error.message });
}
}
-// Process different URL types
-async function processUrl(url) {
+// YouTube Musik abspielen
+async function playYouTubeMusic(url, volume, title) {
+ if (!youtubeAPIReady || !youtubePlayer) {
+ throw new Error('YouTube Player nicht bereit');
+ }
+
+ const videoId = extractYouTubeVideoId(url);
+ if (!videoId) {
+ throw new Error('Ungültige YouTube URL');
+ }
+
+ console.log('DJ System: Playing YouTube video:', videoId);
+
+ // Lade und spiele YouTube Video
+ youtubePlayer.loadVideoById({
+ videoId: videoId,
+ startSeconds: 0
+ });
+
+ // Setze Lautstärke
+ youtubePlayer.setVolume(volume || 50);
+
+ // Warte kurz und starte
+ setTimeout(() => {
+ youtubePlayer.playVideo();
+ }, 1000);
+}
+
+// Direkte Audio URL abspielen
+async function playDirectMusic(url, volume, title) {
+ if (!audioPlayer) {
+ throw new Error('Audio Player nicht verfügbar');
+ }
+
+ console.log('DJ System: Playing direct audio:', url);
+
+ audioPlayer.src = url;
+ audioPlayer.volume = (volume || 50) / 100;
+ audioPlayer.load();
+
+ const playPromise = audioPlayer.play();
+ if (playPromise !== undefined) {
+ await playPromise;
+ }
+}
+
+// Musik stoppen
+function stopMusic() {
try {
- // Check if it's a YouTube URL
- if (isYouTubeUrl(url)) {
- console.log('DJ System: Processing YouTube URL');
- return await convertYouTubeUrl(url);
+ // Stoppe YouTube Player
+ if (youtubePlayer && youtubeAPIReady) {
+ youtubePlayer.stopVideo();
}
- // Check if it's a direct audio URL
- if (isDirectAudioUrl(url)) {
- console.log('DJ System: Direct audio URL detected');
- return url;
+ // Stoppe Audio Player
+ if (audioPlayer) {
+ audioPlayer.pause();
+ audioPlayer.currentTime = 0;
+ audioPlayer.src = '';
}
- // Try to use URL as-is (might be pre-converted)
- console.log('DJ System: Using URL as-is');
- return url;
+ isPlaying = false;
+ currentSong = null;
+
+ console.log('DJ System: Music stopped');
} catch (error) {
- console.error('DJ System: Error processing URL:', error);
- return null;
+ console.error('DJ System: Error stopping music:', error);
}
}
-// Check if URL is YouTube
+// Musik pausieren
+function pauseMusic() {
+ try {
+ if (currentSong && isYouTubeUrl(currentSong.url)) {
+ if (youtubePlayer && youtubeAPIReady) {
+ youtubePlayer.pauseVideo();
+ }
+ } else if (audioPlayer) {
+ audioPlayer.pause();
+ }
+ } catch (error) {
+ console.error('DJ System: Error pausing music:', error);
+ }
+}
+
+// Musik fortsetzen
+function resumeMusic() {
+ try {
+ if (currentSong && isYouTubeUrl(currentSong.url)) {
+ if (youtubePlayer && youtubeAPIReady) {
+ youtubePlayer.playVideo();
+ }
+ } else if (audioPlayer) {
+ audioPlayer.play();
+ }
+ } catch (error) {
+ console.error('DJ System: Error resuming music:', error);
+ }
+}
+
+// Lautstärke setzen
+function setVolume(volume) {
+ try {
+ currentVolume = Math.max(0, Math.min(100, volume));
+
+ if (currentSong && isYouTubeUrl(currentSong.url)) {
+ if (youtubePlayer && youtubeAPIReady) {
+ youtubePlayer.setVolume(currentVolume);
+ }
+ } else if (audioPlayer) {
+ audioPlayer.volume = currentVolume / 100;
+ }
+
+ console.log('DJ System: Volume set to', currentVolume + '%');
+ } catch (error) {
+ console.error('DJ System: Error setting volume:', error);
+ }
+}
+
+// YouTube URL prüfen
function isYouTubeUrl(url) {
const youtubePatterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
@@ -197,38 +333,7 @@ function isYouTubeUrl(url) {
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
+// YouTube Video ID extrahieren
function extractYouTubeVideoId(url) {
const patterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
@@ -245,157 +350,30 @@ function extractYouTubeVideoId(url) {
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`, {
+// FiveM benachrichtigen
+function notifyFiveM(event, data) {
+ fetch(`https://${GetParentResourceName()}/` + event, {
method: 'POST',
headers: {
- 'Content-Type': 'application/json; charset=UTF-8',
+ 'Content-Type': 'application/json'
},
- body: JSON.stringify({
- error: message
- })
+ body: JSON.stringify(data)
}).catch(err => {
- console.error('DJ System: Failed to notify error:', err);
+ console.error('DJ System: Failed to notify FiveM:', err);
});
}
-// Get current resource name
function GetParentResourceName() {
return window.location.hostname;
}
-// Debug functions (can be called from browser console)
+// Debug
window.djDebug = {
getCurrentSong: () => currentSong,
getVolume: () => currentVolume,
isPlaying: () => isPlaying,
- getAudioPlayer: () => audioPlayer,
- testPlay: (url) => playMusic(url, 50, 'Test Song'),
- testStop: () => stopMusic(),
- testVolume: (vol) => setVolume(vol)
+ youtubeReady: () => youtubeAPIReady,
+ testYouTube: (videoId) => playYouTubeMusic('https://www.youtube.com/watch?v=' + videoId, 50, 'Test')
};
-// Log when script is loaded
-console.log('DJ System: Script loaded and ready');
+console.log('DJ System: YouTube streaming system loaded');
diff --git a/resources/[tools]/nordi_dj/server/main.lua b/resources/[tools]/nordi_dj/server/main.lua
index 68c565d4d..96f9cb0de 100644
--- a/resources/[tools]/nordi_dj/server/main.lua
+++ b/resources/[tools]/nordi_dj/server/main.lua
@@ -360,54 +360,61 @@ local function useAlternativeConversion(originalUrl, videoId, callback)
end)
end
--- Aktualisiere die PlayMusic Funktion
+-- Aktualisierte PlayMusic Funktion
RegisterServerEvent('dj:playMusic')
AddEventHandler('dj:playMusic', function(title, url, volume)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
- print('[DJ System] Spiele Musik ab: ' .. title .. ' | URL: ' .. url)
-
- -- Prüfe ob es eine YouTube URL ist
- if string.match(url, "youtube%.com") or string.match(url, "youtu%.be") then
- convertYouTubeUrl(url, function(convertedUrl, error)
- if error then
- TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Konvertieren: ' .. error, 'error')
- return
- end
-
- if convertedUrl then
- -- Sende konvertierte URL an alle Clients
- TriggerClientEvent('dj:playMusicClient', -1, title, convertedUrl, volume)
-
- -- Speichere in Session History
- MySQL.Async.execute('INSERT INTO dj_session_history (dj_citizenid, dj_name, booth_name, song_title, song_url, song_type, volume, session_start) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())', {
- Player.PlayerData.citizenid,
- Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname,
- 'Unknown', -- Du kannst hier den Booth-Namen hinzufügen
- title,
- url,
- 'youtube',
- volume
- })
- else
- TriggerClientEvent('QBCore:Notify', src, 'Konvertierung fehlgeschlagen', 'error')
- end
- end)
- else
- -- Direkte Audio URL
- TriggerClientEvent('dj:playMusicClient', -1, title, url, volume)
+ -- Bereinige YouTube URL (entferne Playlist-Parameter)
+ local cleanUrl = url
+ if string.find(url, "youtube%.com") or string.find(url, "youtu%.be") then
+ -- Entferne &list= und andere Parameter
+ cleanUrl = string.gsub(url, "&list=.-$", "")
+ cleanUrl = string.gsub(cleanUrl, "&start_radio=.-$", "")
+ cleanUrl = string.gsub(cleanUrl, "&index=.-$", "")
- -- Speichere in Session History
- MySQL.Async.execute('INSERT INTO dj_session_history (dj_citizenid, dj_name, booth_name, song_title, song_url, song_type, volume, session_start) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())', {
- Player.PlayerData.citizenid,
- Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname,
- 'Unknown',
- title,
- url,
- 'direct',
- volume
- })
+ print('[DJ System] Bereinigte YouTube URL: ' .. cleanUrl)
end
+
+ print('[DJ System] Streaming: ' .. title .. ' | URL: ' .. cleanUrl)
+
+ -- Sende direkt an alle Clients zum Streamen
+ TriggerClientEvent('dj:playMusicClient', -1, title, cleanUrl, volume)
+
+ -- Speichere in Datenbank
+ local songType = 'direct'
+ if string.find(cleanUrl, "youtube") then
+ songType = 'youtube'
+ end
+
+ MySQL.Async.execute('INSERT INTO dj_session_history (dj_citizenid, dj_name, booth_name, song_title, song_url, song_type, volume, session_start) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())', {
+ Player.PlayerData.citizenid,
+ Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname,
+ 'DJ Booth',
+ title,
+ cleanUrl,
+ songType,
+ volume
+ })
+
+ TriggerClientEvent('QBCore:Notify', src, 'Streaming: ' .. title, 'success')
end)
+
+-- Audio Error Handler
+RegisterNUICallback('audioError', function(data, cb)
+ local src = source
+ print('[DJ System] Audio Error: ' .. (data.error or 'Unknown'))
+
+ TriggerClientEvent('QBCore:Notify', src, 'Audio Fehler: ' .. (data.error or 'Unbekannt'), 'error')
+ cb('ok')
+end)
+
+-- Song Ended Handler
+RegisterNUICallback('songEnded', function(data, cb)
+ print('[DJ System] Song ended')
+ -- Hier könntest du Playlist-Logik hinzufügen
+ cb('ok')
+end)
+