// 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 }; // Interface Drag & Resize Variablen let isDragging = false; let isResizing = false; let dragStartX = 0; let dragStartY = 0; let resizeStartWidth = 0; let resizeStartHeight = 0; let interfacePosition = { x: 0, y: 0 }; let interfaceSize = { width: 1000, height: 700 }; // Standard-Größe // Initialize DJ Interface document.addEventListener('DOMContentLoaded', function() { initializeDJSystem(); setupEventListeners(); startAnimations(); loadYouTubeAPI(); }); function initializeDJSystem() { console.log('DJ System: Initializing professional interface...'); // Lösche gespeicherte Einstellungen, um die neue Position zu erzwingen localStorage.removeItem('djInterfacePosition'); localStorage.removeItem('djInterfaceSize'); // Initialisiere Interface-Einstellungen initializeInterfaceSettings(); // Initialize audio players djInterface.decks.A.player = document.getElementById('audio-player-a'); djInterface.decks.B.player = document.getElementById('audio-player-b'); // Setup audio event listeners setupAudioEventListeners(); // Initialize waveforms initializeWaveforms(); // Start time display updateTimeDisplay(); setInterval(updateTimeDisplay, 1000); // Start VU meters animation startVUMeters(); // Setup Interface-Kontrollen setupInterfaceControls(); console.log('DJ System: Professional interface ready!'); } function initializeDJSystem() { console.log('DJ System: Initializing professional interface...'); // Initialisiere Interface-Einstellungen initializeInterfaceSettings(); // Initialize audio players djInterface.decks.A.player = document.getElementById('audio-player-a'); djInterface.decks.B.player = document.getElementById('audio-player-b'); // Setup audio event listeners setupAudioEventListeners(); // Initialize waveforms initializeWaveforms(); // Start time display updateTimeDisplay(); setInterval(updateTimeDisplay, 1000); // Start VU meters animation startVUMeters(); // Setup Interface-Kontrollen setupInterfaceControls(); // Lade Playlists loadPlaylists(); // Setup Playlist Event Listeners setupPlaylistEventListeners(); // Event-Listener für "Neue Playlist"-Button const createPlaylistBtn = document.getElementById('create-playlist-btn'); if (createPlaylistBtn) { createPlaylistBtn.addEventListener('click', createNewPlaylist); } console.log('DJ System: Professional interface ready!'); } function setupEventListeners() { // Knob interactions setupKnobControls(); // Keyboard shortcuts document.addEventListener('keydown', handleKeyboardShortcuts); // Window resize window.addEventListener('resize', handleResize); } function setupAudioEventListeners() { // Setup event listeners for audio players if (djInterface.decks.A.player) { djInterface.decks.A.player.addEventListener('ended', function() { notifyFiveM('songEnded', { deck: 'A' }); }); djInterface.decks.A.player.addEventListener('error', function(e) { notifyFiveM('audioError', { deck: 'A', error: 'Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error') }); }); } if (djInterface.decks.B.player) { djInterface.decks.B.player.addEventListener('ended', function() { notifyFiveM('songEnded', { deck: 'B' }); }); djInterface.decks.B.player.addEventListener('error', function(e) { notifyFiveM('audioError', { deck: 'B', error: 'Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error') }); }); } } function setupKnobControls() { const knobs = document.querySelectorAll('.knob'); knobs.forEach(knob => { let isDragging = false; let startY = 0; let startRotation = 0; knob.addEventListener('mousedown', (e) => { isDragging = true; startY = e.clientY; startRotation = getCurrentRotation(knob.querySelector('.knob-indicator')); document.body.style.cursor = 'grabbing'; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaY = startY - e.clientY; const rotation = Math.max(-150, Math.min(150, startRotation + deltaY * 2)); const indicator = knob.querySelector('.knob-indicator'); indicator.style.transform = `translateX(-50%) rotate(${rotation}deg)`; // Update EQ values const channel = knob.dataset.channel; const eqType = knob.dataset.eq; const effect = knob.dataset.effect; if (channel && eqType) { const value = Math.round((rotation / 150) * 100); djInterface.mixer.eq[channel][eqType] = value; const valueSpan = knob.parentElement.querySelector('.eq-value'); if (valueSpan) { valueSpan.textContent = value > 0 ? `+${value}` : value; } applyEQ(channel, eqType, value); } if (effect) { const value = Math.round(((rotation + 150) / 300) * 100); djInterface.effects[effect] = value; applyEffect(effect, value); } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; document.body.style.cursor = 'default'; } }); }); } function getCurrentRotation(element) { if (!element) return 0; const transform = window.getComputedStyle(element).transform; if (transform === 'none') return 0; const matrix = transform.match(/matrix\(([^)]+)\)/); if (!matrix) return 0; const values = matrix[1].split(',').map(parseFloat); const angle = Math.atan2(values[1], values[0]) * (180 / Math.PI); return angle; } // 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'); djInterface.youtubeAPIReady = true; }; // Deck Functions function loadTrackToDeck(deck) { djInterface.currentDeck = deck; document.getElementById('track-loader').classList.remove('hidden'); } function confirmLoadTrack() { const title = document.getElementById('track-title').value; const artist = document.getElementById('track-artist').value; const url = document.getElementById('track-url').value; if (!title || !url) { showNotification('Please fill in title and URL', 'error'); return; } const deck = djInterface.currentDeck; const trackData = { title, artist, url }; loadTrackToPlayer(deck, trackData); closeTrackLoader(); showNotification(`Track loaded to Deck ${deck}: ${title}`, 'success'); } function loadTrackToPlayer(deck, trackData) { const deckData = djInterface.decks[deck]; deckData.track = trackData; // Update UI document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = trackData.title.substring(0, 15); document.getElementById(`title-${deck.toLowerCase()}`).textContent = trackData.title; document.getElementById(`artist-${deck.toLowerCase()}`).textContent = trackData.artist || 'Unknown Artist'; // Load audio if (isYouTubeUrl(trackData.url)) { loadYouTubeTrack(deck, trackData); } else { loadDirectTrack(deck, trackData); } // Generate waveform generateWaveform(deck, trackData.url); } function loadYouTubeTrack(deck, trackData) { const videoId = extractYouTubeVideoId(trackData.url); if (!videoId) { showNotification('Invalid YouTube URL', 'error'); return; } // Create hidden YouTube player for this deck createYouTubePlayerForDeck(deck, videoId); } 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); } } // Neuen Player erstellen djInterface.decks[deck].youtubePlayer = new YT.Player(containerId, { height: '1', width: '1', videoId: videoId, 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: { '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); } } }); } // 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()}`); switch(event.data) { case YT.PlayerState.PLAYING: console.log(`DJ System: YouTube playing on Deck ${deck}`); deckData.isPlaying = true; if (playBtn) { playBtn.innerHTML = ''; playBtn.classList.add('playing'); } if (vinyl) { vinyl.classList.add('spinning'); } // Notify FiveM notifyFiveM('deckStateChanged', { deck: deck, isPlaying: true, track: deckData.track }); break; case YT.PlayerState.PAUSED: console.log(`DJ System: YouTube paused on Deck ${deck}`); deckData.isPlaying = false; if (playBtn) { playBtn.innerHTML = ''; playBtn.classList.remove('playing'); } if (vinyl) { vinyl.classList.remove('spinning'); } // Notify FiveM notifyFiveM('deckStateChanged', { deck: deck, isPlaying: false, track: deckData.track }); break; case YT.PlayerState.ENDED: console.log(`DJ System: YouTube ended on Deck ${deck}`); deckData.isPlaying = false; if (playBtn) { playBtn.innerHTML = ''; playBtn.classList.remove('playing'); } if (vinyl) { vinyl.classList.remove('spinning'); } // Notify FiveM notifyFiveM('songEnded', { deck: deck }); break; } } // YouTube Error Handler function handleYouTubeError(deck, event) { console.error(`DJ System: YouTube error on Deck ${deck}:`, 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; } showNotification(errorMessage, 'error'); // Notify FiveM notifyFiveM('audioError', { deck: deck, error: errorMessage }); } function loadDirectTrack(deck, trackData) { const player = djInterface.decks[deck].player; player.src = trackData.url; player.load(); } function togglePlay(deck) { const deckData = djInterface.decks[deck]; const playBtn = document.getElementById(`play-${deck.toLowerCase()}`); const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`); if (!deckData.track) { showNotification(`No track loaded in Deck ${deck}`, 'warning'); return; } if (deckData.isPlaying) { // Pause pauseTrack(deck); playBtn.innerHTML = ''; playBtn.classList.remove('playing'); vinyl.classList.remove('spinning'); deckData.isPlaying = false; } else { // Play playTrack(deck); playBtn.innerHTML = ''; playBtn.classList.add('playing'); vinyl.classList.add('spinning'); deckData.isPlaying = true; } // Send to FiveM notifyFiveM('deckStateChanged', { deck: deck, isPlaying: deckData.isPlaying, track: deckData.track }); } function playTrack(deck) { const deckData = djInterface.decks[deck]; if (deckData.youtubePlayer) { deckData.youtubePlayer.playVideo(); } else if (deckData.player) { deckData.player.play(); } } function pauseTrack(deck) { const deckData = djInterface.decks[deck]; if (deckData.youtubePlayer) { deckData.youtubePlayer.pauseVideo(); } else if (deckData.player) { deckData.player.pause(); } } function cue(deck) { const deckData = djInterface.decks[deck]; if (!deckData.track) { showNotification(`No track loaded in Deck ${deck}`, 'warning'); return; } // Set cue point to current position or go to cue point if (deckData.isPlaying) { deckData.cuePoint = getCurrentTime(deck); showNotification(`Cue point set in Deck ${deck}`, 'info'); } else { seekTo(deck, deckData.cuePoint); showNotification(`Jumped to cue point in Deck ${deck}`, 'info'); } } function adjustPitch(deck, value) { const deckData = djInterface.decks[deck]; deckData.pitch = parseFloat(value); document.getElementById(`pitch-value-${deck.toLowerCase()}`).textContent = `${value}%`; // Apply pitch change (this would need audio processing) applyPitchChange(deck, value); } function adjustVolume(deck, value) { const deckData = djInterface.decks[deck]; deckData.volume = parseInt(value); if (deckData.youtubePlayer) { deckData.youtubePlayer.setVolume(value); } else if (deckData.player) { deckData.player.volume = value / 100; } updateVUMeter(deck, value); // Notify FiveM about volume change notifyFiveM('volumeChanged', { deck: deck, volume: value }); } function adjustCrossfader(value) { djInterface.mixer.crossfader = parseInt(value); const volumeA = value <= 50 ? 100 : (100 - (value - 50) * 2); const volumeB = value >= 50 ? 100 : (value * 2); // Apply crossfader mixing applyCrossfaderMix(volumeA, volumeB); // Visual feedback const crossfader = document.getElementById('crossfader'); crossfader.style.background = `linear-gradient(90deg, rgba(78, 205, 196, ${volumeA/100}) 0%, rgba(255, 107, 107, 0.5) 50%, rgba(78, 205, 196, ${volumeB/100}) 100%)`; } function applyCrossfaderMix(volumeA, volumeB) { const deckA = djInterface.decks.A; const deckB = djInterface.decks.B; const finalVolumeA = (deckA.volume * volumeA / 100) / 100; const finalVolumeB = (deckB.volume * volumeB / 100) / 100; if (deckA.youtubePlayer) { deckA.youtubePlayer.setVolume(finalVolumeA * 100); } else if (deckA.player) { deckA.player.volume = finalVolumeA; } if (deckB.youtubePlayer) { deckB.youtubePlayer.setVolume(finalVolumeB * 100); } else if (deckB.player) { deckB.player.volume = finalVolumeB; } } function adjustMasterVolume(value) { djInterface.mixer.masterVolume = parseInt(value); // Apply master volume to both decks const masterMultiplier = value / 100; Object.keys(djInterface.decks).forEach(deck => { const deckData = djInterface.decks[deck]; const finalVolume = (deckData.volume * masterMultiplier) / 100; if (deckData.youtubePlayer) { deckData.youtubePlayer.setVolume(finalVolume * 100); } else if (deckData.player) { deckData.player.volume = finalVolume; } }); updateMasterVU(value); } function setCrossfaderCurve(type) { document.querySelectorAll('.curve-btn').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); djInterface.mixer.crossfaderCurve = type; showNotification(`Crossfader curve set to ${type}`, 'info'); } // EQ Functions function applyEQ(channel, eqType, value) { const deckData = djInterface.decks[channel]; if (!deckData.audioContext) { setupAudioContext(channel); } // Apply EQ filter (simplified) console.log(`Applying ${eqType} EQ: ${value}% to Deck ${channel}`); // Visual feedback const knob = document.querySelector(`[data-channel="${channel}"][data-eq="${eqType}"] .knob-indicator`); if (knob) { const hue = value > 0 ? 120 : value < 0 ? 0 : 200; knob.style.boxShadow = `0 0 8px hsl(${hue}, 70%, 50%)`; } } // Effects Functions function toggleEffect(effectType) { const isActive = djInterface.effects[effectType]; djInterface.effects[effectType] = !isActive; const btn = event.target; btn.classList.toggle('active'); if (djInterface.effects[effectType]) { applyEffect(effectType, 50); showNotification(`${effectType.toUpperCase()} ON`, 'success'); } else { removeEffect(effectType); showNotification(`${effectType.toUpperCase()} OFF`, 'info'); } } function applyEffect(effectType, value) { console.log(`Applying ${effectType} effect: ${value}%`); // Effect processing would go here switch(effectType) { case 'reverb': applyReverb(value); break; case 'delay': applyDelay(value); break; case 'filter': applyFilter(value); break; case 'flanger': applyFlanger(value); break; } } function removeEffect(effectType) { console.log(`Removing ${effectType} effect`); // Remove effect processing } // Waveform Functions function initializeWaveforms() { const canvasA = document.getElementById('waveform-a'); const canvasB = document.getElementById('waveform-b'); if (canvasA && canvasB) { drawEmptyWaveform(canvasA); drawEmptyWaveform(canvasB); } } function drawEmptyWaveform(canvas) { if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; ctx.clearRect(0, 0, width, height); // Draw center line ctx.strokeStyle = 'rgba(78, 205, 196, 0.3)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, height / 2); ctx.lineTo(width, height / 2); ctx.stroke(); // Draw grid ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; for (let i = 0; i < width; i += 20) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, height); ctx.stroke(); } } function generateWaveform(deck, url) { const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`); if (!canvas) return; const ctx = canvas.getContext('2d'); // Simulate waveform generation drawSimulatedWaveform(ctx, canvas.width, canvas.height); } function drawSimulatedWaveform(ctx, width, height) { ctx.clearRect(0, 0, width, height); const centerY = height / 2; const gradient = ctx.createLinearGradient(0, 0, 0, height); gradient.addColorStop(0, 'rgba(255, 107, 107, 0.8)'); gradient.addColorStop(0.5, 'rgba(78, 205, 196, 0.8)'); gradient.addColorStop(1, 'rgba(255, 107, 107, 0.8)'); ctx.fillStyle = gradient; for (let x = 0; x < width; x += 2) { const amplitude = Math.random() * (height / 2) * (0.3 + Math.sin(x * 0.01) * 0.7); ctx.fillRect(x, centerY - amplitude, 2, amplitude * 2); } } // VU Meters function startVUMeters() { setInterval(() => { updateVUMeter('A', djInterface.decks.A.isPlaying ? Math.random() * 100 : 0); updateVUMeter('B', djInterface.decks.B.isPlaying ? Math.random() * 100 : 0); updateMasterVU(djInterface.mixer.masterVolume); }, 100); } function updateVUMeter(deck, level) { const vuBar = document.getElementById(`vu-${deck.toLowerCase()}`); if (vuBar) { vuBar.style.height = `${level}%`; // Color based on level if (level > 80) { vuBar.style.background = 'linear-gradient(180deg, #ff0000, #ff6b6b)'; } else if (level > 60) { vuBar.style.background = 'linear-gradient(180deg, #feca57, #ff6b6b)'; } else { vuBar.style.background = 'linear-gradient(180deg, #4ecdc4, #feca57)'; } } } function updateMasterVU(level) { const masterVU = document.getElementById('master-vu'); if (masterVU) { masterVU.style.height = `${level}%`; } } // Time and BPM function updateTimeDisplay() { const now = new Date(); const timeString = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); const timeDisplay = document.getElementById('current-time'); if (timeDisplay) { timeDisplay.textContent = timeString; } // Update track times updateTrackTimes(); // Update BPM (simulated) updateBPMDisplay(); } function updateTrackTimes() { ['A', 'B'].forEach(deck => { const deckData = djInterface.decks[deck]; if (deckData.track && deckData.isPlaying) { const currentTime = getCurrentTime(deck); const duration = getDuration(deck); const elapsedElement = document.getElementById(`time-elapsed-${deck.toLowerCase()}`); const totalElement = document.getElementById(`time-total-${deck.toLowerCase()}`); if (elapsedElement) { elapsedElement.textContent = formatTime(currentTime); } if (totalElement) { totalElement.textContent = formatTime(duration); } } }); } function getCurrentTime(deck) { const deckData = djInterface.decks[deck]; if (deckData.youtubePlayer) { return deckData.youtubePlayer.getCurrentTime() || 0; } else if (deckData.player) { return deckData.player.currentTime || 0; } return 0; } function getDuration(deck) { const deckData = djInterface.decks[deck]; if (deckData.youtubePlayer) { return deckData.youtubePlayer.getDuration() || 0; } else if (deckData.player) { return deckData.player.duration || 0; } return 0; } function formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } function updateBPMDisplay() { // Simulate BPM detection const bpm = 120 + Math.floor(Math.random() * 40); const bpmDisplay = document.getElementById('bpm-display'); if (bpmDisplay) { bpmDisplay.textContent = bpm; } } // Recording Functions function toggleRecording() { djInterface.isRecording = !djInterface.isRecording; const recordBtn = document.querySelector('.record-btn'); const recordTime = document.querySelector('.recording-time'); if (djInterface.isRecording) { recordBtn.classList.add('recording'); recordBtn.innerHTML = 'STOP'; startRecordingTimer(); showNotification('Recording started', 'success'); } else { recordBtn.classList.remove('recording'); recordBtn.innerHTML = 'REC'; stopRecordingTimer(); showNotification('Recording stopped', 'info'); } } let recordingStartTime = 0; let recordingTimer = null; function startRecordingTimer() { recordingStartTime = Date.now(); recordingTimer = setInterval(() => { const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000); const recordTime = document.querySelector('.recording-time'); if (recordTime) { recordTime.textContent = formatTime(elapsed); } }, 1000); } function stopRecordingTimer() { if (recordingTimer) { clearInterval(recordingTimer); recordingTimer = null; } const recordTime = document.querySelector('.recording-time'); if (recordTime) { recordTime.textContent = '00:00'; } } // Search and Library Functions function searchTracks(query) { console.log('Searching tracks:', query); // Simulate track search const mockTracks = [ { title: 'Summer Vibes', artist: 'DJ Cool', url: 'https://example.com/track1' }, { title: 'Night Drive', artist: 'Electronic Dreams', url: 'https://example.com/track2' }, { title: 'Bass Drop', artist: 'Heavy Beats', url: 'https://example.com/track3' }, { title: 'Chill Out', artist: 'Ambient Sounds', url: 'https://example.com/track4' } ]; const filteredTracks = mockTracks.filter(track => track.title.toLowerCase().includes(query.toLowerCase()) || track.artist.toLowerCase().includes(query.toLowerCase()) ); displayTrackList(filteredTracks); } function displayTrackList(tracks) { const trackList = document.getElementById('track-list'); if (!trackList) return; trackList.innerHTML = ''; tracks.forEach(track => { const trackItem = document.createElement('div'); trackItem.className = 'track-item'; trackItem.innerHTML = `
${track.title}
${track.artist}
`; trackItem.addEventListener('dblclick', () => { if (djInterface.currentDeck) { loadTrackToPlayer(djInterface.currentDeck, track); showNotification(`${track.title} loaded to Deck ${djInterface.currentDeck}`, 'success'); } }); trackList.appendChild(trackItem); }); } // Keyboard Shortcuts function handleKeyboardShortcuts(event) { if (event.target.tagName === 'INPUT') return; switch(event.code) { case 'Space': event.preventDefault(); if (djInterface.currentDeck) { togglePlay(djInterface.currentDeck); } break; case 'KeyQ': togglePlay('A'); break; case 'KeyW': togglePlay('B'); break; case 'KeyA': cue('A'); break; case 'KeyS': cue('B'); break; case 'KeyZ': adjustCrossfader(0); // Full A break; case 'KeyX': adjustCrossfader(50); // Center break; case 'KeyC': adjustCrossfader(100); // Full B break; case 'KeyR': toggleRecording(); break; } } // Animations function startAnimations() { // Vinyl spinning animation is handled by CSS // Playhead animation setInterval(() => { ['A', 'B'].forEach(deck => { const deckData = djInterface.decks[deck]; if (deckData.isPlaying && deckData.track) { updatePlayhead(deck); } }); }, 100); } function updatePlayhead(deck) { const playhead = document.getElementById(`playhead-${deck.toLowerCase()}`); if (!playhead) return; const currentTime = getCurrentTime(deck); const duration = getDuration(deck); if (duration > 0) { const progress = (currentTime / duration) * 100; playhead.style.left = `${progress}%`; } } // Modal Functions function closeTrackLoader() { document.getElementById('track-loader').classList.add('hidden'); // Clear form document.getElementById('track-title').value = ''; document.getElementById('track-artist').value = ''; document.getElementById('track-url').value = ''; } function ejectDeck(deck) { const deckData = djInterface.decks[deck]; if (deckData.isPlaying) { togglePlay(deck); } // Clear deck deckData.track = null; deckData.cuePoint = 0; // Clear UI document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = 'NO TRACK'; document.getElementById(`title-${deck.toLowerCase()}`).textContent = 'No Track Loaded'; document.getElementById(`artist-${deck.toLowerCase()}`).textContent = '-'; document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent = '00:00'; document.getElementById(`time-total-${deck.toLowerCase()}`).textContent = '00:00'; // Clear waveform const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`); drawEmptyWaveform(canvas); showNotification(`Deck ${deck} ejected`, 'info'); } // Initialisiere Interface-Einstellungen function initializeInterfaceSettings() { console.log('Initializing interface settings'); // Setze Standardwerte interfacePosition = { x: (window.innerWidth - 1000) / 2, y: (window.innerHeight - 700) / 2 }; interfaceSize = { width: 1000, height: 700 }; // Versuche gespeicherte Einstellungen zu laden try { const savedPosition = localStorage.getItem('djInterfacePosition'); const savedSize = localStorage.getItem('djInterfaceSize'); if (savedPosition) { const parsedPosition = JSON.parse(savedPosition); // Prüfe ob die Position gültig ist if (parsedPosition && typeof parsedPosition.x === 'number' && typeof parsedPosition.y === 'number') { interfacePosition = parsedPosition; } } if (savedSize) { const parsedSize = JSON.parse(savedSize); // Prüfe ob die Größe gültig ist if (parsedSize && typeof parsedSize.width === 'number' && typeof parsedSize.height === 'number') { interfaceSize = parsedSize; } } } catch (e) { console.error('DJ System: Error loading saved settings', e); // Lösche ungültige Einstellungen localStorage.removeItem('djInterfacePosition'); localStorage.removeItem('djInterfaceSize'); } // Wende Einstellungen an applyInterfaceSettings(); } function applyInterfaceSettings() { const djInterface = document.getElementById('dj-interface'); if (!djInterface) return; // Setze Position djInterface.style.position = 'absolute'; djInterface.style.left = interfacePosition.x + 'px'; djInterface.style.top = interfacePosition.y + 'px'; // Setze Größe djInterface.style.width = interfaceSize.width + 'px'; djInterface.style.height = interfaceSize.height + 'px'; // Speichere Einstellungen localStorage.setItem('djInterfacePosition', JSON.stringify(interfacePosition)); localStorage.setItem('djInterfaceSize', JSON.stringify(interfaceSize)); } function setupInterfaceControls() { const djInterfaceElement = document.getElementById('dj-interface'); const header = document.querySelector('.dj-header'); const resizeHandle = document.createElement('div'); // Verbesserte Schließen-Funktion const closeBtn = document.querySelector('.close-btn'); if (closeBtn) { closeBtn.addEventListener('click', function(e) { // Nur Interface schließen, Musik weiterlaufen lassen closeDJInterface(); e.preventDefault(); e.stopPropagation(); }); } // Erstelle Resize-Handle resizeHandle.className = 'resize-handle'; resizeHandle.innerHTML = ''; resizeHandle.style.cssText = ` position: absolute; bottom: 0; right: 0; width: 20px; height: 20px; cursor: nwse-resize; color: rgba(255, 255, 255, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; `; djInterfaceElement.appendChild(resizeHandle); // Header Drag-Funktionalität if (header) { header.style.cursor = 'move'; header.addEventListener('mousedown', function(e) { // Ignoriere Klicks auf den Schließen-Button if (e.target.closest('.close-btn')) return; isDragging = true; dragStartX = e.clientX - interfacePosition.x; dragStartY = e.clientY - interfacePosition.y; document.addEventListener('mousemove', handleDrag); document.addEventListener('mouseup', stopDrag); }); } // Resize-Funktionalität resizeHandle.addEventListener('mousedown', function(e) { isResizing = true; resizeStartWidth = interfaceSize.width; resizeStartHeight = interfaceSize.height; dragStartX = e.clientX; dragStartY = e.clientY; document.addEventListener('mousemove', handleResize); document.addEventListener('mouseup', stopResize); e.preventDefault(); e.stopPropagation(); }); // Verbesserte Schließen-Funktion // Korrigierte Schreibweise: close-btn ist die CSS-Klasse const closeButton = document.querySelector('.close-btn'); if (closeButton) { closeButton.addEventListener('click', function(e) { closeDJInterface(); e.preventDefault(); e.stopPropagation(); }); } } function handleDrag(e) { if (!isDragging) return; interfacePosition.x = e.clientX - dragStartX; interfacePosition.y = e.clientY - dragStartY; // Begrenze Position auf Bildschirm interfacePosition.x = Math.max(0, Math.min(window.innerWidth - interfaceSize.width, interfacePosition.x)); interfacePosition.y = Math.max(0, Math.min(window.innerHeight - interfaceSize.height, interfacePosition.y)); const djInterfaceElement = document.getElementById('dj-interface'); if (djInterfaceElement) { djInterfaceElement.style.left = interfacePosition.x + 'px'; djInterfaceElement.style.top = interfacePosition.y + 'px'; } } function stopDrag() { if (isDragging) { isDragging = false; document.removeEventListener('mousemove', handleDrag); document.removeEventListener('mouseup', stopDrag); // Speichere Position localStorage.setItem('djInterfacePosition', JSON.stringify(interfacePosition)); } } function handleResize(e) { if (!isResizing) return; const deltaX = e.clientX - dragStartX; const deltaY = e.clientY - dragStartY; interfaceSize.width = Math.max(800, resizeStartWidth + deltaX); interfaceSize.height = Math.max(600, resizeStartHeight + deltaY); const djInterfaceElement = document.getElementById('dj-interface'); if (djInterfaceElement) { djInterfaceElement.style.width = interfaceSize.width + 'px'; djInterfaceElement.style.height = interfaceSize.height + 'px'; } } function stopResize() { if (isResizing) { isResizing = false; document.removeEventListener('mousemove', handleResize); document.removeEventListener('mouseup', stopResize); // Speichere Größe localStorage.setItem('djInterfaceSize', JSON.stringify(interfaceSize)); } } function closeDJInterface() { console.log('DJ System: Closing interface...'); // Entferne Event-Listener für Drag und Resize document.removeEventListener('mousemove', handleDrag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('mousemove', handleResize); document.removeEventListener('mouseup', stopResize); // Verstecke Interface const djInterfaceElement = document.getElementById('dj-interface'); if (djInterfaceElement) { djInterfaceElement.classList.add('hidden'); } // Wichtig: Benachrichtige FiveM BEVOR wir irgendwelche lokalen Variablen zurücksetzen notifyFiveM('djInterfaceClosed', { stopMusic: false // Musik weiterlaufen lassen }); // Setze Drag & Resize Status zurück isDragging = false; isResizing = false; console.log('DJ System: Interface closed, music continues playing'); } function stopAllAudio() { // Stoppe YouTube-Player for (const deck of ['A', 'B']) { if (djInterface.decks[deck].youtubePlayer) { try { djInterface.decks[deck].youtubePlayer.stopVideo(); } catch (e) { console.error(`DJ System: Error stopping YouTube player for Deck ${deck}`, e); } } // Stoppe Audio-Player if (djInterface.decks[deck].player) { try { djInterface.decks[deck].player.pause(); djInterface.decks[deck].player.currentTime = 0; } catch (e) { console.error(`DJ System: Error stopping audio player for Deck ${deck}`, e); } } // Reset Deck-Status djInterface.decks[deck].isPlaying = false; } } function showDJInterface() { console.log('DJ System: Showing interface'); const djInterfaceElement = document.getElementById('dj-interface'); if (!djInterfaceElement) { console.error('DJ System: Interface element not found!'); notifyFiveM('error', {error: 'Interface element not found'}); return; } // Entferne hidden-Klasse djInterfaceElement.classList.remove('hidden'); // Position weiter rechts und unten, falls keine gespeicherte Position if (!localStorage.getItem('djInterfacePosition')) { interfacePosition = { x: (window.innerWidth - interfaceSize.width) / 2, y: (window.innerHeight - interfaceSize.height) / 2 }; } // Wende gespeicherte Einstellungen an applyInterfaceSettings(); console.log('DJ System: Interface shown'); } // Aktualisiere den Message Handler window.addEventListener('message', function(event) { const data = event.data; switch(data.type) { case 'showDJInterface': if (data.center) { // Zentriere das Interface, aber weiter rechts und unten interfacePosition = { x: (window.innerWidth - interfaceSize.width) / 2 + 200, // 200px weiter rechts y: (window.innerHeight - interfaceSize.height) / 2 + 150 // 150px weiter unten }; } if (data.reset) { // Setze Größe zurück interfaceSize = { width: 1000, height: 700 }; } showDJInterface(); break; // ... andere cases ... } }); // Utility Functions function showNotification(message, type = 'info') { console.log(`DJ System [${type.toUpperCase()}]: ${message}`); // 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); `; 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); } function handleResize() { // Handle responsive design const djInterfaceElement = document.getElementById('dj-interface'); if (window.innerWidth < 1200) { djInterfaceElement.classList.add('mobile-layout'); } else { djInterfaceElement.classList.remove('mobile-layout'); } } // YouTube Integration 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)); } 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; } // Verbesserte notifyFiveM-Funktion mit Fehlerbehandlung function notifyFiveM(event, data, callback) { try { const callbackId = callback ? Date.now().toString() + Math.random().toString(36).substr(2, 5) : null; if (callback) { window.callbacks = window.callbacks || {}; window.callbacks[callbackId] = callback; } const payload = { ...data, callbackId: callbackId }; console.log(`DJ System: Sending ${event} to FiveM`, payload); fetch(`https://${GetParentResourceName()}/${event}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(err => { console.error('DJ System: Failed to notify FiveM:', err); if (callback) { delete window.callbacks[callbackId]; callback({success: false, error: 'Failed to communicate with FiveM'}); } }); } catch (error) { console.error('DJ System: Error in notifyFiveM:', error); if (callback) { callback({success: false, error: 'Internal error'}); } } } // Callback-Handler window.handleCallback = function(callbackId, data) { if (window.callbacks && window.callbacks[callbackId]) { window.callbacks[callbackId](data); delete window.callbacks[callbackId]; } }; // Message Handler for FiveM window.addEventListener('message', function(event) { const data = event.data; switch(data.type) { case 'showDJInterface': showDJInterface(); break; case 'hideDJInterface': closeDJInterface(); break; case 'loadTrack': if (data.deck && data.track) { loadTrackToPlayer(data.deck, data.track); } break; case 'setVolume': if (data.deck && data.volume !== undefined) { adjustVolume(data.deck, data.volume); } break; case 'playMusic': playMusic(data.url, data.volume, data.title); break; case 'stopMusic': stopMusic(); break; } }); // Direct music playback for FiveM integration function playMusic(url, volume, title) { // This function is used when FiveM sends a direct playMusic command // It will play through the main audio system, not through the decks console.log('DJ System: Playing direct music:', title, url); // If YouTube URL, load into deck A if (isYouTubeUrl(url)) { const videoId = extractYouTubeVideoId(url); if (videoId) { // Create track data const trackData = { title: title || 'Unknown Track', artist: '', url: url }; // Load into deck A and play loadTrackToPlayer('A', trackData); // Set volume adjustVolume('A', volume || 50); // Play if (!djInterface.decks.A.isPlaying) { togglePlay('A'); } return; } } // For direct audio URLs, use a separate audio element const audioElement = document.createElement('audio'); audioElement.src = url; audioElement.volume = (volume || 50) / 100; audioElement.addEventListener('canplay', () => { audioElement.play().catch(e => { console.error('DJ System: Error playing direct audio:', e); showNotification('Error playing audio', 'error'); }); }); audioElement.addEventListener('error', (e) => { console.error('DJ System: Direct audio error:', e); showNotification('Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error'), 'error'); }); // Store for later reference window.currentDirectAudio = audioElement; } function stopMusic() { // Stop any direct audio if (window.currentDirectAudio) { window.currentDirectAudio.pause(); window.currentDirectAudio.src = ''; window.currentDirectAudio = null; } // Also stop deck A if it's playing if (djInterface.decks.A.isPlaying) { togglePlay('A'); } // And deck B if (djInterface.decks.B.isPlaying) { togglePlay('B'); } } function setVolume(volume) { // Set volume for direct audio if (window.currentDirectAudio) { window.currentDirectAudio.volume = volume / 100; } // Also set master volume adjustMasterVolume(volume); } // Initialize search with default tracks document.addEventListener('DOMContentLoaded', function() { setTimeout(() => { searchTracks(''); // Load default tracks }, 1000); }); console.log('DJ System: Professional interface loaded! 🎛️'); console.log('Keyboard shortcuts:'); console.log('Q/W - Play/Pause Deck A/B'); console.log('A/S - Cue Deck A/B'); console.log('Z/X/C - Crossfader positions'); console.log('R - Toggle recording'); console.log('Space - Play/Pause current deck'); // In script.js - Separate Funktion zum Stoppen der Musik function stopAllAudioAndCloseInterface() { // Stoppe YouTube-Player for (const deck of ['A', 'B']) { if (djInterface.decks[deck].youtubePlayer) { try { djInterface.decks[deck].youtubePlayer.stopVideo(); } catch (e) { console.error(`DJ System: Error stopping YouTube player for Deck ${deck}`, e); } } // Stoppe Audio-Player if (djInterface.decks[deck].player) { try { djInterface.decks[deck].player.pause(); djInterface.decks[deck].player.currentTime = 0; } catch (e) { console.error(`DJ System: Error stopping audio player for Deck ${deck}`, e); } } // Reset Deck-Status djInterface.decks[deck].isPlaying = false; } // Schließe Interface closeDJInterface(); // Benachrichtige FiveM, dass die Musik gestoppt werden soll notifyFiveM('djInterfaceClosed', { stopMusic: true }); } function showDJInterface() { console.log('DJ System: Showing interface'); const djInterfaceElement = document.getElementById('dj-interface'); if (!djInterfaceElement) { console.error('DJ System: Interface element not found!'); notifyFiveM('error', {error: 'Interface element not found'}); return; } // Entferne hidden-Klasse djInterfaceElement.classList.remove('hidden'); // Position weiter rechts und unten, falls keine gespeicherte Position if (!localStorage.getItem('djInterfacePosition')) { interfacePosition = { x: (window.innerWidth - interfaceSize.width) / 2, y: (window.innerHeight - interfaceSize.height) / 2 }; } // Wende gespeicherte Einstellungen an applyInterfaceSettings(); console.log('DJ System: Interface shown'); } // Vereinfachte Playlist-Verwaltung let playlists = []; let currentPlaylist = null; let currentPlaylistIndex = 0; // Playlist laden - vereinfacht function loadPlaylists() { console.log('DJ System: Loading playlists'); // Simuliere Playlists für den Anfang playlists = [ { id: 1, name: "Party Hits", isOwner: true, songs: [ { id: 1, title: "Song 1", artist: "Artist 1", url: "https://example.com/song1.mp3" }, { id: 2, title: "Song 2", artist: "Artist 2", url: "https://example.com/song2.mp3" } ] }, { id: 2, name: "Chill Vibes", isOwner: true, songs: [ { id: 3, title: "Chill Song 1", artist: "Chill Artist 1", url: "https://example.com/chill1.mp3" }, { id: 4, title: "Chill Song 2", artist: "Chill Artist 2", url: "https://example.com/chill2.mp3" } ] } ]; // Aktualisiere Anzeige updatePlaylistDisplay(); } // Playlist-Anzeige aktualisieren - vereinfacht function updatePlaylistDisplay() { console.log('DJ System: Updating playlist display'); const playlistContainer = document.getElementById('playlist-container'); if (!playlistContainer) { console.error('DJ System: Playlist container not found'); return; } playlistContainer.innerHTML = ''; if (playlists.length === 0) { playlistContainer.innerHTML = '
Keine Playlists gefunden
'; return; } // Erstelle Playlist-Elemente playlists.forEach((playlist) => { const playlistElement = document.createElement('div'); playlistElement.className = 'playlist-item'; if (currentPlaylist && currentPlaylist.id === playlist.id) { playlistElement.classList.add('active'); } playlistElement.innerHTML = `
${playlist.name} ${playlist.songs.length} Songs
`; // Event-Listener für Play-Button const playBtn = playlistElement.querySelector('.playlist-play-btn'); if (playBtn) { playBtn.onclick = function(e) { e.preventDefault(); e.stopPropagation(); playPlaylist(playlist); }; } playlistContainer.appendChild(playlistElement); }); } // Playlist abspielen - vereinfacht function playPlaylist(playlist) { console.log('DJ System: Playing playlist', playlist.name); if (!playlist || !playlist.songs || playlist.songs.length === 0) { showNotification('Diese Playlist enthält keine Songs', 'warning'); return; } currentPlaylist = playlist; currentPlaylistIndex = 0; // Ersten Song abspielen playPlaylistSong(currentPlaylistIndex); } // Playlist-Song abspielen - vereinfacht function playPlaylistSong(index) { if (!currentPlaylist || !currentPlaylist.songs || index >= currentPlaylist.songs.length) { showNotification('Playlist beendet', 'info'); currentPlaylist = null; return; } const song = currentPlaylist.songs[index]; console.log('DJ System: Playing playlist song', song.title); // Spiele den Song direkt ab PlayMusicAsDJ(song.title, song.url, currentVolume || 50); showNotification(`Playlist: ${currentPlaylist.name} - Song ${index + 1}/${currentPlaylist.songs.length}`, 'info'); } // Nächster Song in Playlist function playNextSong() { if (!currentPlaylist) return; currentPlaylistIndex++; if (currentPlaylistIndex < currentPlaylist.songs.length) { playPlaylistSong(currentPlaylistIndex); } else { showNotification('Playlist beendet', 'info'); currentPlaylist = null; } } // Funktion zum Abspielen von Musik als DJ (vereinfacht) function PlayMusicAsDJ(title, url, volume) { console.log('DJ System: Playing music as DJ', title, url); // Sende an FiveM notifyFiveM('playTrack', { title: title, url: url, volume: volume || 50 }); }