1
0
Fork 0
forked from Simnation/Main
Main/resources/[tools]/nordi_dj/html/script.js
2025-08-03 19:35:29 +02:00

1967 lines
60 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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() {
console.log('DJ System: Closing interface...');
// Entferne Event-Listener für Drag und Resize
document.removeEventListener('mousemove', handleDrag);
document.removeEventListener('mouseup', stopDrag);
document.removeEventListener('mousemove', handleResize);
document.removeEventListener('mouseup', stopResize);
// Verstecke Interface
const djInterfaceElement = document.getElementById('dj-interface');
if (djInterfaceElement) {
djInterfaceElement.classList.add('hidden');
}
// Wichtig: Benachrichtige FiveM BEVOR wir irgendwelche lokalen Variablen zurücksetzen
notifyFiveM('djInterfaceClosed', {
stopMusic: false // Musik weiterlaufen lassen
});
// Setze Drag & Resize Status zurück
isDragging = false;
isResizing = false;
console.log('DJ System: Interface closed, music continues playing');
}
function stopAllAudio() {
// Stoppe YouTube-Player
for (const deck of ['A', 'B']) {
if (djInterface.decks[deck].youtubePlayer) {
try {
djInterface.decks[deck].youtubePlayer.stopVideo();
} catch (e) {
console.error(`DJ System: Error stopping YouTube player for Deck ${deck}`, e);
}
}
// Stoppe Audio-Player
if (djInterface.decks[deck].player) {
try {
djInterface.decks[deck].player.pause();
djInterface.decks[deck].player.currentTime = 0;
} catch (e) {
console.error(`DJ System: Error stopping audio player for Deck ${deck}`, e);
}
}
// Reset Deck-Status
djInterface.decks[deck].isPlaying = false;
}
}
function showDJInterface() {
console.log('DJ System: Showing interface');
const djInterfaceElement = document.getElementById('dj-interface');
if (!djInterfaceElement) {
console.error('DJ System: Interface element not found!');
notifyFiveM('error', {error: 'Interface element not found'});
return;
}
// Entferne hidden-Klasse
djInterfaceElement.classList.remove('hidden');
// Position weiter rechts und unten, falls keine gespeicherte Position
if (!localStorage.getItem('djInterfacePosition')) {
interfacePosition = {
x: (window.innerWidth - interfaceSize.width) / 2,
y: (window.innerHeight - interfaceSize.height) / 2
};
}
// Wende gespeicherte Einstellungen an
applyInterfaceSettings();
console.log('DJ System: Interface shown');
}
// Aktualisiere den Message Handler
window.addEventListener('message', function(event) {
const data = event.data;
switch(data.type) {
case 'showDJInterface':
if (data.center) {
// Zentriere das Interface, aber weiter rechts und unten
interfacePosition = {
x: (window.innerWidth - interfaceSize.width) / 2 + 200, // 200px weiter rechts
y: (window.innerHeight - interfaceSize.height) / 2 + 150 // 150px weiter unten
};
}
if (data.reset) {
// Setze Größe zurück
interfaceSize = { width: 1000, height: 700 };
}
showDJInterface();
break;
// ... andere cases ...
}
});
// Utility Functions
function showNotification(message, type = 'info') {
console.log(`DJ System [${type.toUpperCase()}]: ${message}`);
// Create notification element
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
padding: 15px 20px;
border-radius: 10px;
color: white;
font-weight: 600;
z-index: 10000;
transform: translateX(100%);
transition: transform 0.3s ease;
background: ${type === 'error' ? '#ff6b6b' : type === 'success' ? '#4ecdc4' : type === 'warning' ? '#feca57' : '#45b7d1'};
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// Remove after 3 seconds
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
function handleResize() {
// Handle responsive design
const djInterfaceElement = document.getElementById('dj-interface');
if (window.innerWidth < 1200) {
djInterfaceElement.classList.add('mobile-layout');
} else {
djInterfaceElement.classList.remove('mobile-layout');
}
}
// YouTube Integration
function isYouTubeUrl(url) {
const youtubePatterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
];
return youtubePatterns.some(pattern => pattern.test(url));
}
function extractYouTubeVideoId(url) {
const patterns = [
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
];
for (let pattern of patterns) {
const match = url.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return null;
}
// Verbesserte notifyFiveM-Funktion mit Fehlerbehandlung
function notifyFiveM(event, data, callback) {
try {
const callbackId = callback ? Date.now().toString() + Math.random().toString(36).substr(2, 5) : null;
if (callback) {
window.callbacks = window.callbacks || {};
window.callbacks[callbackId] = callback;
}
const payload = {
...data,
callbackId: callbackId
};
console.log(`DJ System: Sending ${event} to FiveM`, payload);
fetch(`https://${GetParentResourceName()}/${event}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}).catch(err => {
console.error('DJ System: Failed to notify FiveM:', err);
if (callback) {
delete window.callbacks[callbackId];
callback({success: false, error: 'Failed to communicate with FiveM'});
}
});
} catch (error) {
console.error('DJ System: Error in notifyFiveM:', error);
if (callback) {
callback({success: false, error: 'Internal error'});
}
}
}
// Callback-Handler
window.handleCallback = function(callbackId, data) {
if (window.callbacks && window.callbacks[callbackId]) {
window.callbacks[callbackId](data);
delete window.callbacks[callbackId];
}
};
// Message Handler for FiveM
window.addEventListener('message', function(event) {
const data = event.data;
switch(data.type) {
case 'showDJInterface':
showDJInterface();
break;
case 'hideDJInterface':
closeDJInterface();
break;
case 'loadTrack':
if (data.deck && data.track) {
loadTrackToPlayer(data.deck, data.track);
}
break;
case 'setVolume':
if (data.deck && data.volume !== undefined) {
adjustVolume(data.deck, data.volume);
}
break;
case 'playMusic':
playMusic(data.url, data.volume, data.title);
break;
case 'stopMusic':
stopMusic();
break;
}
});
// Direct music playback for FiveM integration
function playMusic(url, volume, title) {
// This function is used when FiveM sends a direct playMusic command
// It will play through the main audio system, not through the decks
console.log('DJ System: Playing direct music:', title, url);
// If YouTube URL, load into deck A
if (isYouTubeUrl(url)) {
const videoId = extractYouTubeVideoId(url);
if (videoId) {
// Create track data
const trackData = {
title: title || 'Unknown Track',
artist: '',
url: url
};
// Load into deck A and play
loadTrackToPlayer('A', trackData);
// Set volume
adjustVolume('A', volume || 50);
// Play
if (!djInterface.decks.A.isPlaying) {
togglePlay('A');
}
return;
}
}
// For direct audio URLs, use a separate audio element
const audioElement = document.createElement('audio');
audioElement.src = url;
audioElement.volume = (volume || 50) / 100;
audioElement.addEventListener('canplay', () => {
audioElement.play().catch(e => {
console.error('DJ System: Error playing direct audio:', e);
showNotification('Error playing audio', 'error');
});
});
audioElement.addEventListener('error', (e) => {
console.error('DJ System: Direct audio error:', e);
showNotification('Audio error: ' + (e.target.error ? e.target.error.message : 'Unknown error'), 'error');
});
// Store for later reference
window.currentDirectAudio = audioElement;
}
function stopMusic() {
// Stop any direct audio
if (window.currentDirectAudio) {
window.currentDirectAudio.pause();
window.currentDirectAudio.src = '';
window.currentDirectAudio = null;
}
// Also stop deck A if it's playing
if (djInterface.decks.A.isPlaying) {
togglePlay('A');
}
// And deck B
if (djInterface.decks.B.isPlaying) {
togglePlay('B');
}
}
function setVolume(volume) {
// Set volume for direct audio
if (window.currentDirectAudio) {
window.currentDirectAudio.volume = volume / 100;
}
// Also set master volume
adjustMasterVolume(volume);
}
// Initialize search with default tracks
document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
searchTracks(''); // Load default tracks
}, 1000);
});
console.log('DJ System: Professional interface loaded! 🎛️');
console.log('Keyboard shortcuts:');
console.log('Q/W - Play/Pause Deck A/B');
console.log('A/S - Cue Deck A/B');
console.log('Z/X/C - Crossfader positions');
console.log('R - Toggle recording');
console.log('Space - Play/Pause current deck');
// In script.js - Separate Funktion zum Stoppen der Musik
function stopAllAudioAndCloseInterface() {
// Stoppe YouTube-Player
for (const deck of ['A', 'B']) {
if (djInterface.decks[deck].youtubePlayer) {
try {
djInterface.decks[deck].youtubePlayer.stopVideo();
} catch (e) {
console.error(`DJ System: Error stopping YouTube player for Deck ${deck}`, e);
}
}
// Stoppe Audio-Player
if (djInterface.decks[deck].player) {
try {
djInterface.decks[deck].player.pause();
djInterface.decks[deck].player.currentTime = 0;
} catch (e) {
console.error(`DJ System: Error stopping audio player for Deck ${deck}`, e);
}
}
// Reset Deck-Status
djInterface.decks[deck].isPlaying = false;
}
// Schließe Interface
closeDJInterface();
// Benachrichtige FiveM, dass die Musik gestoppt werden soll
notifyFiveM('djInterfaceClosed', {
stopMusic: true
});
}
// 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();
}
});
}