ed
This commit is contained in:
		
							parent
							
								
									cac2b97954
								
							
						
					
					
						commit
						a3ab70ec94
					
				
					 4 changed files with 2773 additions and 109 deletions
				
			
		| 
						 | 
				
			
			@ -75,70 +75,56 @@ function GetNearbyDJBooth()
 | 
			
		|||
    return nil
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function OpenDJMenu()
 | 
			
		||||
    TriggerServerEvent('dj:server:getPlaylists')
 | 
			
		||||
    
 | 
			
		||||
    local options = {
 | 
			
		||||
        {
 | 
			
		||||
            title = 'YouTube Song abspielen',
 | 
			
		||||
            description = 'Spiele einen Song von YouTube ab',
 | 
			
		||||
            icon = 'fab fa-youtube',
 | 
			
		||||
            onSelect = function()
 | 
			
		||||
                OpenYouTubeMenu()
 | 
			
		||||
            end
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title = 'Direkte URL abspielen',
 | 
			
		||||
            description = 'Spiele einen Song von einer direkten URL ab',
 | 
			
		||||
            icon = 'play',
 | 
			
		||||
            onSelect = function()
 | 
			
		||||
                OpenDirectUrlMenu()
 | 
			
		||||
            end
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title = 'Musik stoppen',
 | 
			
		||||
            description = 'Stoppe die aktuelle Musik',
 | 
			
		||||
            icon = 'stop',
 | 
			
		||||
            onSelect = function()
 | 
			
		||||
                StopMusic()
 | 
			
		||||
            end
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title = 'Lautstärke ändern',
 | 
			
		||||
            description = 'Aktuelle Lautstärke: ' .. currentVolume .. '%',
 | 
			
		||||
            icon = 'volume-up',
 | 
			
		||||
            onSelect = function()
 | 
			
		||||
                OpenVolumeMenu()
 | 
			
		||||
            end
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title = 'Playlists verwalten',
 | 
			
		||||
            description = 'Erstelle und verwalte Playlists',
 | 
			
		||||
            icon = 'list',
 | 
			
		||||
            onSelect = function()
 | 
			
		||||
                OpenPlaylistMenu()
 | 
			
		||||
            end
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if isPlaying and currentSong then
 | 
			
		||||
        table.insert(options, 2, {
 | 
			
		||||
            title = 'Aktueller Song',
 | 
			
		||||
            description = currentSong.title,
 | 
			
		||||
            icon = 'music',
 | 
			
		||||
            disabled = true
 | 
			
		||||
-- Aktualisierte Client-Funktionen für das neue UI
 | 
			
		||||
function OpenDJInterface()
 | 
			
		||||
    if not isDJBooth then
 | 
			
		||||
        lib.notify({
 | 
			
		||||
            title = 'DJ System',
 | 
			
		||||
            description = 'Du musst an einem DJ Pult stehen',
 | 
			
		||||
            type = 'error'
 | 
			
		||||
        })
 | 
			
		||||
        return
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    lib.registerContext({
 | 
			
		||||
        id = 'dj_main_menu',
 | 
			
		||||
        title = 'DJ System - ' .. currentDJBooth.name,
 | 
			
		||||
        options = options
 | 
			
		||||
    SetNuiFocus(true, true)
 | 
			
		||||
    SendNUIMessage({
 | 
			
		||||
        type = 'showDJInterface'
 | 
			
		||||
    })
 | 
			
		||||
    
 | 
			
		||||
    lib.showContext('dj_main_menu')
 | 
			
		||||
    isUIOpen = true
 | 
			
		||||
    
 | 
			
		||||
    -- Disable controls while UI is open
 | 
			
		||||
    CreateThread(function()
 | 
			
		||||
        while isUIOpen do
 | 
			
		||||
            DisableAllControlActions(0)
 | 
			
		||||
            EnableControlAction(0, 1, true) -- Mouse look
 | 
			
		||||
            EnableControlAction(0, 2, true) -- Mouse look
 | 
			
		||||
            Wait(0)
 | 
			
		||||
        end
 | 
			
		||||
    end)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- NUI Callbacks für das neue Interface
 | 
			
		||||
RegisterNUICallback('djInterfaceClosed', function(data, cb)
 | 
			
		||||
    SetNuiFocus(false, false)
 | 
			
		||||
    isUIOpen = false
 | 
			
		||||
    cb('ok')
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
RegisterNUICallback('deckStateChanged', function(data, cb)
 | 
			
		||||
    print(string.format('[DJ System] Deck %s %s: %s', 
 | 
			
		||||
        data.deck, 
 | 
			
		||||
        data.isPlaying and 'playing' or 'stopped',
 | 
			
		||||
        data.track and data.track.title or 'No track'
 | 
			
		||||
    ))
 | 
			
		||||
    
 | 
			
		||||
    -- Hier könntest du zusätzliche Logik hinzufügen
 | 
			
		||||
    -- z.B. Synchronisation mit anderen Spielern
 | 
			
		||||
    
 | 
			
		||||
    cb('ok')
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function OpenYouTubeMenu()
 | 
			
		||||
    local input = lib.inputDialog('YouTube Song abspielen', {
 | 
			
		||||
        {type = 'input', label = 'Song Titel', placeholder = 'z.B. Daft Punk - One More Time'},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,23 +3,386 @@
 | 
			
		|||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>DJ System - YouTube Streaming</title>
 | 
			
		||||
    <title>DJ System - Professional Interface</title>
 | 
			
		||||
    <link rel="stylesheet" href="style.css">
 | 
			
		||||
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 | 
			
		||||
    <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;600;700&display=swap" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="music-player">
 | 
			
		||||
        <!-- Normaler Audio Player für direkte URLs -->
 | 
			
		||||
        <audio 
 | 
			
		||||
            id="audio-player" 
 | 
			
		||||
            preload="auto"
 | 
			
		||||
            crossorigin="anonymous"
 | 
			
		||||
            style="display: none;">
 | 
			
		||||
        </audio>
 | 
			
		||||
        
 | 
			
		||||
        <!-- YouTube Player wird dynamisch erstellt -->
 | 
			
		||||
        <!-- Container wird von JavaScript erstellt -->
 | 
			
		||||
    <!-- DJ Interface Container -->
 | 
			
		||||
    <div id="dj-interface" class="hidden">
 | 
			
		||||
        <!-- Header -->
 | 
			
		||||
        <div class="dj-header">
 | 
			
		||||
            <div class="logo">
 | 
			
		||||
                <i class="fas fa-music"></i>
 | 
			
		||||
                <span>DJ SYSTEM</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="status-bar">
 | 
			
		||||
                <div class="status-item">
 | 
			
		||||
                    <i class="fas fa-signal"></i>
 | 
			
		||||
                    <span id="connection-status">CONNECTED</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="status-item">
 | 
			
		||||
                    <i class="fas fa-clock"></i>
 | 
			
		||||
                    <span id="current-time">00:00</span>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="close-btn" onclick="closeDJInterface()">
 | 
			
		||||
                <i class="fas fa-times"></i>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Main DJ Console -->
 | 
			
		||||
        <div class="dj-console">
 | 
			
		||||
            <!-- Left Deck -->
 | 
			
		||||
            <div class="deck deck-left">
 | 
			
		||||
                <div class="deck-header">
 | 
			
		||||
                    <h3>DECK A</h3>
 | 
			
		||||
                    <div class="deck-controls">
 | 
			
		||||
                        <button class="btn-deck" onclick="loadTrackToDeck('A')">
 | 
			
		||||
                            <i class="fas fa-folder-open"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button class="btn-deck" onclick="ejectDeck('A')">
 | 
			
		||||
                            <i class="fas fa-eject"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Vinyl Plattenspieler -->
 | 
			
		||||
                <div class="turntable">
 | 
			
		||||
                    <div class="vinyl-container">
 | 
			
		||||
                        <div class="vinyl" id="vinyl-a">
 | 
			
		||||
                            <div class="vinyl-center">
 | 
			
		||||
                                <div class="vinyl-hole"></div>
 | 
			
		||||
                                <div class="vinyl-label">
 | 
			
		||||
                                    <span id="track-name-a">NO TRACK</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="vinyl-grooves">
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="tonearm">
 | 
			
		||||
                            <div class="tonearm-base"></div>
 | 
			
		||||
                            <div class="tonearm-arm"></div>
 | 
			
		||||
                            <div class="tonearm-needle"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- Plattenspieler Controls -->
 | 
			
		||||
                    <div class="turntable-controls">
 | 
			
		||||
                        <button class="play-btn" id="play-a" onclick="togglePlay('A')">
 | 
			
		||||
                            <i class="fas fa-play"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button class="cue-btn" onclick="cue('A')">CUE</button>
 | 
			
		||||
                        <div class="pitch-slider">
 | 
			
		||||
                            <label>PITCH</label>
 | 
			
		||||
                            <input type="range" id="pitch-a" min="-20" max="20" value="0" oninput="adjustPitch('A', this.value)">
 | 
			
		||||
                            <span id="pitch-value-a">0%</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Waveform Display -->
 | 
			
		||||
                <div class="waveform-container">
 | 
			
		||||
                    <canvas id="waveform-a" width="300" height="80"></canvas>
 | 
			
		||||
                    <div class="playhead" id="playhead-a"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Track Info -->
 | 
			
		||||
                <div class="track-info">
 | 
			
		||||
                    <div class="track-title" id="title-a">No Track Loaded</div>
 | 
			
		||||
                    <div class="track-artist" id="artist-a">-</div>
 | 
			
		||||
                    <div class="track-time">
 | 
			
		||||
                        <span id="time-elapsed-a">00:00</span> / 
 | 
			
		||||
                        <span id="time-total-a">00:00</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Center Mixer -->
 | 
			
		||||
            <div class="mixer">
 | 
			
		||||
                <div class="mixer-header">
 | 
			
		||||
                    <h3>MIXER</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Crossfader Section -->
 | 
			
		||||
                <div class="crossfader-section">
 | 
			
		||||
                    <div class="crossfader-container">
 | 
			
		||||
                        <label>A</label>
 | 
			
		||||
                        <input type="range" id="crossfader" min="0" max="100" value="50" oninput="adjustCrossfader(this.value)">
 | 
			
		||||
                        <label>B</label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="crossfader-curve">
 | 
			
		||||
                        <button class="curve-btn active" onclick="setCrossfaderCurve('smooth')">~</button>
 | 
			
		||||
                        <button class="curve-btn" onclick="setCrossfaderCurve('cut')">|</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Channel Controls -->
 | 
			
		||||
                <div class="channel-controls">
 | 
			
		||||
                    <!-- Channel A -->
 | 
			
		||||
                    <div class="channel channel-a">
 | 
			
		||||
                        <div class="channel-header">CH A</div>
 | 
			
		||||
                        
 | 
			
		||||
                        <!-- EQ -->
 | 
			
		||||
                        <div class="eq-section">
 | 
			
		||||
                            <div class="eq-knob">
 | 
			
		||||
                                <label>HIGH</label>
 | 
			
		||||
                                <div class="knob" data-channel="A" data-eq="high">
 | 
			
		||||
                                    <div class="knob-indicator"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <span class="eq-value">0</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="eq-knob">
 | 
			
		||||
                                <label>MID</label>
 | 
			
		||||
                                <div class="knob" data-channel="A" data-eq="mid">
 | 
			
		||||
                                    <div class="knob-indicator"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <span class="eq-value">0</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="eq-knob">
 | 
			
		||||
                                <label>LOW</label>
 | 
			
		||||
                                <div class="knob" data-channel="A" data-eq="low">
 | 
			
		||||
                                    <div class="knob-indicator"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <span class="eq-value">0</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Volume Fader -->
 | 
			
		||||
                        <div class="volume-fader">
 | 
			
		||||
                            <input type="range" id="volume-a" min="0" max="100" value="75" orient="vertical" oninput="adjustVolume('A', this.value)">
 | 
			
		||||
                            <label>VOLUME</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- VU Meter -->
 | 
			
		||||
                        <div class="vu-meter">
 | 
			
		||||
                            <div class="vu-bar" id="vu-a"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- Master Section -->
 | 
			
		||||
                    <div class="master-section">
 | 
			
		||||
                        <div class="master-header">MASTER</div>
 | 
			
		||||
                        
 | 
			
		||||
                        <!-- Master Volume -->
 | 
			
		||||
                        <div class="master-volume">
 | 
			
		||||
                            <input type="range" id="master-volume" min="0" max="100" value="80" orient="vertical" oninput="adjustMasterVolume(this.value)">
 | 
			
		||||
                            <label>MASTER</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Master VU -->
 | 
			
		||||
                        <div class="master-vu">
 | 
			
		||||
                            <div class="vu-bar" id="master-vu"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- BPM Display -->
 | 
			
		||||
                        <div class="bpm-display">
 | 
			
		||||
                            <div class="bpm-value" id="bpm-display">120</div>
 | 
			
		||||
                            <label>BPM</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- Channel B -->
 | 
			
		||||
                    <div class="channel channel-b">
 | 
			
		||||
                        <div class="channel-header">CH B</div>
 | 
			
		||||
                        
 | 
			
		||||
                        <!-- EQ -->
 | 
			
		||||
                        <div class="eq-section">
 | 
			
		||||
                            <div class="eq-knob">
 | 
			
		||||
                                <label>HIGH</label>
 | 
			
		||||
                                <div class="knob" data-channel="B" data-eq="high">
 | 
			
		||||
                                    <div class="knob-indicator"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <span class="eq-value">0</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="eq-knob">
 | 
			
		||||
                                <label>MID</label>
 | 
			
		||||
                                <div class="knob" data-channel="B" data-eq="mid">
 | 
			
		||||
                                    <div class="knob-indicator"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <span class="eq-value">0</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="eq-knob">
 | 
			
		||||
                                <label>LOW</label>
 | 
			
		||||
                                <div class="knob" data-channel="B" data-eq="low">
 | 
			
		||||
                                    <div class="knob-indicator"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <span class="eq-value">0</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Volume Fader -->
 | 
			
		||||
                        <div class="volume-fader">
 | 
			
		||||
                            <input type="range" id="volume-b" min="0" max="100" value="75" orient="vertical" oninput="adjustVolume('B', this.value)">
 | 
			
		||||
                            <label>VOLUME</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- VU Meter -->
 | 
			
		||||
                        <div class="vu-meter">
 | 
			
		||||
                            <div class="vu-bar" id="vu-b"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Effects Section -->
 | 
			
		||||
                <div class="effects-section">
 | 
			
		||||
                    <div class="effect-unit">
 | 
			
		||||
                        <h4>EFFECTS</h4>
 | 
			
		||||
                        <div class="effect-buttons">
 | 
			
		||||
                            <button class="effect-btn" onclick="toggleEffect('reverb')">REVERB</button>
 | 
			
		||||
                            <button class="effect-btn" onclick="toggleEffect('delay')">DELAY</button>
 | 
			
		||||
                            <button class="effect-btn" onclick="toggleEffect('filter')">FILTER</button>
 | 
			
		||||
                            <button class="effect-btn" onclick="toggleEffect('flanger')">FLANGER</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="effect-knob">
 | 
			
		||||
                            <label>WET/DRY</label>
 | 
			
		||||
                            <div class="knob" data-effect="wetdry">
 | 
			
		||||
                                <div class="knob-indicator"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Right Deck -->
 | 
			
		||||
            <div class="deck deck-right">
 | 
			
		||||
                <div class="deck-header">
 | 
			
		||||
                    <h3>DECK B</h3>
 | 
			
		||||
                    <div class="deck-controls">
 | 
			
		||||
                        <button class="btn-deck" onclick="loadTrackToDeck('B')">
 | 
			
		||||
                            <i class="fas fa-folder-open"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button class="btn-deck" onclick="ejectDeck('B')">
 | 
			
		||||
                            <i class="fas fa-eject"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Vinyl Plattenspieler -->
 | 
			
		||||
                <div class="turntable">
 | 
			
		||||
                    <div class="vinyl-container">
 | 
			
		||||
                        <div class="vinyl" id="vinyl-b">
 | 
			
		||||
                            <div class="vinyl-center">
 | 
			
		||||
                                <div class="vinyl-hole"></div>
 | 
			
		||||
                                <div class="vinyl-label">
 | 
			
		||||
                                    <span id="track-name-b">NO TRACK</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="vinyl-grooves">
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                                <div class="groove"></div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="tonearm">
 | 
			
		||||
                            <div class="tonearm-base"></div>
 | 
			
		||||
                            <div class="tonearm-arm"></div>
 | 
			
		||||
                            <div class="tonearm-needle"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- Plattenspieler Controls -->
 | 
			
		||||
                    <div class="turntable-controls">
 | 
			
		||||
                        <button class="play-btn" id="play-b" onclick="togglePlay('B')">
 | 
			
		||||
                            <i class="fas fa-play"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button class="cue-btn" onclick="cue('B')">CUE</button>
 | 
			
		||||
                        <div class="pitch-slider">
 | 
			
		||||
                            <label>PITCH</label>
 | 
			
		||||
                            <input type="range" id="pitch-b" min="-20" max="20" value="0" oninput="adjustPitch('B', this.value)">
 | 
			
		||||
                            <span id="pitch-value-b">0%</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Waveform Display -->
 | 
			
		||||
                <div class="waveform-container">
 | 
			
		||||
                    <canvas id="waveform-b" width="300" height="80"></canvas>
 | 
			
		||||
                    <div class="playhead" id="playhead-b"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Track Info -->
 | 
			
		||||
                <div class="track-info">
 | 
			
		||||
                    <div class="track-title" id="title-b">No Track Loaded</div>
 | 
			
		||||
                    <div class="track-artist" id="artist-b">-</div>
 | 
			
		||||
                    <div class="track-time">
 | 
			
		||||
                        <span id="time-elapsed-b">00:00</span> / 
 | 
			
		||||
                        <span id="time-total-b">00:00</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Bottom Panel -->
 | 
			
		||||
        <div class="bottom-panel">
 | 
			
		||||
            <!-- Playlist Browser -->
 | 
			
		||||
            <div class="playlist-browser">
 | 
			
		||||
                <div class="browser-header">
 | 
			
		||||
                    <h4>MUSIC LIBRARY</h4>
 | 
			
		||||
                    <div class="search-box">
 | 
			
		||||
                        <i class="fas fa-search"></i>
 | 
			
		||||
                        <input type="text" id="search-input" placeholder="Search tracks..." oninput="searchTracks(this.value)">
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="track-list" id="track-list">
 | 
			
		||||
                    <!-- Tracks werden hier dynamisch geladen -->
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Recording Section -->
 | 
			
		||||
            <div class="recording-section">
 | 
			
		||||
                <h4>RECORDING</h4>
 | 
			
		||||
                <button class="record-btn" onclick="toggleRecording()">
 | 
			
		||||
                    <i class="fas fa-circle"></i>
 | 
			
		||||
                    <span>REC</span>
 | 
			
		||||
                </button>
 | 
			
		||||
                <div class="recording-time">00:00</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    <!-- Track Loader Modal -->
 | 
			
		||||
    <div id="track-loader" class="modal hidden">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h3>Load Track</h3>
 | 
			
		||||
                <button class="close-modal" onclick="closeTrackLoader()">
 | 
			
		||||
                    <i class="fas fa-times"></i>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <div class="input-group">
 | 
			
		||||
                    <label>Track Title</label>
 | 
			
		||||
                    <input type="text" id="track-title" placeholder="Enter track title">
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="input-group">
 | 
			
		||||
                    <label>Artist</label>
 | 
			
		||||
                    <input type="text" id="track-artist" placeholder="Enter artist name">
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="input-group">
 | 
			
		||||
                    <label>URL (YouTube or Direct)</label>
 | 
			
		||||
                    <input type="text" id="track-url" placeholder="https://www.youtube.com/watch?v=...">
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="modal-actions">
 | 
			
		||||
                    <button class="btn-cancel" onclick="closeTrackLoader()">Cancel</button>
 | 
			
		||||
                    <button class="btn-load" onclick="confirmLoadTrack()">Load Track</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Audio Players (versteckt) -->
 | 
			
		||||
    <audio id="audio-player-a" style="display: none;"></audio>
 | 
			
		||||
    <audio id="audio-player-b" style="display: none;"></audio>
 | 
			
		||||
 | 
			
		||||
    <script src="script.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -377,3 +377,942 @@ window.djDebug = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
console.log('DJ System: YouTube streaming system loaded');
 | 
			
		||||
 | 
			
		||||
// DJ System - Professional Interface
 | 
			
		||||
let djInterface = {
 | 
			
		||||
    decks: {
 | 
			
		||||
        A: {
 | 
			
		||||
            track: null,
 | 
			
		||||
            isPlaying: false,
 | 
			
		||||
            volume: 75,
 | 
			
		||||
            pitch: 0,
 | 
			
		||||
            cuePoint: 0,
 | 
			
		||||
            player: null
 | 
			
		||||
        },
 | 
			
		||||
        B: {
 | 
			
		||||
            track: null,
 | 
			
		||||
            isPlaying: false,
 | 
			
		||||
            volume: 75,
 | 
			
		||||
            pitch: 0,
 | 
			
		||||
            cuePoint: 0,
 | 
			
		||||
            player: 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
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Initialize DJ Interface
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
    initializeDJSystem();
 | 
			
		||||
    setupEventListeners();
 | 
			
		||||
    startAnimations();
 | 
			
		||||
    loadYouTubeAPI();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function initializeDJSystem() {
 | 
			
		||||
    console.log('DJ System: Initializing professional interface...');
 | 
			
		||||
    
 | 
			
		||||
    // 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();
 | 
			
		||||
    
 | 
			
		||||
    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 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', () => {
 | 
			
		||||
            isDragging = false;
 | 
			
		||||
            document.body.style.cursor = 'default';
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCurrentRotation(element) {
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Crossfader (Fortsetzung)
 | 
			
		||||
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) {
 | 
			
		||||
    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()}`);
 | 
			
		||||
    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);
 | 
			
		||||
            
 | 
			
		||||
            document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent = 
 | 
			
		||||
                formatTime(currentTime);
 | 
			
		||||
            document.getElementById(`time-total-${deck.toLowerCase()}`).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');
 | 
			
		||||
    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()}`);
 | 
			
		||||
    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
 | 
			
		||||
function closeDJInterface() {
 | 
			
		||||
    document.getElementById('dj-interface').classList.add('hidden');
 | 
			
		||||
    
 | 
			
		||||
    // Stop all playback
 | 
			
		||||
    ['A', 'B'].forEach(deck => {
 | 
			
		||||
        if (djInterface.decks[deck].isPlaying) {
 | 
			
		||||
            togglePlay(deck);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Notify FiveM
 | 
			
		||||
    notifyFiveM('djInterfaceClosed', {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showDJInterface() {
 | 
			
		||||
    document.getElementById('dj-interface').classList.remove('hidden');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 djInterface = document.getElementById('dj-interface');
 | 
			
		||||
    if (window.innerWidth < 1200) {
 | 
			
		||||
        djInterface.classList.add('mobile-layout');
 | 
			
		||||
    } else {
 | 
			
		||||
        djInterface.classList.remove('mobile-layout');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// YouTube Integration (from previous code)
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 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');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue