forked from Simnation/Main
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>
|
||||
<!-- 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>
|
||||
|
||||
<!-- YouTube Player wird dynamisch erstellt -->
|
||||
<!-- Container wird von JavaScript erstellt -->
|
||||
<!-- 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