2025-08-03 17:46:16 +02:00
|
|
|
// DJ System - Professional Interface
|
|
|
|
let djInterface = {
|
|
|
|
decks: {
|
|
|
|
A: {
|
|
|
|
track: null,
|
|
|
|
isPlaying: false,
|
|
|
|
volume: 75,
|
|
|
|
pitch: 0,
|
|
|
|
cuePoint: 0,
|
|
|
|
player: null,
|
|
|
|
youtubePlayer: null
|
|
|
|
},
|
|
|
|
B: {
|
|
|
|
track: null,
|
|
|
|
isPlaying: false,
|
|
|
|
volume: 75,
|
|
|
|
pitch: 0,
|
|
|
|
cuePoint: 0,
|
|
|
|
player: null,
|
|
|
|
youtubePlayer: null
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mixer: {
|
|
|
|
crossfader: 50,
|
|
|
|
masterVolume: 80,
|
|
|
|
eq: {
|
|
|
|
A: { high: 0, mid: 0, low: 0 },
|
|
|
|
B: { high: 0, mid: 0, low: 0 }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
effects: {
|
|
|
|
reverb: false,
|
|
|
|
delay: false,
|
|
|
|
filter: false,
|
|
|
|
flanger: false,
|
|
|
|
wetDry: 0
|
|
|
|
},
|
|
|
|
currentDeck: null,
|
|
|
|
isRecording: false,
|
|
|
|
youtubeAPIReady: false
|
|
|
|
};
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:09:00 +02:00
|
|
|
// 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');
|
2025-08-03 17:46:16 +02:00
|
|
|
djInterface.youtubeAPIReady = true;
|
2025-08-03 17:09:00 +02:00
|
|
|
};
|
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Hier ist die fehlende Funktion
|
|
|
|
function createYouTubePlayerForDeck(deck, videoId) {
|
|
|
|
if (!djInterface.youtubeAPIReady) {
|
|
|
|
console.error('DJ System: YouTube API not ready');
|
|
|
|
showNotification('YouTube API not ready', 'error');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Container für YouTube Player erstellen
|
|
|
|
const containerId = `youtube-player-${deck.toLowerCase()}`;
|
|
|
|
let container = document.getElementById(containerId);
|
|
|
|
|
|
|
|
if (!container) {
|
|
|
|
container = document.createElement('div');
|
|
|
|
container.id = containerId;
|
|
|
|
container.style.position = 'absolute';
|
|
|
|
container.style.top = '-9999px';
|
|
|
|
container.style.left = '-9999px';
|
|
|
|
container.style.width = '1px';
|
|
|
|
container.style.height = '1px';
|
|
|
|
container.style.opacity = '0';
|
|
|
|
document.body.appendChild(container);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bestehenden Player zerstören, falls vorhanden
|
|
|
|
if (djInterface.decks[deck].youtubePlayer) {
|
|
|
|
try {
|
|
|
|
djInterface.decks[deck].youtubePlayer.destroy();
|
|
|
|
} catch (e) {
|
|
|
|
console.error('DJ System: Error destroying YouTube player', e);
|
|
|
|
}
|
|
|
|
}
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Neuen Player erstellen
|
|
|
|
djInterface.decks[deck].youtubePlayer = new YT.Player(containerId, {
|
2025-08-03 17:09:00 +02:00
|
|
|
height: '1',
|
|
|
|
width: '1',
|
2025-08-03 17:46:16 +02:00
|
|
|
videoId: videoId,
|
2025-08-03 17:09:00 +02:00
|
|
|
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,
|
|
|
|
'origin': window.location.origin
|
|
|
|
},
|
|
|
|
events: {
|
2025-08-03 17:46:16 +02:00
|
|
|
'onReady': function(event) {
|
|
|
|
console.log(`DJ System: YouTube player ready for Deck ${deck}`);
|
|
|
|
// Setze Lautstärke
|
|
|
|
event.target.setVolume(djInterface.decks[deck].volume);
|
|
|
|
|
|
|
|
// Generiere Waveform
|
|
|
|
generateWaveform(deck, videoId);
|
|
|
|
|
|
|
|
// Aktualisiere UI
|
|
|
|
const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`);
|
|
|
|
if (vinyl) {
|
|
|
|
vinyl.classList.add('loaded');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'onStateChange': function(event) {
|
|
|
|
handleYouTubeStateChange(deck, event);
|
|
|
|
},
|
|
|
|
'onError': function(event) {
|
|
|
|
handleYouTubeError(deck, event);
|
|
|
|
}
|
2025-08-03 17:09:00 +02:00
|
|
|
}
|
2025-08-03 16:51:12 +02:00
|
|
|
});
|
2025-08-03 17:09:00 +02:00
|
|
|
}
|
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// YouTube State Change Handler
|
|
|
|
function handleYouTubeStateChange(deck, event) {
|
|
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
const playBtn = document.getElementById(`play-${deck.toLowerCase()}`);
|
|
|
|
const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`);
|
|
|
|
|
2025-08-03 17:09:00 +02:00
|
|
|
switch(event.data) {
|
|
|
|
case YT.PlayerState.PLAYING:
|
2025-08-03 17:46:16 +02:00
|
|
|
console.log(`DJ System: YouTube playing on Deck ${deck}`);
|
|
|
|
deckData.isPlaying = true;
|
|
|
|
if (playBtn) {
|
|
|
|
playBtn.innerHTML = '<i class="fas fa-pause"></i>';
|
|
|
|
playBtn.classList.add('playing');
|
|
|
|
}
|
|
|
|
if (vinyl) {
|
|
|
|
vinyl.classList.add('spinning');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify FiveM
|
|
|
|
notifyFiveM('deckStateChanged', {
|
|
|
|
deck: deck,
|
|
|
|
isPlaying: true,
|
|
|
|
track: deckData.track
|
|
|
|
});
|
2025-08-03 17:09:00 +02:00
|
|
|
break;
|
2025-08-03 17:46:16 +02:00
|
|
|
|
2025-08-03 17:09:00 +02:00
|
|
|
case YT.PlayerState.PAUSED:
|
2025-08-03 17:46:16 +02:00
|
|
|
console.log(`DJ System: YouTube paused on Deck ${deck}`);
|
|
|
|
deckData.isPlaying = false;
|
|
|
|
if (playBtn) {
|
|
|
|
playBtn.innerHTML = '<i class="fas fa-play"></i>';
|
|
|
|
playBtn.classList.remove('playing');
|
|
|
|
}
|
|
|
|
if (vinyl) {
|
|
|
|
vinyl.classList.remove('spinning');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify FiveM
|
|
|
|
notifyFiveM('deckStateChanged', {
|
|
|
|
deck: deck,
|
|
|
|
isPlaying: false,
|
|
|
|
track: deckData.track
|
|
|
|
});
|
2025-08-03 17:09:00 +02:00
|
|
|
break;
|
2025-08-03 17:46:16 +02:00
|
|
|
|
2025-08-03 17:09:00 +02:00
|
|
|
case YT.PlayerState.ENDED:
|
2025-08-03 17:46:16 +02:00
|
|
|
console.log(`DJ System: YouTube ended on Deck ${deck}`);
|
|
|
|
deckData.isPlaying = false;
|
|
|
|
if (playBtn) {
|
|
|
|
playBtn.innerHTML = '<i class="fas fa-play"></i>';
|
|
|
|
playBtn.classList.remove('playing');
|
|
|
|
}
|
|
|
|
if (vinyl) {
|
|
|
|
vinyl.classList.remove('spinning');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify FiveM
|
|
|
|
notifyFiveM('songEnded', {
|
|
|
|
deck: deck
|
|
|
|
});
|
2025-08-03 17:09:00 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// YouTube Error Handler
|
|
|
|
function handleYouTubeError(deck, event) {
|
|
|
|
console.error(`DJ System: YouTube error on Deck ${deck}:`, event.data);
|
2025-08-03 17:09:00 +02:00
|
|
|
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;
|
|
|
|
}
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
showNotification(errorMessage, 'error');
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Notify FiveM
|
|
|
|
notifyFiveM('audioError', {
|
|
|
|
deck: deck,
|
|
|
|
error: errorMessage
|
2025-08-03 16:51:12 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Füge den Rest deines bestehenden script.js-Codes hier ein
|
|
|
|
// ...
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Initialize DJ Interface
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
initializeDJSystem();
|
|
|
|
setupEventListeners();
|
|
|
|
startAnimations();
|
|
|
|
loadYouTubeAPI();
|
|
|
|
});
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Funktion zum Extrahieren der YouTube Video ID
|
|
|
|
function extractYouTubeVideoId(url) {
|
|
|
|
const patterns = [
|
|
|
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
|
|
|
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
|
|
];
|
2025-08-03 17:09:00 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
for (let pattern of patterns) {
|
|
|
|
const match = url.match(pattern);
|
|
|
|
if (match && match[1]) {
|
|
|
|
return match[1];
|
2025-08-03 17:09:00 +02:00
|
|
|
}
|
2025-08-03 16:51:12 +02:00
|
|
|
}
|
2025-08-03 17:46:16 +02:00
|
|
|
|
|
|
|
return null;
|
2025-08-03 16:51:12 +02:00
|
|
|
}
|
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Prüfe ob es eine YouTube URL ist
|
2025-08-03 17:09:00 +02:00
|
|
|
function isYouTubeUrl(url) {
|
|
|
|
const youtubePatterns = [
|
|
|
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
|
|
|
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
|
|
];
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:09:00 +02:00
|
|
|
return youtubePatterns.some(pattern => pattern.test(url));
|
2025-08-03 16:51:12 +02:00
|
|
|
}
|
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Benachrichtigungen anzeigen
|
|
|
|
function showNotification(message, type = 'info') {
|
|
|
|
console.log(`DJ System [${type.toUpperCase()}]: ${message}`);
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
// Create notification element
|
|
|
|
const notification = document.createElement('div');
|
|
|
|
notification.className = `notification notification-${type}`;
|
|
|
|
notification.textContent = message;
|
|
|
|
notification.style.cssText = `
|
|
|
|
position: fixed;
|
|
|
|
top: 80px;
|
|
|
|
right: 20px;
|
|
|
|
padding: 15px 20px;
|
|
|
|
border-radius: 10px;
|
|
|
|
color: white;
|
|
|
|
font-weight: 600;
|
|
|
|
z-index: 10000;
|
|
|
|
transform: translateX(100%);
|
|
|
|
transition: transform 0.3s ease;
|
|
|
|
background: ${type === 'error' ? '#ff6b6b' : type === 'success' ? '#4ecdc4' : type === 'warning' ? '#feca57' : '#45b7d1'};
|
|
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
|
|
|
`;
|
2025-08-03 16:51:12 +02:00
|
|
|
|
2025-08-03 17:46:16 +02:00
|
|
|
document.body.appendChild(notification);
|
|
|
|
|
|
|
|
// Animate in
|
|
|
|
setTimeout(() => {
|
|
|
|
notification.style.transform = 'translateX(0)';
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
// Remove after 3 seconds
|
|
|
|
setTimeout(() => {
|
|
|
|
notification.style.transform = 'translateX(100%)';
|
|
|
|
setTimeout(() => {
|
|
|
|
document.body.removeChild(notification);
|
|
|
|
}, 300);
|
|
|
|
}, 3000);
|
2025-08-03 16:51:12 +02:00
|
|
|
}
|
|
|
|
|
2025-08-03 17:09:00 +02:00
|
|
|
// FiveM benachrichtigen
|
|
|
|
function notifyFiveM(event, data) {
|
2025-08-03 17:36:16 +02:00
|
|
|
fetch(`https://${GetParentResourceName()}/${event}`, {
|
2025-08-03 16:51:12 +02:00
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
2025-08-03 17:09:00 +02:00
|
|
|
'Content-Type': 'application/json'
|
2025-08-03 16:51:12 +02:00
|
|
|
},
|
2025-08-03 17:09:00 +02:00
|
|
|
body: JSON.stringify(data)
|
2025-08-03 16:51:12 +02:00
|
|
|
}).catch(err => {
|
2025-08-03 17:09:00 +02:00
|
|
|
console.error('DJ System: Failed to notify FiveM:', err);
|
2025-08-03 16:51:12 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function GetParentResourceName() {
|
|
|
|
return window.location.hostname;
|
|
|
|
}
|