forked from Simnation/Main
1312 lines
37 KiB
JavaScript
1312 lines
37 KiB
JavaScript
let audioPlayer = null;
|
|
let currentVolume = 50;
|
|
let isPlaying = false;
|
|
let currentSong = null;
|
|
let fadeInterval = null;
|
|
|
|
// YouTube Player API laden
|
|
let youtubeAPIReady = false;
|
|
let youtubePlayer = null;
|
|
|
|
// Initialize when page loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadYouTubeAPI();
|
|
setupAudioPlayer();
|
|
});
|
|
|
|
// 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');
|
|
youtubeAPIReady = true;
|
|
|
|
// Erstelle versteckten YouTube Player
|
|
createYouTubePlayer();
|
|
};
|
|
|
|
// YouTube Player erstellen
|
|
function createYouTubePlayer() {
|
|
// Container für YouTube Player
|
|
const playerContainer = document.createElement('div');
|
|
playerContainer.id = 'youtube-player-container';
|
|
playerContainer.style.position = 'absolute';
|
|
playerContainer.style.top = '-9999px';
|
|
playerContainer.style.left = '-9999px';
|
|
playerContainer.style.width = '1px';
|
|
playerContainer.style.height = '1px';
|
|
playerContainer.style.opacity = '0';
|
|
document.body.appendChild(playerContainer);
|
|
|
|
youtubePlayer = new YT.Player('youtube-player-container', {
|
|
height: '1',
|
|
width: '1',
|
|
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,
|
|
'start': 0,
|
|
'origin': window.location.origin
|
|
},
|
|
events: {
|
|
'onReady': onYouTubePlayerReady,
|
|
'onStateChange': onYouTubePlayerStateChange,
|
|
'onError': onYouTubePlayerError
|
|
}
|
|
});
|
|
}
|
|
|
|
function onYouTubePlayerReady(event) {
|
|
console.log('DJ System: YouTube Player ready');
|
|
}
|
|
|
|
function onYouTubePlayerStateChange(event) {
|
|
switch(event.data) {
|
|
case YT.PlayerState.PLAYING:
|
|
console.log('DJ System: YouTube playing');
|
|
isPlaying = true;
|
|
break;
|
|
case YT.PlayerState.PAUSED:
|
|
console.log('DJ System: YouTube paused');
|
|
isPlaying = false;
|
|
break;
|
|
case YT.PlayerState.ENDED:
|
|
console.log('DJ System: YouTube ended');
|
|
isPlaying = false;
|
|
notifyFiveM('songEnded', {});
|
|
break;
|
|
case YT.PlayerState.BUFFERING:
|
|
console.log('DJ System: YouTube buffering');
|
|
break;
|
|
}
|
|
}
|
|
|
|
function onYouTubePlayerError(event) {
|
|
console.error('DJ System: YouTube error:', 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;
|
|
}
|
|
|
|
notifyFiveM('audioError', { error: errorMessage });
|
|
}
|
|
|
|
// Setup normaler Audio Player für direkte URLs
|
|
function setupAudioPlayer() {
|
|
audioPlayer = document.getElementById('audio-player');
|
|
if (!audioPlayer) {
|
|
audioPlayer = document.createElement('audio');
|
|
audioPlayer.id = 'audio-player';
|
|
audioPlayer.style.display = 'none';
|
|
document.body.appendChild(audioPlayer);
|
|
}
|
|
|
|
audioPlayer.addEventListener('play', () => {
|
|
isPlaying = true;
|
|
console.log('DJ System: Audio playing');
|
|
});
|
|
|
|
audioPlayer.addEventListener('pause', () => {
|
|
isPlaying = false;
|
|
console.log('DJ System: Audio paused');
|
|
});
|
|
|
|
audioPlayer.addEventListener('ended', () => {
|
|
isPlaying = false;
|
|
notifyFiveM('songEnded', {});
|
|
});
|
|
|
|
audioPlayer.addEventListener('error', (e) => {
|
|
console.error('DJ System: Audio error', e);
|
|
notifyFiveM('audioError', { error: 'Audio playback error' });
|
|
});
|
|
}
|
|
|
|
// Main message handler
|
|
window.addEventListener('message', function(event) {
|
|
const data = event.data;
|
|
|
|
switch(data.type) {
|
|
case 'playMusic':
|
|
playMusic(data.url, data.volume, data.title);
|
|
break;
|
|
case 'stopMusic':
|
|
stopMusic();
|
|
break;
|
|
case 'setVolume':
|
|
setVolume(data.volume);
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Musik abspielen - YouTube oder direkte URL
|
|
async function playMusic(url, volume, title = 'Unknown') {
|
|
try {
|
|
console.log('DJ System: Playing:', title, url);
|
|
|
|
// Stoppe aktuelle Musik
|
|
stopMusic();
|
|
|
|
// Setze Lautstärke
|
|
currentVolume = volume || 50;
|
|
|
|
// Prüfe ob YouTube URL
|
|
if (isYouTubeUrl(url)) {
|
|
await playYouTubeMusic(url, volume, title);
|
|
} else {
|
|
await playDirectMusic(url, volume, title);
|
|
}
|
|
|
|
// Speichere aktuelle Song Info
|
|
currentSong = {
|
|
title: title,
|
|
url: url,
|
|
volume: volume
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('DJ System: Error playing music:', error);
|
|
notifyFiveM('audioError', { error: error.message });
|
|
}
|
|
}
|
|
|
|
// YouTube Musik abspielen
|
|
async function playYouTubeMusic(url, volume, title) {
|
|
if (!youtubeAPIReady || !youtubePlayer) {
|
|
throw new Error('YouTube Player nicht bereit');
|
|
}
|
|
|
|
const videoId = extractYouTubeVideoId(url);
|
|
if (!videoId) {
|
|
throw new Error('Ungültige YouTube URL');
|
|
}
|
|
|
|
console.log('DJ System: Playing YouTube video:', videoId);
|
|
|
|
// Lade und spiele YouTube Video
|
|
youtubePlayer.loadVideoById({
|
|
videoId: videoId,
|
|
startSeconds: 0
|
|
});
|
|
|
|
// Setze Lautstärke
|
|
youtubePlayer.setVolume(volume || 50);
|
|
|
|
// Warte kurz und starte
|
|
setTimeout(() => {
|
|
youtubePlayer.playVideo();
|
|
}, 1000);
|
|
}
|
|
|
|
// Direkte Audio URL abspielen
|
|
async function playDirectMusic(url, volume, title) {
|
|
if (!audioPlayer) {
|
|
throw new Error('Audio Player nicht verfügbar');
|
|
}
|
|
|
|
console.log('DJ System: Playing direct audio:', url);
|
|
|
|
audioPlayer.src = url;
|
|
audioPlayer.volume = (volume || 50) / 100;
|
|
audioPlayer.load();
|
|
|
|
const playPromise = audioPlayer.play();
|
|
if (playPromise !== undefined) {
|
|
await playPromise;
|
|
}
|
|
}
|
|
|
|
// Musik stoppen
|
|
function stopMusic() {
|
|
try {
|
|
// Stoppe YouTube Player
|
|
if (youtubePlayer && youtubeAPIReady) {
|
|
youtubePlayer.stopVideo();
|
|
}
|
|
|
|
// Stoppe Audio Player
|
|
if (audioPlayer) {
|
|
audioPlayer.pause();
|
|
audioPlayer.currentTime = 0;
|
|
audioPlayer.src = '';
|
|
}
|
|
|
|
isPlaying = false;
|
|
currentSong = null;
|
|
|
|
console.log('DJ System: Music stopped');
|
|
|
|
} catch (error) {
|
|
console.error('DJ System: Error stopping music:', error);
|
|
}
|
|
}
|
|
|
|
// Musik pausieren
|
|
function pauseMusic() {
|
|
try {
|
|
if (currentSong && isYouTubeUrl(currentSong.url)) {
|
|
if (youtubePlayer && youtubeAPIReady) {
|
|
youtubePlayer.pauseVideo();
|
|
}
|
|
} else if (audioPlayer) {
|
|
audioPlayer.pause();
|
|
}
|
|
} catch (error) {
|
|
console.error('DJ System: Error pausing music:', error);
|
|
}
|
|
}
|
|
|
|
// Musik fortsetzen
|
|
function resumeMusic() {
|
|
try {
|
|
if (currentSong && isYouTubeUrl(currentSong.url)) {
|
|
if (youtubePlayer && youtubeAPIReady) {
|
|
youtubePlayer.playVideo();
|
|
}
|
|
} else if (audioPlayer) {
|
|
audioPlayer.play();
|
|
}
|
|
} catch (error) {
|
|
console.error('DJ System: Error resuming music:', error);
|
|
}
|
|
}
|
|
|
|
// Lautstärke setzen
|
|
function setVolume(volume) {
|
|
try {
|
|
currentVolume = Math.max(0, Math.min(100, volume));
|
|
|
|
if (currentSong && isYouTubeUrl(currentSong.url)) {
|
|
if (youtubePlayer && youtubeAPIReady) {
|
|
youtubePlayer.setVolume(currentVolume);
|
|
}
|
|
} else if (audioPlayer) {
|
|
audioPlayer.volume = currentVolume / 100;
|
|
}
|
|
|
|
console.log('DJ System: Volume set to', currentVolume + '%');
|
|
} catch (error) {
|
|
console.error('DJ System: Error setting volume:', error);
|
|
}
|
|
}
|
|
|
|
// YouTube URL prüfen
|
|
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));
|
|
}
|
|
|
|
// YouTube Video ID extrahieren
|
|
function extractYouTubeVideoId(url) {
|
|
const patterns = [
|
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
|
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
];
|
|
|
|
for (let pattern of patterns) {
|
|
const match = url.match(pattern);
|
|
if (match && match[1]) {
|
|
return match[1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// FiveM benachrichtigen
|
|
function notifyFiveM(event, data) {
|
|
fetch(`https://${GetParentResourceName()}/${event}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
}).catch(err => {
|
|
console.error('DJ System: Failed to notify FiveM:', err);
|
|
});
|
|
}
|
|
|
|
function GetParentResourceName() {
|
|
return window.location.hostname;
|
|
}
|
|
|
|
// Debug
|
|
window.djDebug = {
|
|
getCurrentSong: () => currentSong,
|
|
getVolume: () => currentVolume,
|
|
isPlaying: () => isPlaying,
|
|
youtubeReady: () => youtubeAPIReady,
|
|
testYouTube: (videoId) => playYouTubeMusic('https://www.youtube.com/watch?v=' + videoId, 50, 'Test')
|
|
};
|
|
|
|
console.log('DJ System: YouTube streaming system loaded');
|
|
|
|
// DJ System - Professional Interface
|
|
let djInterface = {
|
|
decks: {
|
|
A: {
|
|
track: null,
|
|
isPlaying: false,
|
|
volume: 75,
|
|
pitch: 0,
|
|
cuePoint: 0,
|
|
player: null
|
|
},
|
|
B: {
|
|
track: null,
|
|
isPlaying: false,
|
|
volume: 75,
|
|
pitch: 0,
|
|
cuePoint: 0,
|
|
player: null
|
|
}
|
|
},
|
|
mixer: {
|
|
crossfader: 50,
|
|
masterVolume: 80,
|
|
eq: {
|
|
A: { high: 0, mid: 0, low: 0 },
|
|
B: { high: 0, mid: 0, low: 0 }
|
|
}
|
|
},
|
|
effects: {
|
|
reverb: false,
|
|
delay: false,
|
|
filter: false,
|
|
flanger: false,
|
|
wetDry: 0
|
|
},
|
|
currentDeck: null,
|
|
isRecording: false
|
|
};
|
|
|
|
// Initialize DJ Interface
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeDJSystem();
|
|
setupEventListeners();
|
|
startAnimations();
|
|
loadYouTubeAPI();
|
|
});
|
|
|
|
function initializeDJSystem() {
|
|
console.log('DJ System: Initializing professional interface...');
|
|
|
|
// Initialize audio players
|
|
djInterface.decks.A.player = document.getElementById('audio-player-a');
|
|
djInterface.decks.B.player = document.getElementById('audio-player-b');
|
|
|
|
// Setup audio event listeners
|
|
setupAudioEventListeners();
|
|
|
|
// Initialize waveforms
|
|
initializeWaveforms();
|
|
|
|
// Start time display
|
|
updateTimeDisplay();
|
|
setInterval(updateTimeDisplay, 1000);
|
|
|
|
// Start VU meters animation
|
|
startVUMeters();
|
|
|
|
console.log('DJ System: Professional interface ready!');
|
|
}
|
|
|
|
function setupEventListeners() {
|
|
// Knob interactions
|
|
setupKnobControls();
|
|
|
|
// Keyboard shortcuts
|
|
document.addEventListener('keydown', handleKeyboardShortcuts);
|
|
|
|
// Window resize
|
|
window.addEventListener('resize', handleResize);
|
|
}
|
|
|
|
function setupKnobControls() {
|
|
const knobs = document.querySelectorAll('.knob');
|
|
|
|
knobs.forEach(knob => {
|
|
let isDragging = false;
|
|
let startY = 0;
|
|
let startRotation = 0;
|
|
|
|
knob.addEventListener('mousedown', (e) => {
|
|
isDragging = true;
|
|
startY = e.clientY;
|
|
startRotation = getCurrentRotation(knob.querySelector('.knob-indicator'));
|
|
document.body.style.cursor = 'grabbing';
|
|
e.preventDefault();
|
|
});
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
if (!isDragging) return;
|
|
|
|
const deltaY = startY - e.clientY;
|
|
const rotation = Math.max(-150, Math.min(150, startRotation + deltaY * 2));
|
|
|
|
const indicator = knob.querySelector('.knob-indicator');
|
|
indicator.style.transform = `translateX(-50%) rotate(${rotation}deg)`;
|
|
|
|
// Update EQ values
|
|
const channel = knob.dataset.channel;
|
|
const eqType = knob.dataset.eq;
|
|
const effect = knob.dataset.effect;
|
|
|
|
if (channel && eqType) {
|
|
const value = Math.round((rotation / 150) * 100);
|
|
djInterface.mixer.eq[channel][eqType] = value;
|
|
|
|
const valueSpan = knob.parentElement.querySelector('.eq-value');
|
|
if (valueSpan) {
|
|
valueSpan.textContent = value > 0 ? `+${value}` : value;
|
|
}
|
|
|
|
applyEQ(channel, eqType, value);
|
|
}
|
|
|
|
if (effect) {
|
|
const value = Math.round(((rotation + 150) / 300) * 100);
|
|
djInterface.effects[effect] = value;
|
|
applyEffect(effect, value);
|
|
}
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
isDragging = false;
|
|
document.body.style.cursor = 'default';
|
|
});
|
|
});
|
|
}
|
|
|
|
function getCurrentRotation(element) {
|
|
const transform = window.getComputedStyle(element).transform;
|
|
if (transform === 'none') return 0;
|
|
|
|
const matrix = transform.match(/matrix\(([^)]+)\)/);
|
|
if (!matrix) return 0;
|
|
|
|
const values = matrix[1].split(',').map(parseFloat);
|
|
const angle = Math.atan2(values[1], values[0]) * (180 / Math.PI);
|
|
return angle;
|
|
}
|
|
|
|
// Deck Functions
|
|
function loadTrackToDeck(deck) {
|
|
djInterface.currentDeck = deck;
|
|
document.getElementById('track-loader').classList.remove('hidden');
|
|
}
|
|
|
|
function confirmLoadTrack() {
|
|
const title = document.getElementById('track-title').value;
|
|
const artist = document.getElementById('track-artist').value;
|
|
const url = document.getElementById('track-url').value;
|
|
|
|
if (!title || !url) {
|
|
showNotification('Please fill in title and URL', 'error');
|
|
return;
|
|
}
|
|
|
|
const deck = djInterface.currentDeck;
|
|
const trackData = { title, artist, url };
|
|
|
|
loadTrackToPlayer(deck, trackData);
|
|
closeTrackLoader();
|
|
|
|
showNotification(`Track loaded to Deck ${deck}: ${title}`, 'success');
|
|
}
|
|
|
|
function loadTrackToPlayer(deck, trackData) {
|
|
const deckData = djInterface.decks[deck];
|
|
deckData.track = trackData;
|
|
|
|
// Update UI
|
|
document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = trackData.title.substring(0, 15);
|
|
document.getElementById(`title-${deck.toLowerCase()}`).textContent = trackData.title;
|
|
document.getElementById(`artist-${deck.toLowerCase()}`).textContent = trackData.artist || 'Unknown Artist';
|
|
|
|
// Load audio
|
|
if (isYouTubeUrl(trackData.url)) {
|
|
loadYouTubeTrack(deck, trackData);
|
|
} else {
|
|
loadDirectTrack(deck, trackData);
|
|
}
|
|
|
|
// Generate waveform
|
|
generateWaveform(deck, trackData.url);
|
|
}
|
|
|
|
function loadYouTubeTrack(deck, trackData) {
|
|
const videoId = extractYouTubeVideoId(trackData.url);
|
|
if (!videoId) {
|
|
showNotification('Invalid YouTube URL', 'error');
|
|
return;
|
|
}
|
|
|
|
// Create hidden YouTube player for this deck
|
|
createYouTubePlayerForDeck(deck, videoId);
|
|
}
|
|
|
|
function loadDirectTrack(deck, trackData) {
|
|
const player = djInterface.decks[deck].player;
|
|
player.src = trackData.url;
|
|
player.load();
|
|
}
|
|
|
|
function togglePlay(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
const playBtn = document.getElementById(`play-${deck.toLowerCase()}`);
|
|
const vinyl = document.getElementById(`vinyl-${deck.toLowerCase()}`);
|
|
|
|
if (!deckData.track) {
|
|
showNotification(`No track loaded in Deck ${deck}`, 'warning');
|
|
return;
|
|
}
|
|
|
|
if (deckData.isPlaying) {
|
|
// Pause
|
|
pauseTrack(deck);
|
|
playBtn.innerHTML = '<i class="fas fa-play"></i>';
|
|
playBtn.classList.remove('playing');
|
|
vinyl.classList.remove('spinning');
|
|
deckData.isPlaying = false;
|
|
} else {
|
|
// Play
|
|
playTrack(deck);
|
|
playBtn.innerHTML = '<i class="fas fa-pause"></i>';
|
|
playBtn.classList.add('playing');
|
|
vinyl.classList.add('spinning');
|
|
deckData.isPlaying = true;
|
|
}
|
|
|
|
// Send to FiveM
|
|
notifyFiveM('deckStateChanged', {
|
|
deck: deck,
|
|
isPlaying: deckData.isPlaying,
|
|
track: deckData.track
|
|
});
|
|
}
|
|
|
|
function playTrack(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
if (deckData.youtubePlayer) {
|
|
deckData.youtubePlayer.playVideo();
|
|
} else if (deckData.player) {
|
|
deckData.player.play();
|
|
}
|
|
}
|
|
|
|
function pauseTrack(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
if (deckData.youtubePlayer) {
|
|
deckData.youtubePlayer.pauseVideo();
|
|
} else if (deckData.player) {
|
|
deckData.player.pause();
|
|
}
|
|
}
|
|
|
|
function cue(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
if (!deckData.track) {
|
|
showNotification(`No track loaded in Deck ${deck}`, 'warning');
|
|
return;
|
|
}
|
|
|
|
// Set cue point to current position or go to cue point
|
|
if (deckData.isPlaying) {
|
|
deckData.cuePoint = getCurrentTime(deck);
|
|
showNotification(`Cue point set in Deck ${deck}`, 'info');
|
|
} else {
|
|
seekTo(deck, deckData.cuePoint);
|
|
showNotification(`Jumped to cue point in Deck ${deck}`, 'info');
|
|
}
|
|
}
|
|
|
|
function adjustPitch(deck, value) {
|
|
const deckData = djInterface.decks[deck];
|
|
deckData.pitch = parseFloat(value);
|
|
|
|
document.getElementById(`pitch-value-${deck.toLowerCase()}`).textContent = `${value}%`;
|
|
|
|
// Apply pitch change (this would need audio processing)
|
|
applyPitchChange(deck, value);
|
|
}
|
|
|
|
function adjustVolume(deck, value) {
|
|
const deckData = djInterface.decks[deck];
|
|
deckData.volume = parseInt(value);
|
|
|
|
if (deckData.youtubePlayer) {
|
|
deckData.youtubePlayer.setVolume(value);
|
|
} else if (deckData.player) {
|
|
deckData.player.volume = value / 100;
|
|
}
|
|
|
|
updateVUMeter(deck, value);
|
|
}
|
|
|
|
// Crossfader (Fortsetzung)
|
|
function adjustCrossfader(value) {
|
|
djInterface.mixer.crossfader = parseInt(value);
|
|
|
|
const volumeA = value <= 50 ? 100 : (100 - (value - 50) * 2);
|
|
const volumeB = value >= 50 ? 100 : (value * 2);
|
|
|
|
// Apply crossfader mixing
|
|
applyCrossfaderMix(volumeA, volumeB);
|
|
|
|
// Visual feedback
|
|
const crossfader = document.getElementById('crossfader');
|
|
crossfader.style.background = `linear-gradient(90deg,
|
|
rgba(78, 205, 196, ${volumeA/100}) 0%,
|
|
rgba(255, 107, 107, 0.5) 50%,
|
|
rgba(78, 205, 196, ${volumeB/100}) 100%)`;
|
|
}
|
|
|
|
function applyCrossfaderMix(volumeA, volumeB) {
|
|
const deckA = djInterface.decks.A;
|
|
const deckB = djInterface.decks.B;
|
|
|
|
const finalVolumeA = (deckA.volume * volumeA / 100) / 100;
|
|
const finalVolumeB = (deckB.volume * volumeB / 100) / 100;
|
|
|
|
if (deckA.youtubePlayer) {
|
|
deckA.youtubePlayer.setVolume(finalVolumeA * 100);
|
|
} else if (deckA.player) {
|
|
deckA.player.volume = finalVolumeA;
|
|
}
|
|
|
|
if (deckB.youtubePlayer) {
|
|
deckB.youtubePlayer.setVolume(finalVolumeB * 100);
|
|
} else if (deckB.player) {
|
|
deckB.player.volume = finalVolumeB;
|
|
}
|
|
}
|
|
|
|
function adjustMasterVolume(value) {
|
|
djInterface.mixer.masterVolume = parseInt(value);
|
|
|
|
// Apply master volume to both decks
|
|
const masterMultiplier = value / 100;
|
|
|
|
Object.keys(djInterface.decks).forEach(deck => {
|
|
const deckData = djInterface.decks[deck];
|
|
const finalVolume = (deckData.volume * masterMultiplier) / 100;
|
|
|
|
if (deckData.youtubePlayer) {
|
|
deckData.youtubePlayer.setVolume(finalVolume * 100);
|
|
} else if (deckData.player) {
|
|
deckData.player.volume = finalVolume;
|
|
}
|
|
});
|
|
|
|
updateMasterVU(value);
|
|
}
|
|
|
|
function setCrossfaderCurve(type) {
|
|
document.querySelectorAll('.curve-btn').forEach(btn => btn.classList.remove('active'));
|
|
event.target.classList.add('active');
|
|
|
|
djInterface.mixer.crossfaderCurve = type;
|
|
showNotification(`Crossfader curve set to ${type}`, 'info');
|
|
}
|
|
|
|
// EQ Functions
|
|
function applyEQ(channel, eqType, value) {
|
|
const deckData = djInterface.decks[channel];
|
|
|
|
if (!deckData.audioContext) {
|
|
setupAudioContext(channel);
|
|
}
|
|
|
|
// Apply EQ filter (simplified)
|
|
console.log(`Applying ${eqType} EQ: ${value}% to Deck ${channel}`);
|
|
|
|
// Visual feedback
|
|
const knob = document.querySelector(`[data-channel="${channel}"][data-eq="${eqType}"] .knob-indicator`);
|
|
if (knob) {
|
|
const hue = value > 0 ? 120 : value < 0 ? 0 : 200;
|
|
knob.style.boxShadow = `0 0 8px hsl(${hue}, 70%, 50%)`;
|
|
}
|
|
}
|
|
|
|
// Effects Functions
|
|
function toggleEffect(effectType) {
|
|
const isActive = djInterface.effects[effectType];
|
|
djInterface.effects[effectType] = !isActive;
|
|
|
|
const btn = event.target;
|
|
btn.classList.toggle('active');
|
|
|
|
if (djInterface.effects[effectType]) {
|
|
applyEffect(effectType, 50);
|
|
showNotification(`${effectType.toUpperCase()} ON`, 'success');
|
|
} else {
|
|
removeEffect(effectType);
|
|
showNotification(`${effectType.toUpperCase()} OFF`, 'info');
|
|
}
|
|
}
|
|
|
|
function applyEffect(effectType, value) {
|
|
console.log(`Applying ${effectType} effect: ${value}%`);
|
|
|
|
// Effect processing would go here
|
|
switch(effectType) {
|
|
case 'reverb':
|
|
applyReverb(value);
|
|
break;
|
|
case 'delay':
|
|
applyDelay(value);
|
|
break;
|
|
case 'filter':
|
|
applyFilter(value);
|
|
break;
|
|
case 'flanger':
|
|
applyFlanger(value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function removeEffect(effectType) {
|
|
console.log(`Removing ${effectType} effect`);
|
|
// Remove effect processing
|
|
}
|
|
|
|
// Waveform Functions
|
|
function initializeWaveforms() {
|
|
const canvasA = document.getElementById('waveform-a');
|
|
const canvasB = document.getElementById('waveform-b');
|
|
|
|
if (canvasA && canvasB) {
|
|
drawEmptyWaveform(canvasA);
|
|
drawEmptyWaveform(canvasB);
|
|
}
|
|
}
|
|
|
|
function drawEmptyWaveform(canvas) {
|
|
const ctx = canvas.getContext('2d');
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
// Draw center line
|
|
ctx.strokeStyle = 'rgba(78, 205, 196, 0.3)';
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, height / 2);
|
|
ctx.lineTo(width, height / 2);
|
|
ctx.stroke();
|
|
|
|
// Draw grid
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
|
for (let i = 0; i < width; i += 20) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(i, 0);
|
|
ctx.lineTo(i, height);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function generateWaveform(deck, url) {
|
|
const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`);
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Simulate waveform generation
|
|
drawSimulatedWaveform(ctx, canvas.width, canvas.height);
|
|
}
|
|
|
|
function drawSimulatedWaveform(ctx, width, height) {
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
const centerY = height / 2;
|
|
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
|
gradient.addColorStop(0, 'rgba(255, 107, 107, 0.8)');
|
|
gradient.addColorStop(0.5, 'rgba(78, 205, 196, 0.8)');
|
|
gradient.addColorStop(1, 'rgba(255, 107, 107, 0.8)');
|
|
|
|
ctx.fillStyle = gradient;
|
|
|
|
for (let x = 0; x < width; x += 2) {
|
|
const amplitude = Math.random() * (height / 2) * (0.3 + Math.sin(x * 0.01) * 0.7);
|
|
ctx.fillRect(x, centerY - amplitude, 2, amplitude * 2);
|
|
}
|
|
}
|
|
|
|
// VU Meters
|
|
function startVUMeters() {
|
|
setInterval(() => {
|
|
updateVUMeter('A', djInterface.decks.A.isPlaying ? Math.random() * 100 : 0);
|
|
updateVUMeter('B', djInterface.decks.B.isPlaying ? Math.random() * 100 : 0);
|
|
updateMasterVU(djInterface.mixer.masterVolume);
|
|
}, 100);
|
|
}
|
|
|
|
function updateVUMeter(deck, level) {
|
|
const vuBar = document.getElementById(`vu-${deck.toLowerCase()}`);
|
|
if (vuBar) {
|
|
vuBar.style.height = `${level}%`;
|
|
|
|
// Color based on level
|
|
if (level > 80) {
|
|
vuBar.style.background = 'linear-gradient(180deg, #ff0000, #ff6b6b)';
|
|
} else if (level > 60) {
|
|
vuBar.style.background = 'linear-gradient(180deg, #feca57, #ff6b6b)';
|
|
} else {
|
|
vuBar.style.background = 'linear-gradient(180deg, #4ecdc4, #feca57)';
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateMasterVU(level) {
|
|
const masterVU = document.getElementById('master-vu');
|
|
if (masterVU) {
|
|
masterVU.style.height = `${level}%`;
|
|
}
|
|
}
|
|
|
|
// Time and BPM
|
|
function updateTimeDisplay() {
|
|
const now = new Date();
|
|
const timeString = now.toLocaleTimeString('de-DE', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
|
|
const timeDisplay = document.getElementById('current-time');
|
|
if (timeDisplay) {
|
|
timeDisplay.textContent = timeString;
|
|
}
|
|
|
|
// Update track times
|
|
updateTrackTimes();
|
|
|
|
// Update BPM (simulated)
|
|
updateBPMDisplay();
|
|
}
|
|
|
|
function updateTrackTimes() {
|
|
['A', 'B'].forEach(deck => {
|
|
const deckData = djInterface.decks[deck];
|
|
if (deckData.track && deckData.isPlaying) {
|
|
const currentTime = getCurrentTime(deck);
|
|
const duration = getDuration(deck);
|
|
|
|
document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent =
|
|
formatTime(currentTime);
|
|
document.getElementById(`time-total-${deck.toLowerCase()}`).textContent =
|
|
formatTime(duration);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getCurrentTime(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
if (deckData.youtubePlayer) {
|
|
return deckData.youtubePlayer.getCurrentTime() || 0;
|
|
} else if (deckData.player) {
|
|
return deckData.player.currentTime || 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function getDuration(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
if (deckData.youtubePlayer) {
|
|
return deckData.youtubePlayer.getDuration() || 0;
|
|
} else if (deckData.player) {
|
|
return deckData.player.duration || 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function formatTime(seconds) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function updateBPMDisplay() {
|
|
// Simulate BPM detection
|
|
const bpm = 120 + Math.floor(Math.random() * 40);
|
|
const bpmDisplay = document.getElementById('bpm-display');
|
|
if (bpmDisplay) {
|
|
bpmDisplay.textContent = bpm;
|
|
}
|
|
}
|
|
|
|
// Recording Functions
|
|
function toggleRecording() {
|
|
djInterface.isRecording = !djInterface.isRecording;
|
|
|
|
const recordBtn = document.querySelector('.record-btn');
|
|
const recordTime = document.querySelector('.recording-time');
|
|
|
|
if (djInterface.isRecording) {
|
|
recordBtn.classList.add('recording');
|
|
recordBtn.innerHTML = '<i class="fas fa-stop"></i><span>STOP</span>';
|
|
startRecordingTimer();
|
|
showNotification('Recording started', 'success');
|
|
} else {
|
|
recordBtn.classList.remove('recording');
|
|
recordBtn.innerHTML = '<i class="fas fa-circle"></i><span>REC</span>';
|
|
stopRecordingTimer();
|
|
showNotification('Recording stopped', 'info');
|
|
}
|
|
}
|
|
|
|
let recordingStartTime = 0;
|
|
let recordingTimer = null;
|
|
|
|
function startRecordingTimer() {
|
|
recordingStartTime = Date.now();
|
|
recordingTimer = setInterval(() => {
|
|
const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
|
|
const recordTime = document.querySelector('.recording-time');
|
|
if (recordTime) {
|
|
recordTime.textContent = formatTime(elapsed);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function stopRecordingTimer() {
|
|
if (recordingTimer) {
|
|
clearInterval(recordingTimer);
|
|
recordingTimer = null;
|
|
}
|
|
|
|
const recordTime = document.querySelector('.recording-time');
|
|
if (recordTime) {
|
|
recordTime.textContent = '00:00';
|
|
}
|
|
}
|
|
|
|
// Search and Library Functions
|
|
function searchTracks(query) {
|
|
console.log('Searching tracks:', query);
|
|
|
|
// Simulate track search
|
|
const mockTracks = [
|
|
{ title: 'Summer Vibes', artist: 'DJ Cool', url: 'https://example.com/track1' },
|
|
{ title: 'Night Drive', artist: 'Electronic Dreams', url: 'https://example.com/track2' },
|
|
{ title: 'Bass Drop', artist: 'Heavy Beats', url: 'https://example.com/track3' },
|
|
{ title: 'Chill Out', artist: 'Ambient Sounds', url: 'https://example.com/track4' }
|
|
];
|
|
|
|
const filteredTracks = mockTracks.filter(track =>
|
|
track.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
track.artist.toLowerCase().includes(query.toLowerCase())
|
|
);
|
|
|
|
displayTrackList(filteredTracks);
|
|
}
|
|
|
|
function displayTrackList(tracks) {
|
|
const trackList = document.getElementById('track-list');
|
|
trackList.innerHTML = '';
|
|
|
|
tracks.forEach(track => {
|
|
const trackItem = document.createElement('div');
|
|
trackItem.className = 'track-item';
|
|
trackItem.innerHTML = `
|
|
<div class="track-item-title">${track.title}</div>
|
|
<div class="track-item-artist">${track.artist}</div>
|
|
`;
|
|
|
|
trackItem.addEventListener('dblclick', () => {
|
|
if (djInterface.currentDeck) {
|
|
loadTrackToPlayer(djInterface.currentDeck, track);
|
|
showNotification(`${track.title} loaded to Deck ${djInterface.currentDeck}`, 'success');
|
|
}
|
|
});
|
|
|
|
trackList.appendChild(trackItem);
|
|
});
|
|
}
|
|
|
|
// Keyboard Shortcuts
|
|
function handleKeyboardShortcuts(event) {
|
|
if (event.target.tagName === 'INPUT') return;
|
|
|
|
switch(event.code) {
|
|
case 'Space':
|
|
event.preventDefault();
|
|
if (djInterface.currentDeck) {
|
|
togglePlay(djInterface.currentDeck);
|
|
}
|
|
break;
|
|
case 'KeyQ':
|
|
togglePlay('A');
|
|
break;
|
|
case 'KeyW':
|
|
togglePlay('B');
|
|
break;
|
|
case 'KeyA':
|
|
cue('A');
|
|
break;
|
|
case 'KeyS':
|
|
cue('B');
|
|
break;
|
|
case 'KeyZ':
|
|
adjustCrossfader(0); // Full A
|
|
break;
|
|
case 'KeyX':
|
|
adjustCrossfader(50); // Center
|
|
break;
|
|
case 'KeyC':
|
|
adjustCrossfader(100); // Full B
|
|
break;
|
|
case 'KeyR':
|
|
toggleRecording();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Animations
|
|
function startAnimations() {
|
|
// Vinyl spinning animation is handled by CSS
|
|
|
|
// Playhead animation
|
|
setInterval(() => {
|
|
['A', 'B'].forEach(deck => {
|
|
const deckData = djInterface.decks[deck];
|
|
if (deckData.isPlaying && deckData.track) {
|
|
updatePlayhead(deck);
|
|
}
|
|
});
|
|
}, 100);
|
|
}
|
|
|
|
function updatePlayhead(deck) {
|
|
const playhead = document.getElementById(`playhead-${deck.toLowerCase()}`);
|
|
const currentTime = getCurrentTime(deck);
|
|
const duration = getDuration(deck);
|
|
|
|
if (duration > 0) {
|
|
const progress = (currentTime / duration) * 100;
|
|
playhead.style.left = `${progress}%`;
|
|
}
|
|
}
|
|
|
|
// Modal Functions
|
|
function closeTrackLoader() {
|
|
document.getElementById('track-loader').classList.add('hidden');
|
|
|
|
// Clear form
|
|
document.getElementById('track-title').value = '';
|
|
document.getElementById('track-artist').value = '';
|
|
document.getElementById('track-url').value = '';
|
|
}
|
|
|
|
function ejectDeck(deck) {
|
|
const deckData = djInterface.decks[deck];
|
|
|
|
if (deckData.isPlaying) {
|
|
togglePlay(deck);
|
|
}
|
|
|
|
// Clear deck
|
|
deckData.track = null;
|
|
deckData.cuePoint = 0;
|
|
|
|
// Clear UI
|
|
document.getElementById(`track-name-${deck.toLowerCase()}`).textContent = 'NO TRACK';
|
|
document.getElementById(`title-${deck.toLowerCase()}`).textContent = 'No Track Loaded';
|
|
document.getElementById(`artist-${deck.toLowerCase()}`).textContent = '-';
|
|
document.getElementById(`time-elapsed-${deck.toLowerCase()}`).textContent = '00:00';
|
|
document.getElementById(`time-total-${deck.toLowerCase()}`).textContent = '00:00';
|
|
|
|
// Clear waveform
|
|
const canvas = document.getElementById(`waveform-${deck.toLowerCase()}`);
|
|
drawEmptyWaveform(canvas);
|
|
|
|
showNotification(`Deck ${deck} ejected`, 'info');
|
|
}
|
|
|
|
// Interface Control
|
|
function closeDJInterface() {
|
|
document.getElementById('dj-interface').classList.add('hidden');
|
|
|
|
// Stop all playback
|
|
['A', 'B'].forEach(deck => {
|
|
if (djInterface.decks[deck].isPlaying) {
|
|
togglePlay(deck);
|
|
}
|
|
});
|
|
|
|
// Notify FiveM
|
|
notifyFiveM('djInterfaceClosed', {});
|
|
}
|
|
|
|
function showDJInterface() {
|
|
document.getElementById('dj-interface').classList.remove('hidden');
|
|
}
|
|
|
|
// Utility Functions
|
|
function showNotification(message, type = 'info') {
|
|
console.log(`DJ System [${type.toUpperCase()}]: ${message}`);
|
|
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 80px;
|
|
right: 20px;
|
|
padding: 15px 20px;
|
|
border-radius: 10px;
|
|
color: white;
|
|
font-weight: 600;
|
|
z-index: 10000;
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease;
|
|
background: ${type === 'error' ? '#ff6b6b' : type === 'success' ? '#4ecdc4' : type === 'warning' ? '#feca57' : '#45b7d1'};
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(0)';
|
|
}, 100);
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
document.body.removeChild(notification);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
function handleResize() {
|
|
// Handle responsive design
|
|
const djInterface = document.getElementById('dj-interface');
|
|
if (window.innerWidth < 1200) {
|
|
djInterface.classList.add('mobile-layout');
|
|
} else {
|
|
djInterface.classList.remove('mobile-layout');
|
|
}
|
|
}
|
|
|
|
// YouTube Integration (from previous code)
|
|
function isYouTubeUrl(url) {
|
|
const youtubePatterns = [
|
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
|
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
];
|
|
|
|
return youtubePatterns.some(pattern => pattern.test(url));
|
|
}
|
|
|
|
function extractYouTubeVideoId(url) {
|
|
const patterns = [
|
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
|
/youtube\.com\/watch\?.*v=([^&\n?#]+)/
|
|
];
|
|
|
|
for (let pattern of patterns) {
|
|
const match = url.match(pattern);
|
|
if (match && match[1]) {
|
|
return match[1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// FiveM Integration
|
|
function notifyFiveM(event, data) {
|
|
fetch(`https://${GetParentResourceName()}/${event}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
}).catch(err => {
|
|
console.error('DJ System: Failed to notify FiveM:', err);
|
|
});
|
|
}
|
|
|
|
function GetParentResourceName() {
|
|
return window.location.hostname;
|
|
}
|
|
|
|
// Message Handler for FiveM
|
|
window.addEventListener('message', function(event) {
|
|
const data = event.data;
|
|
|
|
switch(data.type) {
|
|
case 'showDJInterface':
|
|
showDJInterface();
|
|
break;
|
|
case 'hideDJInterface':
|
|
closeDJInterface();
|
|
break;
|
|
case 'loadTrack':
|
|
if (data.deck && data.track) {
|
|
loadTrackToPlayer(data.deck, data.track);
|
|
}
|
|
break;
|
|
case 'setVolume':
|
|
if (data.deck && data.volume !== undefined) {
|
|
adjustVolume(data.deck, data.volume);
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Initialize search with default tracks
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setTimeout(() => {
|
|
searchTracks(''); // Load default tracks
|
|
}, 1000);
|
|
});
|
|
|
|
console.log('DJ System: Professional interface loaded! 🎛️');
|
|
console.log('Keyboard shortcuts:');
|
|
console.log('Q/W - Play/Pause Deck A/B');
|
|
console.log('A/S - Cue Deck A/B');
|
|
console.log('Z/X/C - Crossfader positions');
|
|
console.log('R - Toggle recording');
|
|
console.log('Space - Play/Pause current deck');
|
|
|