forked from Simnation/Main
1939 lines
59 KiB
JavaScript
1939 lines
59 KiB
JavaScript
// DJ System - Professional Interface
|
||
let djInterface = {
|
||
decks: {
|
||
A: {
|
||
track: null,
|
||
isPlaying: false,
|
||
volume: 75,
|
||
pitch: 0,
|
||
cuePoint: 0,
|
||
player: null,
|
||
youtubePlayer: null
|
||
},
|
||
B: {
|
||
track: null,
|
||
isPlaying: false,
|
||
volume: 75,
|
||
pitch: 0,
|
||
cuePoint: 0,
|
||
player: null,
|
||
youtubePlayer: null
|
||
}
|
||
},
|
||
mixer: {
|
||
crossfader: 50,
|
||
masterVolume: 80,
|
||
eq: {
|
||
A: { high: 0, mid: 0, low: 0 },
|
||
B: { high: 0, mid: 0, low: 0 }
|
||
}
|
||
},
|
||
effects: {
|
||
reverb: false,
|
||
delay: false,
|
||
filter: false,
|
||
flanger: false,
|
||
wetDry: 0
|
||
},
|
||
currentDeck: null,
|
||
isRecording: false,
|
||
youtubeAPIReady: false
|
||
};
|
||
|
||
// Interface Drag & Resize Variablen
|
||
let isDragging = false;
|
||
let isResizing = false;
|
||
let dragStartX = 0;
|
||
let dragStartY = 0;
|
||
let resizeStartWidth = 0;
|
||
let resizeStartHeight = 0;
|
||
let interfacePosition = { x: 0, y: 0 };
|
||
let interfaceSize = { width: 1000, height: 700 }; // Standard-Größe
|
||
|
||
// Initialize DJ Interface
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializeDJSystem();
|
||
setupEventListeners();
|
||
startAnimations();
|
||
loadYouTubeAPI();
|
||
});
|
||
|
||
function initializeDJSystem() {
|
||
console.log('DJ System: Initializing professional interface...');
|
||
|
||
// Lösche gespeicherte Einstellungen, um die neue Position zu erzwingen
|
||
localStorage.removeItem('djInterfacePosition');
|
||
localStorage.removeItem('djInterfaceSize');
|
||
|
||
// Initialisiere Interface-Einstellungen
|
||
initializeInterfaceSettings();
|
||
|
||
// Initialize audio players
|
||
djInterface.decks.A.player = document.getElementById('audio-player-a');
|
||
djInterface.decks.B.player = document.getElementById('audio-player-b');
|
||
|
||
// Setup audio event listeners
|
||
setupAudioEventListeners();
|
||
|
||
// Initialize waveforms
|
||
initializeWaveforms();
|
||
|
||
// Start time display
|
||
updateTimeDisplay();
|
||
setInterval(updateTimeDisplay, 1000);
|
||
|
||
// Start VU meters animation
|
||
startVUMeters();
|
||
|
||
// Setup Interface-Kontrollen
|
||
setupInterfaceControls();
|
||
|
||
console.log('DJ System: Professional interface ready!');
|
||
}
|
||
|
||
function initializeDJSystem() {
|
||
console.log('DJ System: Initializing professional interface...');
|
||
|
||
// Initialisiere Interface-Einstellungen
|
||
initializeInterfaceSettings();
|
||
|
||
// Initialize audio players
|
||
djInterface.decks.A.player = document.getElementById('audio-player-a');
|
||
djInterface.decks.B.player = document.getElementById('audio-player-b');
|
||
|
||
// Setup audio event listeners
|
||
setupAudioEventListeners();
|
||
|
||
// Initialize waveforms
|
||
initializeWaveforms();
|
||
|
||
// Start time display
|
||
updateTimeDisplay();
|
||
setInterval(updateTimeDisplay, 1000);
|
||
|
||
// Start VU meters animation
|
||
startVUMeters();
|
||
|
||
// Setup Interface-Kontrollen
|
||
setupInterfaceControls();
|
||
|
||
// Lade Playlists
|
||
loadPlaylists();
|
||
|
||
// Setup Playlist Event Listeners
|
||
setupPlaylistEventListeners();
|
||
|
||
// Event-Listener für "Neue Playlist"-Button
|
||
const createPlaylistBtn = document.getElementById('create-playlist-btn');
|
||
if (createPlaylistBtn) {
|
||
createPlaylistBtn.addEventListener('click', createNewPlaylist);
|
||
}
|
||
|
||
console.log('DJ System: Professional interface ready!');
|
||
}
|
||
|
||
|
||
|
||
function setupEventListeners() {
|
||
// Knob interactions
|
||
setupKnobControls();
|
||
|
||
// Keyboard shortcuts
|
||
document.addEventListener('keydown', handleKeyboardShortcuts);
|
||
|
||
// Window resize
|
||
window.addEventListener('resize', handleResize);
|
||
}
|
||
|
||
function setupAudioEventListeners() {
|
||
// Setup event listeners for audio players
|
||
if (djInterface.decks.A.player) {
|
||
djInterface.decks.A.player.addEventListener('ended', function() {
|
||
notifyFiveM('songEnded', { deck: 'A' });
|
||
});
|
||
|
||
djInterface.decks.A.player.addEventListener('error', function(e) {
|
||
notifyFiveM('audioError', {
|
||
deck: 'A',
|
||
error: 'Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error')
|
||
});
|
||
});
|
||
}
|
||
|
||
if (djInterface.decks.B.player) {
|
||
djInterface.decks.B.player.addEventListener('ended', function() {
|
||
notifyFiveM('songEnded', { deck: 'B' });
|
||
});
|
||
|
||
djInterface.decks.B.player.addEventListener('error', function(e) {
|
||
notifyFiveM('audioError', {
|
||
deck: 'B',
|
||
error: 'Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error')
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
function setupKnobControls() {
|
||
const knobs = document.querySelectorAll('.knob');
|
||
|
||
knobs.forEach(knob => {
|
||
let isDragging = false;
|
||
let startY = 0;
|
||
let startRotation = 0;
|
||
|
||
knob.addEventListener('mousedown', (e) => {
|
||
isDragging = true;
|
||
startY = e.clientY;
|
||
startRotation = getCurrentRotation(knob.querySelector('.knob-indicator'));
|
||
document.body.style.cursor = 'grabbing';
|
||
e.preventDefault();
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (!isDragging) return;
|
||
|
||
const deltaY = startY - e.clientY;
|
||
const rotation = Math.max(-150, Math.min(150, startRotation + deltaY * 2));
|
||
|
||
const indicator = knob.querySelector('.knob-indicator');
|
||
indicator.style.transform = `translateX(-50%) rotate(${rotation}deg)`;
|
||
|
||
// Update EQ values
|
||
const channel = knob.dataset.channel;
|
||
const eqType = knob.dataset.eq;
|
||
const effect = knob.dataset.effect;
|
||
|
||
if (channel && eqType) {
|
||
const value = Math.round((rotation / 150) * 100);
|
||
djInterface.mixer.eq[channel][eqType] = value;
|
||
|
||
const valueSpan = knob.parentElement.querySelector('.eq-value');
|
||
if (valueSpan) {
|
||
valueSpan.textContent = value > 0 ? `+${value}` : value;
|
||
}
|
||
|
||
applyEQ(channel, eqType, value);
|
||
}
|
||
|
||
if (effect) {
|
||
const value = Math.round(((rotation + 150) / 300) * 100);
|
||
djInterface.effects[effect] = value;
|
||
applyEffect(effect, value);
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
if (isDragging) {
|
||
isDragging = false;
|
||
document.body.style.cursor = 'default';
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function getCurrentRotation(element) {
|
||
if (!element) return 0;
|
||
|
||
const transform = window.getComputedStyle(element).transform;
|
||
if (transform === 'none') return 0;
|
||
|
||
const matrix = transform.match(/matrix\(([^)]+)\)/);
|
||
if (!matrix) return 0;
|
||
|
||
const values = matrix[1].split(',').map(parseFloat);
|
||
const angle = Math.atan2(values[1], values[0]) * (180 / Math.PI);
|
||
return angle;
|
||
}
|
||
|
||
// YouTube API laden
|
||
function loadYouTubeAPI() {
|
||
// YouTube IFrame API Script laden
|
||
const tag = document.createElement('script');
|
||
tag.src = 'https://www.youtube.com/iframe_api';
|
||
const firstScriptTag = document.getElementsByTagName('script')[0];
|
||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||
}
|
||
|
||
// YouTube API Ready Callback
|
||
window.onYouTubeIframeAPIReady = function() {
|
||
console.log('DJ System: YouTube API ready');
|
||
djInterface.youtubeAPIReady = true;
|
||
};
|
||
|
||
// Deck Functions
|
||
function loadTrackToDeck(deck) {
|
||
djInterface.currentDeck = deck;
|
||
document.getElementById('track-loader').classList.remove('hidden');
|
||
}
|
||
|
||
function confirmLoadTrack() {
|
||
const title = document.getElementById('track-title').value;
|
||
const artist = document.getElementById('track-artist').value;
|
||
const url = document.getElementById('track-url').value;
|
||
|
||
if (!title || !url) {
|
||
showNotification('Please fill in title and URL', 'error');
|
||
return;
|
||
}
|
||
|
||
const deck = djInterface.currentDeck;
|
||
const trackData = { title, artist, url };
|
||
|
||
loadTrackToPlayer(deck, trackData);
|
||
closeTrackLoader();
|
||
|
||
showNotification(`Track loaded to Deck ${deck}: ${title}`, 'success');
|
||
}
|
||
|
||
function loadTrackToPlayer(deck, trackData) {
|
||
const deckData = djInterface.decks[deck];
|
||
deckData.track = trackData;
|
||
|
||
// Update UI
|
||
document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = trackData.title.substring(0, 15);
|
||
document.getElementById(`title-${deck.toLowerCase()}`).textContent = trackData.title;
|
||
document.getElementById(`artist-${deck.toLowerCase()}`).textContent = trackData.artist || 'Unknown Artist';
|
||
|
||
// Load audio
|
||
if (isYouTubeUrl(trackData.url)) {
|
||
loadYouTubeTrack(deck, trackData);
|
||
} else {
|
||
loadDirectTrack(deck, trackData);
|
||
}
|
||
|
||
// Generate waveform
|
||
generateWaveform(deck, trackData.url);
|
||
}
|
||
|
||
function loadYouTubeTrack(deck, trackData) {
|
||
const videoId = extractYouTubeVideoId(trackData.url);
|
||
if (!videoId) {
|
||
showNotification('Invalid YouTube URL', 'error');
|
||
return;
|
||
}
|
||
|
||
// Create hidden YouTube player for this deck
|
||
createYouTubePlayerForDeck(deck, videoId);
|
||
}
|
||
|
||
function createYouTubePlayerForDeck(deck, videoId) {
|
||
if (!djInterface.youtubeAPIReady) {
|
||
console.error('DJ System: YouTube API not ready');
|
||
showNotification('YouTube API not ready', 'error');
|
||
return;
|
||
}
|
||
|
||
// Container für YouTube Player erstellen
|
||
const containerId = `youtube-player-${deck.toLowerCase()}`;
|
||
let container = document.getElementById(containerId);
|
||
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.id = containerId;
|
||
container.style.position = 'absolute';
|
||
container.style.top = '-9999px';
|
||
container.style.left = '-9999px';
|
||
container.style.width = '1px';
|
||
container.style.height = '1px';
|
||
container.style.opacity = '0';
|
||
document.body.appendChild(container);
|
||
}
|
||
|
||
// Bestehenden Player zerstören, falls vorhanden
|
||
if (djInterface.decks[deck].youtubePlayer) {
|
||
try {
|
||
djInterface.decks[deck].youtubePlayer.destroy();
|
||
} catch (e) {
|
||
console.error('DJ System: Error destroying YouTube player', e);
|
||
}
|
||
}
|
||
|
||
// Neuen Player erstellen
|
||
djInterface.decks[deck].youtubePlayer = new YT.Player(containerId, {
|
||
height: '1',
|
||
width: '1',
|
||
videoId: videoId,
|
||
playerVars: {
|
||
'autoplay': 0,
|
||
'controls': 0,
|
||
'disablekb': 1,
|
||
'fs': 0,
|
||
'modestbranding': 1,
|
||
'playsinline': 1,
|
||
'rel': 0,
|
||
'showinfo': 0,
|
||
'iv_load_policy': 3,
|
||
'cc_load_policy': 0,
|
||
'origin': window.location.origin
|
||
},
|
||
events: {
|
||
'onReady': function(event) {
|
||
console.log(`DJ System: YouTube player ready for Deck ${deck}`);
|
||
// Setze Lautstärke
|
||
event.target.setVolume(djInterface.decks[deck].volume);
|
||
|
||
// Generiere Waveform
|
||
generateWaveform(deck, videoId);
|
||
|
||
// Aktualisiere UI
|
||
const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`);
|
||
if (vinyl) {
|
||
vinyl.classList.add('loaded');
|
||
}
|
||
},
|
||
'onStateChange': function(event) {
|
||
handleYouTubeStateChange(deck, event);
|
||
},
|
||
'onError': function(event) {
|
||
handleYouTubeError(deck, event);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// YouTube State Change Handler
|
||
function handleYouTubeStateChange(deck, event) {
|
||
const deckData = djInterface.decks[deck];
|
||
const playBtn = document.getElementById(`play-${deck.toLowerCase()}`);
|
||
const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`);
|
||
|
||
switch(event.data) {
|
||
case YT.PlayerState.PLAYING:
|
||
console.log(`DJ System: YouTube playing on Deck ${deck}`);
|
||
deckData.isPlaying = true;
|
||
if (playBtn) {
|
||
playBtn.innerHTML = '<i class="fas fa-pause"></i>';
|
||
playBtn.classList.add('playing');
|
||
}
|
||
if (vinyl) {
|
||
vinyl.classList.add('spinning');
|
||
}
|
||
|
||
// Notify FiveM
|
||
notifyFiveM('deckStateChanged', {
|
||
deck: deck,
|
||
isPlaying: true,
|
||
track: deckData.track
|
||
});
|
||
break;
|
||
|
||
case YT.PlayerState.PAUSED:
|
||
console.log(`DJ System: YouTube paused on Deck ${deck}`);
|
||
deckData.isPlaying = false;
|
||
if (playBtn) {
|
||
playBtn.innerHTML = '<i class="fas fa-play"></i>';
|
||
playBtn.classList.remove('playing');
|
||
}
|
||
if (vinyl) {
|
||
vinyl.classList.remove('spinning');
|
||
}
|
||
|
||
// Notify FiveM
|
||
notifyFiveM('deckStateChanged', {
|
||
deck: deck,
|
||
isPlaying: false,
|
||
track: deckData.track
|
||
});
|
||
break;
|
||
|
||
case YT.PlayerState.ENDED:
|
||
console.log(`DJ System: YouTube ended on Deck ${deck}`);
|
||
deckData.isPlaying = false;
|
||
if (playBtn) {
|
||
playBtn.innerHTML = '<i class="fas fa-play"></i>';
|
||
playBtn.classList.remove('playing');
|
||
}
|
||
if (vinyl) {
|
||
vinyl.classList.remove('spinning');
|
||
}
|
||
|
||
// Notify FiveM
|
||
notifyFiveM('songEnded', {
|
||
deck: deck
|
||
});
|
||
break;
|
||
}
|
||
}
|
||
|
||
// YouTube Error Handler
|
||
function handleYouTubeError(deck, event) {
|
||
console.error(`DJ System: YouTube error on Deck ${deck}:`, event.data);
|
||
let errorMessage = 'YouTube Fehler';
|
||
|
||
switch(event.data) {
|
||
case 2:
|
||
errorMessage = 'Ungültige Video ID';
|
||
break;
|
||
case 5:
|
||
errorMessage = 'HTML5 Player Fehler';
|
||
break;
|
||
case 100:
|
||
errorMessage = 'Video nicht gefunden';
|
||
break;
|
||
case 101:
|
||
case 150:
|
||
errorMessage = 'Video nicht verfügbar (Einbettung deaktiviert)';
|
||
break;
|
||
}
|
||
|
||
showNotification(errorMessage, 'error');
|
||
|
||
// Notify FiveM
|
||
notifyFiveM('audioError', {
|
||
deck: deck,
|
||
error: errorMessage
|
||
});
|
||
}
|
||
|
||
function loadDirectTrack(deck, trackData) {
|
||
const player = djInterface.decks[deck].player;
|
||
player.src = trackData.url;
|
||
player.load();
|
||
}
|
||
|
||
function togglePlay(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
const playBtn = document.getElementById(`play-${deck.toLowerCase()}`);
|
||
const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`);
|
||
|
||
if (!deckData.track) {
|
||
showNotification(`No track loaded in Deck ${deck}`, 'warning');
|
||
return;
|
||
}
|
||
|
||
if (deckData.isPlaying) {
|
||
// Pause
|
||
pauseTrack(deck);
|
||
playBtn.innerHTML = '<i class="fas fa-play"></i>';
|
||
playBtn.classList.remove('playing');
|
||
vinyl.classList.remove('spinning');
|
||
deckData.isPlaying = false;
|
||
} else {
|
||
// Play
|
||
playTrack(deck);
|
||
playBtn.innerHTML = '<i class="fas fa-pause"></i>';
|
||
playBtn.classList.add('playing');
|
||
vinyl.classList.add('spinning');
|
||
deckData.isPlaying = true;
|
||
}
|
||
|
||
// Send to FiveM
|
||
notifyFiveM('deckStateChanged', {
|
||
deck: deck,
|
||
isPlaying: deckData.isPlaying,
|
||
track: deckData.track
|
||
});
|
||
}
|
||
|
||
function playTrack(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
|
||
if (deckData.youtubePlayer) {
|
||
deckData.youtubePlayer.playVideo();
|
||
} else if (deckData.player) {
|
||
deckData.player.play();
|
||
}
|
||
}
|
||
|
||
function pauseTrack(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
|
||
if (deckData.youtubePlayer) {
|
||
deckData.youtubePlayer.pauseVideo();
|
||
} else if (deckData.player) {
|
||
deckData.player.pause();
|
||
}
|
||
}
|
||
|
||
function cue(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
|
||
if (!deckData.track) {
|
||
showNotification(`No track loaded in Deck ${deck}`, 'warning');
|
||
return;
|
||
}
|
||
|
||
// Set cue point to current position or go to cue point
|
||
if (deckData.isPlaying) {
|
||
deckData.cuePoint = getCurrentTime(deck);
|
||
showNotification(`Cue point set in Deck ${deck}`, 'info');
|
||
} else {
|
||
seekTo(deck, deckData.cuePoint);
|
||
showNotification(`Jumped to cue point in Deck ${deck}`, 'info');
|
||
}
|
||
}
|
||
|
||
function adjustPitch(deck, value) {
|
||
const deckData = djInterface.decks[deck];
|
||
deckData.pitch = parseFloat(value);
|
||
|
||
document.getElementById(`pitch-value-${deck.toLowerCase()}`).textContent = `${value}%`;
|
||
|
||
// Apply pitch change (this would need audio processing)
|
||
applyPitchChange(deck, value);
|
||
}
|
||
|
||
function adjustVolume(deck, value) {
|
||
const deckData = djInterface.decks[deck];
|
||
deckData.volume = parseInt(value);
|
||
|
||
if (deckData.youtubePlayer) {
|
||
deckData.youtubePlayer.setVolume(value);
|
||
} else if (deckData.player) {
|
||
deckData.player.volume = value / 100;
|
||
}
|
||
|
||
updateVUMeter(deck, value);
|
||
|
||
// Notify FiveM about volume change
|
||
notifyFiveM('volumeChanged', {
|
||
deck: deck,
|
||
volume: value
|
||
});
|
||
}
|
||
|
||
function adjustCrossfader(value) {
|
||
djInterface.mixer.crossfader = parseInt(value);
|
||
|
||
const volumeA = value <= 50 ? 100 : (100 - (value - 50) * 2);
|
||
const volumeB = value >= 50 ? 100 : (value * 2);
|
||
|
||
// Apply crossfader mixing
|
||
applyCrossfaderMix(volumeA, volumeB);
|
||
|
||
// Visual feedback
|
||
const crossfader = document.getElementById('crossfader');
|
||
crossfader.style.background = `linear-gradient(90deg,
|
||
rgba(78, 205, 196, ${volumeA/100}) 0%,
|
||
rgba(255, 107, 107, 0.5) 50%,
|
||
rgba(78, 205, 196, ${volumeB/100}) 100%)`;
|
||
}
|
||
|
||
function applyCrossfaderMix(volumeA, volumeB) {
|
||
const deckA = djInterface.decks.A;
|
||
const deckB = djInterface.decks.B;
|
||
|
||
const finalVolumeA = (deckA.volume * volumeA / 100) / 100;
|
||
const finalVolumeB = (deckB.volume * volumeB / 100) / 100;
|
||
|
||
if (deckA.youtubePlayer) {
|
||
deckA.youtubePlayer.setVolume(finalVolumeA * 100);
|
||
} else if (deckA.player) {
|
||
deckA.player.volume = finalVolumeA;
|
||
}
|
||
|
||
if (deckB.youtubePlayer) {
|
||
deckB.youtubePlayer.setVolume(finalVolumeB * 100);
|
||
} else if (deckB.player) {
|
||
deckB.player.volume = finalVolumeB;
|
||
}
|
||
}
|
||
|
||
function adjustMasterVolume(value) {
|
||
djInterface.mixer.masterVolume = parseInt(value);
|
||
|
||
// Apply master volume to both decks
|
||
const masterMultiplier = value / 100;
|
||
|
||
Object.keys(djInterface.decks).forEach(deck => {
|
||
const deckData = djInterface.decks[deck];
|
||
const finalVolume = (deckData.volume * masterMultiplier) / 100;
|
||
|
||
if (deckData.youtubePlayer) {
|
||
deckData.youtubePlayer.setVolume(finalVolume * 100);
|
||
} else if (deckData.player) {
|
||
deckData.player.volume = finalVolume;
|
||
}
|
||
});
|
||
|
||
updateMasterVU(value);
|
||
}
|
||
|
||
function setCrossfaderCurve(type) {
|
||
document.querySelectorAll('.curve-btn').forEach(btn => btn.classList.remove('active'));
|
||
event.target.classList.add('active');
|
||
|
||
djInterface.mixer.crossfaderCurve = type;
|
||
showNotification(`Crossfader curve set to ${type}`, 'info');
|
||
}
|
||
|
||
// EQ Functions
|
||
function applyEQ(channel, eqType, value) {
|
||
const deckData = djInterface.decks[channel];
|
||
|
||
if (!deckData.audioContext) {
|
||
setupAudioContext(channel);
|
||
}
|
||
|
||
// Apply EQ filter (simplified)
|
||
console.log(`Applying ${eqType} EQ: ${value}% to Deck ${channel}`);
|
||
|
||
// Visual feedback
|
||
const knob = document.querySelector(`[data-channel="${channel}"][data-eq="${eqType}"] .knob-indicator`);
|
||
if (knob) {
|
||
const hue = value > 0 ? 120 : value < 0 ? 0 : 200;
|
||
knob.style.boxShadow = `0 0 8px hsl(${hue}, 70%, 50%)`;
|
||
}
|
||
}
|
||
|
||
// Effects Functions
|
||
function toggleEffect(effectType) {
|
||
const isActive = djInterface.effects[effectType];
|
||
djInterface.effects[effectType] = !isActive;
|
||
|
||
const btn = event.target;
|
||
btn.classList.toggle('active');
|
||
|
||
if (djInterface.effects[effectType]) {
|
||
applyEffect(effectType, 50);
|
||
showNotification(`${effectType.toUpperCase()} ON`, 'success');
|
||
} else {
|
||
removeEffect(effectType);
|
||
showNotification(`${effectType.toUpperCase()} OFF`, 'info');
|
||
}
|
||
}
|
||
|
||
function applyEffect(effectType, value) {
|
||
console.log(`Applying ${effectType} effect: ${value}%`);
|
||
|
||
// Effect processing would go here
|
||
switch(effectType) {
|
||
case 'reverb':
|
||
applyReverb(value);
|
||
break;
|
||
case 'delay':
|
||
applyDelay(value);
|
||
break;
|
||
case 'filter':
|
||
applyFilter(value);
|
||
break;
|
||
case 'flanger':
|
||
applyFlanger(value);
|
||
break;
|
||
}
|
||
}
|
||
|
||
function removeEffect(effectType) {
|
||
console.log(`Removing ${effectType} effect`);
|
||
// Remove effect processing
|
||
}
|
||
|
||
// Waveform Functions
|
||
function initializeWaveforms() {
|
||
const canvasA = document.getElementById('waveform-a');
|
||
const canvasB = document.getElementById('waveform-b');
|
||
|
||
if (canvasA && canvasB) {
|
||
drawEmptyWaveform(canvasA);
|
||
drawEmptyWaveform(canvasB);
|
||
}
|
||
}
|
||
|
||
function drawEmptyWaveform(canvas) {
|
||
if (!canvas) return;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
const width = canvas.width;
|
||
const height = canvas.height;
|
||
|
||
ctx.clearRect(0, 0, width, height);
|
||
|
||
// Draw center line
|
||
ctx.strokeStyle = 'rgba(78, 205, 196, 0.3)';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, height / 2);
|
||
ctx.lineTo(width, height / 2);
|
||
ctx.stroke();
|
||
|
||
// Draw grid
|
||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
||
for (let i = 0; i < width; i += 20) {
|
||
ctx.beginPath();
|
||
ctx.moveTo(i, 0);
|
||
ctx.lineTo(i, height);
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
|
||
function generateWaveform(deck, url) {
|
||
const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`);
|
||
if (!canvas) return;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
// Simulate waveform generation
|
||
drawSimulatedWaveform(ctx, canvas.width, canvas.height);
|
||
}
|
||
|
||
function drawSimulatedWaveform(ctx, width, height) {
|
||
ctx.clearRect(0, 0, width, height);
|
||
|
||
const centerY = height / 2;
|
||
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
||
gradient.addColorStop(0, 'rgba(255, 107, 107, 0.8)');
|
||
gradient.addColorStop(0.5, 'rgba(78, 205, 196, 0.8)');
|
||
gradient.addColorStop(1, 'rgba(255, 107, 107, 0.8)');
|
||
|
||
ctx.fillStyle = gradient;
|
||
|
||
for (let x = 0; x < width; x += 2) {
|
||
const amplitude = Math.random() * (height / 2) * (0.3 + Math.sin(x * 0.01) * 0.7);
|
||
ctx.fillRect(x, centerY - amplitude, 2, amplitude * 2);
|
||
}
|
||
}
|
||
|
||
// VU Meters
|
||
function startVUMeters() {
|
||
setInterval(() => {
|
||
updateVUMeter('A', djInterface.decks.A.isPlaying ? Math.random() * 100 : 0);
|
||
updateVUMeter('B', djInterface.decks.B.isPlaying ? Math.random() * 100 : 0);
|
||
updateMasterVU(djInterface.mixer.masterVolume);
|
||
}, 100);
|
||
}
|
||
|
||
function updateVUMeter(deck, level) {
|
||
const vuBar = document.getElementById(`vu-${deck.toLowerCase()}`);
|
||
if (vuBar) {
|
||
vuBar.style.height = `${level}%`;
|
||
|
||
// Color based on level
|
||
if (level > 80) {
|
||
vuBar.style.background = 'linear-gradient(180deg, #ff0000, #ff6b6b)';
|
||
} else if (level > 60) {
|
||
vuBar.style.background = 'linear-gradient(180deg, #feca57, #ff6b6b)';
|
||
} else {
|
||
vuBar.style.background = 'linear-gradient(180deg, #4ecdc4, #feca57)';
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateMasterVU(level) {
|
||
const masterVU = document.getElementById('master-vu');
|
||
if (masterVU) {
|
||
masterVU.style.height = `${level}%`;
|
||
}
|
||
}
|
||
|
||
// Time and BPM
|
||
function updateTimeDisplay() {
|
||
const now = new Date();
|
||
const timeString = now.toLocaleTimeString('de-DE', {
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
|
||
const timeDisplay = document.getElementById('current-time');
|
||
if (timeDisplay) {
|
||
timeDisplay.textContent = timeString;
|
||
}
|
||
|
||
// Update track times
|
||
updateTrackTimes();
|
||
|
||
// Update BPM (simulated)
|
||
updateBPMDisplay();
|
||
}
|
||
|
||
function updateTrackTimes() {
|
||
['A', 'B'].forEach(deck => {
|
||
const deckData = djInterface.decks[deck];
|
||
if (deckData.track && deckData.isPlaying) {
|
||
const currentTime = getCurrentTime(deck);
|
||
const duration = getDuration(deck);
|
||
|
||
const elapsedElement = document.getElementById(`time-elapsed-${deck.toLowerCase()}`);
|
||
const totalElement = document.getElementById(`time-total-${deck.toLowerCase()}`);
|
||
|
||
if (elapsedElement) {
|
||
elapsedElement.textContent = formatTime(currentTime);
|
||
}
|
||
|
||
if (totalElement) {
|
||
totalElement.textContent = formatTime(duration);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function getCurrentTime(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
|
||
if (deckData.youtubePlayer) {
|
||
return deckData.youtubePlayer.getCurrentTime() || 0;
|
||
} else if (deckData.player) {
|
||
return deckData.player.currentTime || 0;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
function getDuration(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
|
||
if (deckData.youtubePlayer) {
|
||
return deckData.youtubePlayer.getDuration() || 0;
|
||
} else if (deckData.player) {
|
||
return deckData.player.duration || 0;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
function formatTime(seconds) {
|
||
const mins = Math.floor(seconds / 60);
|
||
const secs = Math.floor(seconds % 60);
|
||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
function updateBPMDisplay() {
|
||
// Simulate BPM detection
|
||
const bpm = 120 + Math.floor(Math.random() * 40);
|
||
const bpmDisplay = document.getElementById('bpm-display');
|
||
if (bpmDisplay) {
|
||
bpmDisplay.textContent = bpm;
|
||
}
|
||
}
|
||
|
||
// Recording Functions
|
||
function toggleRecording() {
|
||
djInterface.isRecording = !djInterface.isRecording;
|
||
|
||
const recordBtn = document.querySelector('.record-btn');
|
||
const recordTime = document.querySelector('.recording-time');
|
||
|
||
if (djInterface.isRecording) {
|
||
recordBtn.classList.add('recording');
|
||
recordBtn.innerHTML = '<i class="fas fa-stop"></i><span>STOP</span>';
|
||
startRecordingTimer();
|
||
showNotification('Recording started', 'success');
|
||
} else {
|
||
recordBtn.classList.remove('recording');
|
||
recordBtn.innerHTML = '<i class="fas fa-circle"></i><span>REC</span>';
|
||
stopRecordingTimer();
|
||
showNotification('Recording stopped', 'info');
|
||
}
|
||
}
|
||
|
||
let recordingStartTime = 0;
|
||
let recordingTimer = null;
|
||
|
||
function startRecordingTimer() {
|
||
recordingStartTime = Date.now();
|
||
recordingTimer = setInterval(() => {
|
||
const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
|
||
const recordTime = document.querySelector('.recording-time');
|
||
if (recordTime) {
|
||
recordTime.textContent = formatTime(elapsed);
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function stopRecordingTimer() {
|
||
if (recordingTimer) {
|
||
clearInterval(recordingTimer);
|
||
recordingTimer = null;
|
||
}
|
||
|
||
const recordTime = document.querySelector('.recording-time');
|
||
if (recordTime) {
|
||
recordTime.textContent = '00:00';
|
||
}
|
||
}
|
||
|
||
// Search and Library Functions
|
||
function searchTracks(query) {
|
||
console.log('Searching tracks:', query);
|
||
|
||
// Simulate track search
|
||
const mockTracks = [
|
||
{ title: 'Summer Vibes', artist: 'DJ Cool', url: 'https://example.com/track1' },
|
||
{ title: 'Night Drive', artist: 'Electronic Dreams', url: 'https://example.com/track2' },
|
||
{ title: 'Bass Drop', artist: 'Heavy Beats', url: 'https://example.com/track3' },
|
||
{ title: 'Chill Out', artist: 'Ambient Sounds', url: 'https://example.com/track4' }
|
||
];
|
||
|
||
const filteredTracks = mockTracks.filter(track =>
|
||
track.title.toLowerCase().includes(query.toLowerCase()) ||
|
||
track.artist.toLowerCase().includes(query.toLowerCase())
|
||
);
|
||
|
||
displayTrackList(filteredTracks);
|
||
}
|
||
|
||
function displayTrackList(tracks) {
|
||
const trackList = document.getElementById('track-list');
|
||
if (!trackList) return;
|
||
|
||
trackList.innerHTML = '';
|
||
|
||
tracks.forEach(track => {
|
||
const trackItem = document.createElement('div');
|
||
trackItem.className = 'track-item';
|
||
trackItem.innerHTML = `
|
||
<div class="track-item-title">${track.title}</div>
|
||
<div class="track-item-artist">${track.artist}</div>
|
||
`;
|
||
|
||
trackItem.addEventListener('dblclick', () => {
|
||
if (djInterface.currentDeck) {
|
||
loadTrackToPlayer(djInterface.currentDeck, track);
|
||
showNotification(`${track.title} loaded to Deck ${djInterface.currentDeck}`, 'success');
|
||
}
|
||
});
|
||
|
||
trackList.appendChild(trackItem);
|
||
});
|
||
}
|
||
|
||
// Keyboard Shortcuts
|
||
function handleKeyboardShortcuts(event) {
|
||
if (event.target.tagName === 'INPUT') return;
|
||
|
||
switch(event.code) {
|
||
case 'Space':
|
||
event.preventDefault();
|
||
if (djInterface.currentDeck) {
|
||
togglePlay(djInterface.currentDeck);
|
||
}
|
||
break;
|
||
case 'KeyQ':
|
||
togglePlay('A');
|
||
break;
|
||
case 'KeyW':
|
||
togglePlay('B');
|
||
break;
|
||
case 'KeyA':
|
||
cue('A');
|
||
break;
|
||
case 'KeyS':
|
||
cue('B');
|
||
break;
|
||
case 'KeyZ':
|
||
adjustCrossfader(0); // Full A
|
||
break;
|
||
case 'KeyX':
|
||
adjustCrossfader(50); // Center
|
||
break;
|
||
case 'KeyC':
|
||
adjustCrossfader(100); // Full B
|
||
break;
|
||
case 'KeyR':
|
||
toggleRecording();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Animations
|
||
function startAnimations() {
|
||
// Vinyl spinning animation is handled by CSS
|
||
|
||
// Playhead animation
|
||
setInterval(() => {
|
||
['A', 'B'].forEach(deck => {
|
||
const deckData = djInterface.decks[deck];
|
||
if (deckData.isPlaying && deckData.track) {
|
||
updatePlayhead(deck);
|
||
}
|
||
});
|
||
}, 100);
|
||
}
|
||
|
||
function updatePlayhead(deck) {
|
||
const playhead = document.getElementById(`playhead-${deck.toLowerCase()}`);
|
||
if (!playhead) return;
|
||
|
||
const currentTime = getCurrentTime(deck);
|
||
const duration = getDuration(deck);
|
||
|
||
if (duration > 0) {
|
||
const progress = (currentTime / duration) * 100;
|
||
playhead.style.left = `${progress}%`;
|
||
}
|
||
}
|
||
|
||
// Modal Functions
|
||
function closeTrackLoader() {
|
||
document.getElementById('track-loader').classList.add('hidden');
|
||
|
||
// Clear form
|
||
document.getElementById('track-title').value = '';
|
||
document.getElementById('track-artist').value = '';
|
||
document.getElementById('track-url').value = '';
|
||
}
|
||
|
||
function ejectDeck(deck) {
|
||
const deckData = djInterface.decks[deck];
|
||
|
||
if (deckData.isPlaying) {
|
||
togglePlay(deck);
|
||
}
|
||
|
||
// Clear deck
|
||
deckData.track = null;
|
||
deckData.cuePoint = 0;
|
||
|
||
// Clear UI
|
||
document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = 'NO TRACK';
|
||
document.getElementById(`title-${deck.toLowerCase()}`).textContent = 'No Track Loaded';
|
||
document.getElementById(`artist-${deck.toLowerCase()}`).textContent = '-';
|
||
document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent = '00:00';
|
||
document.getElementById(`time-total-${deck.toLowerCase()}`).textContent = '00:00';
|
||
|
||
// Clear waveform
|
||
const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`);
|
||
drawEmptyWaveform(canvas);
|
||
|
||
showNotification(`Deck ${deck} ejected`, 'info');
|
||
}
|
||
|
||
// Initialisiere Interface-Einstellungen
|
||
function initializeInterfaceSettings() {
|
||
console.log('Initializing interface settings');
|
||
|
||
// Setze Standardwerte
|
||
interfacePosition = {
|
||
x: (window.innerWidth - 1000) / 2,
|
||
y: (window.innerHeight - 700) / 2
|
||
};
|
||
|
||
interfaceSize = { width: 1000, height: 700 };
|
||
|
||
// Versuche gespeicherte Einstellungen zu laden
|
||
try {
|
||
const savedPosition = localStorage.getItem('djInterfacePosition');
|
||
const savedSize = localStorage.getItem('djInterfaceSize');
|
||
|
||
if (savedPosition) {
|
||
const parsedPosition = JSON.parse(savedPosition);
|
||
// Prüfe ob die Position gültig ist
|
||
if (parsedPosition && typeof parsedPosition.x === 'number' && typeof parsedPosition.y === 'number') {
|
||
interfacePosition = parsedPosition;
|
||
}
|
||
}
|
||
|
||
if (savedSize) {
|
||
const parsedSize = JSON.parse(savedSize);
|
||
// Prüfe ob die Größe gültig ist
|
||
if (parsedSize && typeof parsedSize.width === 'number' && typeof parsedSize.height === 'number') {
|
||
interfaceSize = parsedSize;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('DJ System: Error loading saved settings', e);
|
||
// Lösche ungültige Einstellungen
|
||
localStorage.removeItem('djInterfacePosition');
|
||
localStorage.removeItem('djInterfaceSize');
|
||
}
|
||
|
||
// Wende Einstellungen an
|
||
applyInterfaceSettings();
|
||
}
|
||
|
||
|
||
function applyInterfaceSettings() {
|
||
const djInterface = document.getElementById('dj-interface');
|
||
if (!djInterface) return;
|
||
|
||
// Setze Position
|
||
djInterface.style.position = 'absolute';
|
||
djInterface.style.left = interfacePosition.x + 'px';
|
||
djInterface.style.top = interfacePosition.y + 'px';
|
||
|
||
// Setze Größe
|
||
djInterface.style.width = interfaceSize.width + 'px';
|
||
djInterface.style.height = interfaceSize.height + 'px';
|
||
|
||
// Speichere Einstellungen
|
||
localStorage.setItem('djInterfacePosition', JSON.stringify(interfacePosition));
|
||
localStorage.setItem('djInterfaceSize', JSON.stringify(interfaceSize));
|
||
}
|
||
|
||
function setupInterfaceControls() {
|
||
const djInterfaceElement = document.getElementById('dj-interface');
|
||
const header = document.querySelector('.dj-header');
|
||
const resizeHandle = document.createElement('div');
|
||
|
||
// Verbesserte Schließen-Funktion
|
||
const closeBtn = document.querySelector('.close-btn');
|
||
if (closeBtn) {
|
||
closeBtn.addEventListener('click', function(e) {
|
||
// Nur Interface schließen, Musik weiterlaufen lassen
|
||
closeDJInterface();
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
});
|
||
}
|
||
|
||
|
||
// Erstelle Resize-Handle
|
||
resizeHandle.className = 'resize-handle';
|
||
resizeHandle.innerHTML = '<i class="fas fa-grip-lines-diagonal"></i>';
|
||
resizeHandle.style.cssText = `
|
||
position: absolute;
|
||
bottom: 0;
|
||
right: 0;
|
||
width: 20px;
|
||
height: 20px;
|
||
cursor: nwse-resize;
|
||
color: rgba(255, 255, 255, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
`;
|
||
djInterfaceElement.appendChild(resizeHandle);
|
||
|
||
// Header Drag-Funktionalität
|
||
if (header) {
|
||
header.style.cursor = 'move';
|
||
|
||
header.addEventListener('mousedown', function(e) {
|
||
// Ignoriere Klicks auf den Schließen-Button
|
||
if (e.target.closest('.close-btn')) return;
|
||
|
||
isDragging = true;
|
||
dragStartX = e.clientX - interfacePosition.x;
|
||
dragStartY = e.clientY - interfacePosition.y;
|
||
|
||
document.addEventListener('mousemove', handleDrag);
|
||
document.addEventListener('mouseup', stopDrag);
|
||
});
|
||
}
|
||
|
||
// Resize-Funktionalität
|
||
resizeHandle.addEventListener('mousedown', function(e) {
|
||
isResizing = true;
|
||
resizeStartWidth = interfaceSize.width;
|
||
resizeStartHeight = interfaceSize.height;
|
||
dragStartX = e.clientX;
|
||
dragStartY = e.clientY;
|
||
|
||
document.addEventListener('mousemove', handleResize);
|
||
document.addEventListener('mouseup', stopResize);
|
||
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
});
|
||
|
||
// Verbesserte Schließen-Funktion
|
||
// Korrigierte Schreibweise: close-btn ist die CSS-Klasse
|
||
const closeButton = document.querySelector('.close-btn');
|
||
if (closeButton) {
|
||
closeButton.addEventListener('click', function(e) {
|
||
closeDJInterface();
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
});
|
||
}
|
||
}
|
||
function handleDrag(e) {
|
||
if (!isDragging) return;
|
||
|
||
interfacePosition.x = e.clientX - dragStartX;
|
||
interfacePosition.y = e.clientY - dragStartY;
|
||
|
||
// Begrenze Position auf Bildschirm
|
||
interfacePosition.x = Math.max(0, Math.min(window.innerWidth - interfaceSize.width, interfacePosition.x));
|
||
interfacePosition.y = Math.max(0, Math.min(window.innerHeight - interfaceSize.height, interfacePosition.y));
|
||
|
||
const djInterfaceElement = document.getElementById('dj-interface');
|
||
if (djInterfaceElement) {
|
||
djInterfaceElement.style.left = interfacePosition.x + 'px';
|
||
djInterfaceElement.style.top = interfacePosition.y + 'px';
|
||
}
|
||
}
|
||
|
||
function stopDrag() {
|
||
if (isDragging) {
|
||
isDragging = false;
|
||
document.removeEventListener('mousemove', handleDrag);
|
||
document.removeEventListener('mouseup', stopDrag);
|
||
|
||
// Speichere Position
|
||
localStorage.setItem('djInterfacePosition', JSON.stringify(interfacePosition));
|
||
}
|
||
}
|
||
|
||
function handleResize(e) {
|
||
if (!isResizing) return;
|
||
|
||
const deltaX = e.clientX - dragStartX;
|
||
const deltaY = e.clientY - dragStartY;
|
||
|
||
interfaceSize.width = Math.max(800, resizeStartWidth + deltaX);
|
||
interfaceSize.height = Math.max(600, resizeStartHeight + deltaY);
|
||
|
||
const djInterfaceElement = document.getElementById('dj-interface');
|
||
if (djInterfaceElement) {
|
||
djInterfaceElement.style.width = interfaceSize.width + 'px';
|
||
djInterfaceElement.style.height = interfaceSize.height + 'px';
|
||
}
|
||
}
|
||
|
||
function stopResize() {
|
||
if (isResizing) {
|
||
isResizing = false;
|
||
document.removeEventListener('mousemove', handleResize);
|
||
document.removeEventListener('mouseup', stopResize);
|
||
|
||
// Speichere Größe
|
||
localStorage.setItem('djInterfaceSize', JSON.stringify(interfaceSize));
|
||
}
|
||
}
|
||
|
||
function closeDJInterface() {
|
||
|
||
|
||
// Entferne Event-Listener
|
||
document.removeEventListener('mousemove', handleDrag);
|
||
document.removeEventListener('mouseup', stopDrag);
|
||
document.removeEventListener('mousemove', handleResize);
|
||
document.removeEventListener('mouseup', stopResize);
|
||
|
||
// Verstecke Interface
|
||
const djInterfaceElement = document.getElementById('dj-interface');
|
||
if (djInterfaceElement) {
|
||
djInterfaceElement.classList.add('hidden');
|
||
}
|
||
|
||
// Benachrichtige FiveM
|
||
notifyFiveM('djInterfaceClosed', {
|
||
stopMusic: false // Wichtig: Teile FiveM mit, dass die Musik weiterlaufen soll
|
||
});
|
||
|
||
console.log('DJ System: Interface closed, music continues playing');
|
||
}
|
||
|
||
function stopAllAudio() {
|
||
// Stoppe YouTube-Player
|
||
for (const deck of ['A', 'B']) {
|
||
if (djInterface.decks[deck].youtubePlayer) {
|
||
try {
|
||
djInterface.decks[deck].youtubePlayer.stopVideo();
|
||
} catch (e) {
|
||
console.error(`DJ System: Error stopping YouTube player for Deck ${deck}`, e);
|
||
}
|
||
}
|
||
|
||
// Stoppe Audio-Player
|
||
if (djInterface.decks[deck].player) {
|
||
try {
|
||
djInterface.decks[deck].player.pause();
|
||
djInterface.decks[deck].player.currentTime = 0;
|
||
} catch (e) {
|
||
console.error(`DJ System: Error stopping audio player for Deck ${deck}`, e);
|
||
}
|
||
}
|
||
|
||
// Reset Deck-Status
|
||
djInterface.decks[deck].isPlaying = false;
|
||
}
|
||
}
|
||
|
||
function showDJInterface() {
|
||
const djInterfaceElement = document.getElementById('dj-interface');
|
||
if (djInterfaceElement) {
|
||
djInterfaceElement.classList.remove('hidden');
|
||
|
||
// Position weiter rechts und unten
|
||
if (!localStorage.getItem('djInterfacePosition')) {
|
||
interfacePosition = {
|
||
x: (window.innerWidth - interfaceSize.width) / 2 + 200, // 200px weiter rechts
|
||
y: (window.innerHeight - interfaceSize.height) / 2 + 150 // 150px weiter unten
|
||
};
|
||
}
|
||
|
||
// Wende gespeicherte Einstellungen an
|
||
applyInterfaceSettings();
|
||
}
|
||
}
|
||
|
||
|
||
// Aktualisiere den Message Handler
|
||
window.addEventListener('message', function(event) {
|
||
const data = event.data;
|
||
|
||
switch(data.type) {
|
||
case 'showDJInterface':
|
||
if (data.center) {
|
||
// Zentriere das Interface, aber weiter rechts und unten
|
||
interfacePosition = {
|
||
x: (window.innerWidth - interfaceSize.width) / 2 + 200, // 200px weiter rechts
|
||
y: (window.innerHeight - interfaceSize.height) / 2 + 150 // 150px weiter unten
|
||
};
|
||
}
|
||
|
||
if (data.reset) {
|
||
// Setze Größe zurück
|
||
interfaceSize = { width: 1000, height: 700 };
|
||
}
|
||
|
||
showDJInterface();
|
||
break;
|
||
// ... andere cases ...
|
||
}
|
||
});
|
||
|
||
|
||
|
||
// Utility Functions
|
||
function showNotification(message, type = 'info') {
|
||
console.log(`DJ System [${type.toUpperCase()}]: ${message}`);
|
||
|
||
// Create notification element
|
||
const notification = document.createElement('div');
|
||
notification.className = `notification notification-${type}`;
|
||
notification.textContent = message;
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 80px;
|
||
right: 20px;
|
||
padding: 15px 20px;
|
||
border-radius: 10px;
|
||
color: white;
|
||
font-weight: 600;
|
||
z-index: 10000;
|
||
transform: translateX(100%);
|
||
transition: transform 0.3s ease;
|
||
background: ${type === 'error' ? '#ff6b6b' : type === 'success' ? '#4ecdc4' : type === 'warning' ? '#feca57' : '#45b7d1'};
|
||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
||
`;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// Animate in
|
||
setTimeout(() => {
|
||
notification.style.transform = 'translateX(0)';
|
||
}, 100);
|
||
|
||
// Remove after 3 seconds
|
||
setTimeout(() => {
|
||
notification.style.transform = 'translateX(100%)';
|
||
setTimeout(() => {
|
||
document.body.removeChild(notification);
|
||
}, 300);
|
||
}, 3000);
|
||
}
|
||
|
||
function handleResize() {
|
||
// Handle responsive design
|
||
const djInterfaceElement = document.getElementById('dj-interface');
|
||
if (window.innerWidth < 1200) {
|
||
djInterfaceElement.classList.add('mobile-layout');
|
||
} else {
|
||
djInterfaceElement.classList.remove('mobile-layout');
|
||
}
|
||
}
|
||
|
||
// YouTube Integration
|
||
function isYouTubeUrl(url) {
|
||
const youtubePatterns = [
|
||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
||
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
||
];
|
||
|
||
return youtubePatterns.some(pattern => pattern.test(url));
|
||
}
|
||
|
||
function extractYouTubeVideoId(url) {
|
||
const patterns = [
|
||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
||
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
||
];
|
||
|
||
for (let pattern of patterns) {
|
||
const match = url.match(pattern);
|
||
if (match && match[1]) {
|
||
return match[1];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// Verbesserte notifyFiveM-Funktion mit Callback-Unterstützung
|
||
function notifyFiveM(event, data, callback) {
|
||
const callbackId = callback ? Date.now().toString() + Math.random().toString(36).substr(2, 5) : null;
|
||
|
||
if (callback) {
|
||
window.callbacks = window.callbacks || {};
|
||
window.callbacks[callbackId] = callback;
|
||
}
|
||
|
||
fetch(`https://${GetParentResourceName()}/${event}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
...data,
|
||
callbackId: callbackId
|
||
})
|
||
}).catch(err => {
|
||
console.error('DJ System: Failed to notify FiveM:', err);
|
||
if (callback) {
|
||
delete window.callbacks[callbackId];
|
||
}
|
||
});
|
||
}
|
||
|
||
// Callback-Handler
|
||
window.handleCallback = function(callbackId, data) {
|
||
if (window.callbacks && window.callbacks[callbackId]) {
|
||
window.callbacks[callbackId](data);
|
||
delete window.callbacks[callbackId];
|
||
}
|
||
};
|
||
|
||
|
||
// Message Handler for FiveM
|
||
window.addEventListener('message', function(event) {
|
||
const data = event.data;
|
||
|
||
switch(data.type) {
|
||
case 'showDJInterface':
|
||
showDJInterface();
|
||
break;
|
||
case 'hideDJInterface':
|
||
closeDJInterface();
|
||
break;
|
||
case 'loadTrack':
|
||
if (data.deck && data.track) {
|
||
loadTrackToPlayer(data.deck, data.track);
|
||
}
|
||
break;
|
||
case 'setVolume':
|
||
if (data.deck && data.volume !== undefined) {
|
||
adjustVolume(data.deck, data.volume);
|
||
}
|
||
break;
|
||
case 'playMusic':
|
||
playMusic(data.url, data.volume, data.title);
|
||
break;
|
||
case 'stopMusic':
|
||
stopMusic();
|
||
break;
|
||
}
|
||
});
|
||
|
||
// Direct music playback for FiveM integration
|
||
function playMusic(url, volume, title) {
|
||
// This function is used when FiveM sends a direct playMusic command
|
||
// It will play through the main audio system, not through the decks
|
||
|
||
console.log('DJ System: Playing direct music:', title, url);
|
||
|
||
// If YouTube URL, load into deck A
|
||
if (isYouTubeUrl(url)) {
|
||
const videoId = extractYouTubeVideoId(url);
|
||
if (videoId) {
|
||
// Create track data
|
||
const trackData = {
|
||
title: title || 'Unknown Track',
|
||
artist: '',
|
||
url: url
|
||
};
|
||
|
||
// Load into deck A and play
|
||
loadTrackToPlayer('A', trackData);
|
||
|
||
// Set volume
|
||
adjustVolume('A', volume || 50);
|
||
|
||
// Play
|
||
if (!djInterface.decks.A.isPlaying) {
|
||
togglePlay('A');
|
||
}
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
// For direct audio URLs, use a separate audio element
|
||
const audioElement = document.createElement('audio');
|
||
audioElement.src = url;
|
||
audioElement.volume = (volume || 50) / 100;
|
||
|
||
audioElement.addEventListener('canplay', () => {
|
||
audioElement.play().catch(e => {
|
||
console.error('DJ System: Error playing direct audio:', e);
|
||
showNotification('Error playing audio', 'error');
|
||
});
|
||
});
|
||
|
||
audioElement.addEventListener('error', (e) => {
|
||
console.error('DJ System: Direct audio error:', e);
|
||
showNotification('Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error'), 'error');
|
||
});
|
||
|
||
// Store for later reference
|
||
window.currentDirectAudio = audioElement;
|
||
}
|
||
|
||
function stopMusic() {
|
||
// Stop any direct audio
|
||
if (window.currentDirectAudio) {
|
||
window.currentDirectAudio.pause();
|
||
window.currentDirectAudio.src = '';
|
||
window.currentDirectAudio = null;
|
||
}
|
||
|
||
// Also stop deck A if it's playing
|
||
if (djInterface.decks.A.isPlaying) {
|
||
togglePlay('A');
|
||
}
|
||
|
||
// And deck B
|
||
if (djInterface.decks.B.isPlaying) {
|
||
togglePlay('B');
|
||
}
|
||
}
|
||
|
||
function setVolume(volume) {
|
||
// Set volume for direct audio
|
||
if (window.currentDirectAudio) {
|
||
window.currentDirectAudio.volume = volume / 100;
|
||
}
|
||
|
||
// Also set master volume
|
||
adjustMasterVolume(volume);
|
||
}
|
||
|
||
// Initialize search with default tracks
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
setTimeout(() => {
|
||
searchTracks(''); // Load default tracks
|
||
}, 1000);
|
||
});
|
||
|
||
console.log('DJ System: Professional interface loaded! 🎛️');
|
||
console.log('Keyboard shortcuts:');
|
||
console.log('Q/W - Play/Pause Deck A/B');
|
||
console.log('A/S - Cue Deck A/B');
|
||
console.log('Z/X/C - Crossfader positions');
|
||
console.log('R - Toggle recording');
|
||
console.log('Space - Play/Pause current deck');
|
||
|
||
// In script.js - Separate Funktion zum Stoppen der Musik
|
||
function stopAllAudioAndCloseInterface() {
|
||
// Stoppe YouTube-Player
|
||
for (const deck of ['A', 'B']) {
|
||
if (djInterface.decks[deck].youtubePlayer) {
|
||
try {
|
||
djInterface.decks[deck].youtubePlayer.stopVideo();
|
||
} catch (e) {
|
||
console.error(`DJ System: Error stopping YouTube player for Deck ${deck}`, e);
|
||
}
|
||
}
|
||
|
||
// Stoppe Audio-Player
|
||
if (djInterface.decks[deck].player) {
|
||
try {
|
||
djInterface.decks[deck].player.pause();
|
||
djInterface.decks[deck].player.currentTime = 0;
|
||
} catch (e) {
|
||
console.error(`DJ System: Error stopping audio player for Deck ${deck}`, e);
|
||
}
|
||
}
|
||
|
||
// Reset Deck-Status
|
||
djInterface.decks[deck].isPlaying = false;
|
||
}
|
||
|
||
// Schließe Interface
|
||
closeDJInterface();
|
||
|
||
// Benachrichtige FiveM, dass die Musik gestoppt werden soll
|
||
notifyFiveM('djInterfaceClosed', {
|
||
stopMusic: true
|
||
});
|
||
}
|
||
|
||
|
||
// Playlist-Verwaltung
|
||
let playlists = [];
|
||
let currentPlaylist = null;
|
||
let currentPlaylistIndex = 0;
|
||
|
||
// Playlist laden
|
||
function loadPlaylists() {
|
||
// Lade Playlists vom Server
|
||
notifyFiveM('getPlaylists', {}, function(response) {
|
||
if (response.success && response.playlists) {
|
||
playlists = response.playlists;
|
||
updatePlaylistDisplay();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Playlist-Anzeige aktualisieren
|
||
function updatePlaylistDisplay() {
|
||
const playlistContainer = document.getElementById('playlist-container');
|
||
if (!playlistContainer) return;
|
||
|
||
playlistContainer.innerHTML = '';
|
||
|
||
if (playlists.length === 0) {
|
||
playlistContainer.innerHTML = '<div class="no-playlists">Keine Playlists gefunden</div>';
|
||
return;
|
||
}
|
||
|
||
// Erstelle Playlist-Elemente
|
||
playlists.forEach((playlist, index) => {
|
||
const playlistElement = document.createElement('div');
|
||
playlistElement.className = 'playlist-item';
|
||
if (currentPlaylist && currentPlaylist.id === playlist.id) {
|
||
playlistElement.classList.add('active');
|
||
}
|
||
|
||
playlistElement.innerHTML = `
|
||
<div class="playlist-header">
|
||
<span class="playlist-name">${playlist.name}</span>
|
||
<span class="playlist-count">${playlist.songs.length} Songs</span>
|
||
</div>
|
||
<div class="playlist-actions">
|
||
<button class="playlist-play-btn" title="Play"><i class="fas fa-play"></i></button>
|
||
<button class="playlist-edit-btn" title="Edit"><i class="fas fa-edit"></i></button>
|
||
${playlist.isOwner ? '<button class="playlist-delete-btn" title="Delete"><i class="fas fa-trash"></i></button>' : ''}
|
||
</div>
|
||
`;
|
||
|
||
// Event-Listener für Playlist-Aktionen
|
||
const playBtn = playlistElement.querySelector('.playlist-play-btn');
|
||
if (playBtn) {
|
||
playBtn.addEventListener('click', () => playPlaylist(playlist));
|
||
}
|
||
|
||
const editBtn = playlistElement.querySelector('.playlist-edit-btn');
|
||
if (editBtn) {
|
||
editBtn.addEventListener('click', () => editPlaylist(playlist));
|
||
}
|
||
|
||
const deleteBtn = playlistElement.querySelector('.playlist-delete-btn');
|
||
if (deleteBtn) {
|
||
deleteBtn.addEventListener('click', () => deletePlaylist(playlist));
|
||
}
|
||
|
||
playlistContainer.appendChild(playlistElement);
|
||
});
|
||
}
|
||
|
||
// Playlist abspielen
|
||
function playPlaylist(playlist) {
|
||
if (!playlist || !playlist.songs || playlist.songs.length === 0) {
|
||
showNotification('Diese Playlist enthält keine Songs', 'warning');
|
||
return;
|
||
}
|
||
|
||
currentPlaylist = playlist;
|
||
currentPlaylistIndex = 0;
|
||
|
||
// Ersten Song abspielen
|
||
playPlaylistSong(currentPlaylistIndex);
|
||
}
|
||
|
||
// Playlist-Song abspielen
|
||
function playPlaylistSong(index) {
|
||
if (!currentPlaylist || !currentPlaylist.songs || index >= currentPlaylist.songs.length) {
|
||
showNotification('Playlist beendet', 'info');
|
||
currentPlaylist = null;
|
||
return;
|
||
}
|
||
|
||
const song = currentPlaylist.songs[index];
|
||
|
||
// Lade Song in Deck A
|
||
const trackData = {
|
||
title: song.title,
|
||
artist: song.artist || '',
|
||
url: song.url
|
||
};
|
||
|
||
loadTrackToPlayer('A', trackData);
|
||
|
||
// Starte Wiedergabe
|
||
if (!djInterface.decks.A.isPlaying) {
|
||
togglePlay('A');
|
||
}
|
||
|
||
showNotification(`Playlist: ${currentPlaylist.name} - Song ${index + 1}/${currentPlaylist.songs.length}`, 'info');
|
||
}
|
||
|
||
// Nächster Song in Playlist
|
||
function playNextSong() {
|
||
if (!currentPlaylist) return;
|
||
|
||
currentPlaylistIndex++;
|
||
if (currentPlaylistIndex < currentPlaylist.songs.length) {
|
||
playPlaylistSong(currentPlaylistIndex);
|
||
} else {
|
||
showNotification('Playlist beendet', 'info');
|
||
currentPlaylist = null;
|
||
}
|
||
}
|
||
|
||
// Playlist bearbeiten
|
||
function editPlaylist(playlist) {
|
||
openPlaylistEditor(playlist);
|
||
}
|
||
|
||
// Playlist löschen
|
||
function deletePlaylist(playlist) {
|
||
if (confirm(`Möchtest du die Playlist "${playlist.name}" wirklich löschen?`)) {
|
||
notifyFiveM('deletePlaylist', { playlistId: playlist.id }, function(response) {
|
||
if (response.success) {
|
||
showNotification('Playlist gelöscht', 'success');
|
||
loadPlaylists(); // Playlists neu laden
|
||
} else {
|
||
showNotification('Fehler beim Löschen der Playlist', 'error');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Neue Playlist erstellen
|
||
function createNewPlaylist() {
|
||
const playlistName = prompt('Name der neuen Playlist:');
|
||
if (!playlistName) return;
|
||
|
||
notifyFiveM('createPlaylist', {
|
||
name: playlistName,
|
||
isPublic: confirm('Soll die Playlist öffentlich sein?')
|
||
}, function(response) {
|
||
if (response.success) {
|
||
showNotification('Playlist erstellt', 'success');
|
||
loadPlaylists(); // Playlists neu laden
|
||
} else {
|
||
showNotification('Fehler beim Erstellen der Playlist', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Song zu Playlist hinzufügen
|
||
function addSongToPlaylist(song, playlistId) {
|
||
notifyFiveM('addToPlaylist', {
|
||
playlistId: playlistId,
|
||
track: song
|
||
}, function(response) {
|
||
if (response.success) {
|
||
showNotification('Song zur Playlist hinzugefügt', 'success');
|
||
loadPlaylists(); // Playlists neu laden
|
||
} else {
|
||
showNotification('Fehler beim Hinzufügen des Songs', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Playlist-Editor öffnen
|
||
function openPlaylistEditor(playlist) {
|
||
// Implementiere einen Playlist-Editor
|
||
// Dies könnte ein Modal sein, in dem Songs hinzugefügt/entfernt werden können
|
||
console.log('Playlist Editor für:', playlist);
|
||
|
||
// Beispiel für ein einfaches Modal
|
||
const modal = document.createElement('div');
|
||
modal.className = 'modal';
|
||
modal.innerHTML = `
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Playlist bearbeiten: ${playlist.name}</h3>
|
||
<button class="close-modal">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="playlist-songs">
|
||
${playlist.songs.map((song, idx) => `
|
||
<div class="playlist-song">
|
||
<span>${idx + 1}. ${song.title}</span>
|
||
<button class="remove-song" data-index="${idx}">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
<div class="add-song-form">
|
||
<h4>Song hinzufügen</h4>
|
||
<div class="input-group">
|
||
<input type="text" id="song-title" placeholder="Titel">
|
||
</div>
|
||
<div class="input-group">
|
||
<input type="text" id="song-artist" placeholder="Künstler (optional)">
|
||
</div>
|
||
<div class="input-group">
|
||
<input type="text" id="song-url" placeholder="URL (YouTube oder direkt)">
|
||
</div>
|
||
<button id="add-song-btn">Song hinzufügen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(modal);
|
||
|
||
// Event-Listener für Modal
|
||
const closeBtn = modal.querySelector('.close-modal');
|
||
if (closeBtn) {
|
||
closeBtn.addEventListener('click', () => {
|
||
document.body.removeChild(modal);
|
||
});
|
||
}
|
||
|
||
// Event-Listener für Song-Entfernung
|
||
const removeBtns = modal.querySelectorAll('.remove-song');
|
||
removeBtns.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const index = parseInt(btn.dataset.index);
|
||
removeSongFromPlaylist(playlist.id, playlist.songs[index].id);
|
||
});
|
||
});
|
||
|
||
// Event-Listener für Song-Hinzufügung
|
||
const addBtn = modal.querySelector('#add-song-btn');
|
||
if (addBtn) {
|
||
addBtn.addEventListener('click', () => {
|
||
const title = modal.querySelector('#song-title').value;
|
||
const artist = modal.querySelector('#song-artist').value;
|
||
const url = modal.querySelector('#song-url').value;
|
||
|
||
if (!title || !url) {
|
||
showNotification('Titel und URL sind erforderlich', 'error');
|
||
return;
|
||
}
|
||
|
||
addSongToPlaylist({
|
||
title: title,
|
||
artist: artist,
|
||
url: url
|
||
}, playlist.id);
|
||
|
||
// Modal schließen
|
||
document.body.removeChild(modal);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Song aus Playlist entfernen
|
||
function removeSongFromPlaylist(playlistId, songId) {
|
||
notifyFiveM('removeSongFromPlaylist', {
|
||
playlistId: playlistId,
|
||
songId: songId
|
||
}, function(response) {
|
||
if (response.success) {
|
||
showNotification('Song aus Playlist entfernt', 'success');
|
||
loadPlaylists(); // Playlists neu laden
|
||
} else {
|
||
showNotification('Fehler beim Entfernen des Songs', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Event-Listener für Song-Ende (für Playlist-Fortsetzung)
|
||
function setupPlaylistEventListeners() {
|
||
// Wenn ein Song endet, spiele den nächsten in der Playlist
|
||
document.addEventListener('songEnded', function(e) {
|
||
if (currentPlaylist) {
|
||
playNextSong();
|
||
}
|
||
});
|
||
}
|