1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-03 17:09:00 +02:00
parent 9a29e35e64
commit cac2b97954
4 changed files with 338 additions and 360 deletions

View file

@ -622,7 +622,7 @@ local function extractVideoId(url)
return nil return nil
end end
-- Aktualisiere die PlayMusic Funktion -- Aktualisierte PlayMusic Funktion
function PlayMusic(title, url, volume) function PlayMusic(title, url, volume)
if not title or not url then if not title or not url then
lib.notify({ lib.notify({
@ -633,29 +633,31 @@ function PlayMusic(title, url, volume)
return return
end end
-- Bereinige YouTube URL -- Bereinige URL von Playlist-Parametern
local cleanedUrl = cleanYouTubeUrl(url) local cleanUrl = url
local videoId = extractVideoId(cleanedUrl) if string.find(url, "youtube") then
cleanUrl = string.gsub(url, "&list=.-$", "")
cleanUrl = string.gsub(cleanUrl, "&start_radio=.-$", "")
cleanUrl = string.gsub(cleanUrl, "&index=.-$", "")
if videoId then
lib.notify({ lib.notify({
title = 'DJ System', title = 'DJ System',
description = 'YouTube Video wird geladen: ' .. title, description = 'YouTube Video wird gestreamt: ' .. title,
type = 'info' type = 'info'
}) })
print('[DJ System] YouTube Video ID: ' .. videoId)
print('[DJ System] Bereinigte URL: ' .. cleanedUrl)
end end
print('[DJ System] Streaming: ' .. title .. ' | Clean URL: ' .. cleanUrl)
-- Sende an Server -- Sende an Server
TriggerServerEvent('dj:playMusic', title, cleanedUrl, volume or 50) TriggerServerEvent('dj:playMusic', title, cleanUrl, volume or 50)
-- Update lokale Variablen -- Update lokale Variablen
isPlaying = true isPlaying = true
currentSong = { currentSong = {
title = title, title = title,
url = cleanedUrl, url = cleanUrl,
volume = volume or 50 volume = volume or 50
} }
end end

View file

@ -3,30 +3,21 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DJ System</title> <title>DJ System - YouTube Streaming</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div id="music-player"> <div id="music-player">
<!-- Normaler Audio Player für direkte URLs -->
<audio <audio
id="audio-player" id="audio-player"
preload="auto" preload="auto"
crossorigin="anonymous" crossorigin="anonymous"
controls="false"
style="display: none;"> style="display: none;">
Dein Browser unterstützt das Audio-Element nicht.
</audio> </audio>
<!-- Optional: Progress indicator (hidden by default) --> <!-- YouTube Player wird dynamisch erstellt -->
<div id="progress-container" style="display: none;"> <!-- Container wird von JavaScript erstellt -->
<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> </div>
<script src="script.js"></script> <script src="script.js"></script>

View file

@ -4,78 +4,152 @@ let isPlaying = false;
let currentSong = null; let currentSong = null;
let fadeInterval = null; let fadeInterval = null;
// YouTube Player API laden
let youtubeAPIReady = false;
let youtubePlayer = null;
// Initialize when page loads // Initialize when page loads
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
audioPlayer = document.getElementById('audio-player'); loadYouTubeAPI();
setupAudioPlayer(); setupAudioPlayer();
}); });
// Setup audio player with event listeners // YouTube API laden
function setupAudioPlayer() { function loadYouTubeAPI() {
if (!audioPlayer) return; // 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);
}
// Audio Events // YouTube API Ready Callback
audioPlayer.addEventListener('loadstart', function() { window.onYouTubeIframeAPIReady = function() {
console.log('DJ System: Loading started'); console.log('DJ System: YouTube API ready');
}); youtubeAPIReady = true;
audioPlayer.addEventListener('canplay', function() { // Erstelle versteckten YouTube Player
console.log('DJ System: Can start playing'); createYouTubePlayer();
}); };
audioPlayer.addEventListener('play', function() { // YouTube Player erstellen
console.log('DJ System: Playback started'); function createYouTubePlayer() {
isPlaying = true; // 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('pause', function() { youtubePlayer = new YT.Player('youtube-player-container', {
console.log('DJ System: Playback paused'); height: '1',
isPlaying = false; width: '1',
}); playerVars: {
'autoplay': 0,
audioPlayer.addEventListener('ended', function() { 'controls': 0,
console.log('DJ System: Song ended'); 'disablekb': 1,
isPlaying = false; 'fs': 0,
// Notify FiveM that song ended (for playlist functionality) 'modestbranding': 1,
fetch(`https://${GetParentResourceName()}/songEnded`, { 'playsinline': 1,
method: 'POST', 'rel': 0,
headers: { 'showinfo': 0,
'Content-Type': 'application/json; charset=UTF-8', 'iv_load_policy': 3,
}, 'cc_load_policy': 0,
body: JSON.stringify({}) 'start': 0,
}); 'origin': window.location.origin
}); },
events: {
audioPlayer.addEventListener('error', function(e) { 'onReady': onYouTubePlayerReady,
console.error('DJ System: Audio error', e); 'onStateChange': onYouTubePlayerStateChange,
isPlaying = false; 'onError': onYouTubePlayerError
// 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 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) { window.addEventListener('message', function(event) {
const data = event.data; const data = event.data;
@ -89,12 +163,6 @@ window.addEventListener('message', function(event) {
case 'setVolume': case 'setVolume':
setVolume(data.volume); setVolume(data.volume);
break; break;
case 'fadeOut':
fadeOut();
break;
case 'fadeIn':
fadeIn();
break;
case 'pauseMusic': case 'pauseMusic':
pauseMusic(); pauseMusic();
break; 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') { async function playMusic(url, volume, title = 'Unknown') {
if (!audioPlayer) {
console.error('DJ System: Audio player not initialized');
return;
}
try { try {
// Stop current music first console.log('DJ System: Playing:', title, url);
// Stoppe aktuelle Musik
stopMusic(); stopMusic();
console.log('DJ System: Attempting to play:', title, url); // Setze Lautstärke
// Set volume
currentVolume = volume || 50; currentVolume = volume || 50;
audioPlayer.volume = currentVolume / 100;
// Handle different URL types // Prüfe ob YouTube URL
let playableUrl = await processUrl(url); if (isYouTubeUrl(url)) {
await playYouTubeMusic(url, volume, title);
if (!playableUrl) { } else {
console.error('DJ System: Could not process URL:', url); await playDirectMusic(url, volume, title);
notifyError('Could not process audio URL');
return;
} }
// Set source and play // Speichere aktuelle Song Info
audioPlayer.src = playableUrl;
audioPlayer.load();
// Store current song info
currentSong = { currentSong = {
title: title, title: title,
url: url, 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) { } catch (error) {
console.error('DJ System: Error in playMusic:', error); console.error('DJ System: Error playing music:', error);
notifyError('Error playing music: ' + error.message); notifyFiveM('audioError', { error: error.message });
} }
} }
// Process different URL types // YouTube Musik abspielen
async function processUrl(url) { 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 { try {
// Check if it's a YouTube URL // Stoppe YouTube Player
if (isYouTubeUrl(url)) { if (youtubePlayer && youtubeAPIReady) {
console.log('DJ System: Processing YouTube URL'); youtubePlayer.stopVideo();
return await convertYouTubeUrl(url);
} }
// Check if it's a direct audio URL // Stoppe Audio Player
if (isDirectAudioUrl(url)) { if (audioPlayer) {
console.log('DJ System: Direct audio URL detected'); audioPlayer.pause();
return url; audioPlayer.currentTime = 0;
audioPlayer.src = '';
} }
// Try to use URL as-is (might be pre-converted) isPlaying = false;
console.log('DJ System: Using URL as-is'); currentSong = null;
return url;
console.log('DJ System: Music stopped');
} catch (error) { } catch (error) {
console.error('DJ System: Error processing URL:', error); console.error('DJ System: Error stopping music:', error);
return null;
} }
} }
// 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) { function isYouTubeUrl(url) {
const youtubePatterns = [ const youtubePatterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
@ -197,38 +333,7 @@ function isYouTubeUrl(url) {
return youtubePatterns.some(pattern => pattern.test(url)); return youtubePatterns.some(pattern => pattern.test(url));
} }
// Check if URL is direct audio // YouTube Video ID extrahieren
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) { function extractYouTubeVideoId(url) {
const patterns = [ const patterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
@ -245,157 +350,30 @@ function extractYouTubeVideoId(url) {
return null; return null;
} }
// Stop music // FiveM benachrichtigen
function stopMusic() { function notifyFiveM(event, data) {
if (!audioPlayer) return; fetch(`https://${GetParentResourceName()}/` + event, {
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', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify(data)
error: message
})
}).catch(err => { }).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() { function GetParentResourceName() {
return window.location.hostname; return window.location.hostname;
} }
// Debug functions (can be called from browser console) // Debug
window.djDebug = { window.djDebug = {
getCurrentSong: () => currentSong, getCurrentSong: () => currentSong,
getVolume: () => currentVolume, getVolume: () => currentVolume,
isPlaying: () => isPlaying, isPlaying: () => isPlaying,
getAudioPlayer: () => audioPlayer, youtubeReady: () => youtubeAPIReady,
testPlay: (url) => playMusic(url, 50, 'Test Song'), testYouTube: (videoId) => playYouTubeMusic('https://www.youtube.com/watch?v=' + videoId, 50, 'Test')
testStop: () => stopMusic(),
testVolume: (vol) => setVolume(vol)
}; };
// Log when script is loaded console.log('DJ System: YouTube streaming system loaded');
console.log('DJ System: Script loaded and ready');

View file

@ -360,54 +360,61 @@ local function useAlternativeConversion(originalUrl, videoId, callback)
end) end)
end end
-- Aktualisiere die PlayMusic Funktion -- Aktualisierte PlayMusic Funktion
RegisterServerEvent('dj:playMusic') RegisterServerEvent('dj:playMusic')
AddEventHandler('dj:playMusic', function(title, url, volume) AddEventHandler('dj:playMusic', function(title, url, volume)
local src = source local src = source
local Player = QBCore.Functions.GetPlayer(src) local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end if not Player then return end
print('[DJ System] Spiele Musik ab: ' .. title .. ' | URL: ' .. url) -- 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=.-$", "")
-- Prüfe ob es eine YouTube URL ist print('[DJ System] Bereinigte YouTube URL: ' .. cleanUrl)
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)
-- 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
})
end 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) 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)