1486 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1486 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // 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...');
 | |
|     
 | |
|     // 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 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 = '<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
 | |
|             });
 | |
|             break;
 | |
|             
 | |
|         case YT.PlayerState.PAUSED:
 | |
|             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
 | |
|             });
 | |
|             break;
 | |
|             
 | |
|         case YT.PlayerState.ENDED:
 | |
|             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
 | |
|             });
 | |
|             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 = '<i class="fas fa-play"></i>';
 | |
|         playBtn.classList.remove('playing');
 | |
|         vinyl.classList.remove('spinning');
 | |
|         deckData.isPlaying = false;
 | |
|     } else {
 | |
|         // Play
 | |
|         playTrack(deck);
 | |
|         playBtn.innerHTML = '<i class="fas fa-pause"></i>';
 | |
|         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 = '<i class="fas fa-stop"></i><span>STOP</span>';
 | |
|         startRecordingTimer();
 | |
|         showNotification('Recording started', 'success');
 | |
|     } else {
 | |
|         recordBtn.classList.remove('recording');
 | |
|         recordBtn.innerHTML = '<i class="fas fa-circle"></i><span>REC</span>';
 | |
|         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 = `
 | |
|             <div class="track-item-title">${track.title}</div>
 | |
|             <div class="track-item-artist">${track.artist}</div>
 | |
|         `;
 | |
|         
 | |
|         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');
 | |
| }
 | |
| 
 | |
| // Interface Control Functions
 | |
| function initializeInterfaceSettings() {
 | |
|     // Versuche gespeicherte Einstellungen zu laden
 | |
|     const savedPosition = localStorage.getItem('djInterfacePosition');
 | |
|     const savedSize = localStorage.getItem('djInterfaceSize');
 | |
|     
 | |
|     if (savedPosition) {
 | |
|         try {
 | |
|             interfacePosition = JSON.parse(savedPosition);
 | |
|         } catch (e) {
 | |
|             console.error('DJ System: Error parsing saved position', e);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     if (savedSize) {
 | |
|         try {
 | |
|             interfaceSize = JSON.parse(savedSize);
 | |
|         } catch (e) {
 | |
|             console.error('DJ System: Error parsing saved size', e);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // 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');
 | |
|     
 | |
|     // Erstelle Resize-Handle
 | |
|     resizeHandle.className = 'resize-handle';
 | |
|     resizeHandle.innerHTML = '<i class="fas fa-grip-lines-diagonal"></i>';
 | |
|     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
 | |
|     const closeBtn = document.querySelector('.close-btn');
 | |
|     if (closeBtn) {
 | |
|         closeBtn.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() {
 | |
|     // Stoppe alle laufenden Prozesse
 | |
|     stopAllAudio();
 | |
|     
 | |
|     // Entferne Event-Listener
 | |
|     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');
 | |
|     }
 | |
|     
 | |
|     // Benachrichtige FiveM
 | |
|     notifyFiveM('djInterfaceClosed', {});
 | |
|     
 | |
|     console.log('DJ System: Interface closed');
 | |
| }
 | |
| 
 | |
| 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() {
 | |
|     const djInterfaceElement = document.getElementById('dj-interface');
 | |
|     if (djInterfaceElement) {
 | |
|         djInterfaceElement.classList.remove('hidden');
 | |
|         
 | |
|         // Wende gespeicherte Einstellungen an
 | |
|         applyInterfaceSettings();
 | |
|     }
 | |
| }
 | |
| 
 | |
| // 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;
 | |
| }
 | |
| 
 | |
| // FiveM Integration
 | |
| function notifyFiveM(event, data) {
 | |
|     fetch(`https://${GetParentResourceName()}/${event}`, {
 | |
|         method: 'POST',
 | |
|         headers: {
 | |
|             'Content-Type': 'application/json'
 | |
|         },
 | |
|         body: JSON.stringify(data)
 | |
|     }).catch(err => {
 | |
|         console.error('DJ System: Failed to notify FiveM:', err);
 | |
|     });
 | |
| }
 | |
| 
 | |
| function GetParentResourceName() {
 | |
|     return window.location.hostname;
 | |
| }
 | |
| 
 | |
| // 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');
 | |
| 
 | 
