This commit is contained in:
Nordi98 2025-08-03 16:51:12 +02:00
parent 0e29315dd1
commit e0a3b21c61
7 changed files with 1463 additions and 0 deletions

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DJ System</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="music-player">
<audio
id="audio-player"
preload="auto"
crossorigin="anonymous"
controls="false"
style="display: none;">
Dein Browser unterstützt das Audio-Element nicht.
</audio>
<!-- Optional: Progress indicator (hidden by default) -->
<div id="progress-container" style="display: none;">
<div id="progress-bar"></div>
</div>
<!-- Optional: Song info display (hidden by default) -->
<div id="song-info" style="display: none;">
<span id="song-title"></span>
<span id="song-time"></span>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,401 @@
let audioPlayer = null;
let currentVolume = 50;
let isPlaying = false;
let currentSong = null;
let fadeInterval = null;
// Initialize when page loads
document.addEventListener('DOMContentLoaded', function() {
audioPlayer = document.getElementById('audio-player');
setupAudioPlayer();
});
// Setup audio player with event listeners
function setupAudioPlayer() {
if (!audioPlayer) return;
// Audio Events
audioPlayer.addEventListener('loadstart', function() {
console.log('DJ System: Loading started');
});
audioPlayer.addEventListener('canplay', function() {
console.log('DJ System: Can start playing');
});
audioPlayer.addEventListener('play', function() {
console.log('DJ System: Playback started');
isPlaying = true;
});
audioPlayer.addEventListener('pause', function() {
console.log('DJ System: Playback paused');
isPlaying = false;
});
audioPlayer.addEventListener('ended', function() {
console.log('DJ System: Song ended');
isPlaying = false;
// Notify FiveM that song ended (for playlist functionality)
fetch(`https://${GetParentResourceName()}/songEnded`, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify({})
});
});
audioPlayer.addEventListener('error', function(e) {
console.error('DJ System: Audio error', e);
isPlaying = false;
// Notify FiveM about the error
fetch(`https://${GetParentResourceName()}/audioError`, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify({
error: 'Audio playback error',
code: audioPlayer.error ? audioPlayer.error.code : 'unknown'
})
});
});
audioPlayer.addEventListener('loadedmetadata', function() {
console.log('DJ System: Metadata loaded, duration:', audioPlayer.duration);
});
audioPlayer.addEventListener('timeupdate', function() {
// Optional: Send progress updates
if (isPlaying && audioPlayer.duration) {
const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100;
// You can use this for progress bars if needed
}
});
}
// Main message handler from FiveM
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;
case 'fadeOut':
fadeOut();
break;
case 'fadeIn':
fadeIn();
break;
case 'pauseMusic':
pauseMusic();
break;
case 'resumeMusic':
resumeMusic();
break;
}
});
// Play music function with YouTube support
async function playMusic(url, volume, title = 'Unknown') {
if (!audioPlayer) {
console.error('DJ System: Audio player not initialized');
return;
}
try {
// Stop current music first
stopMusic();
console.log('DJ System: Attempting to play:', title, url);
// Set volume
currentVolume = volume || 50;
audioPlayer.volume = currentVolume / 100;
// Handle different URL types
let playableUrl = await processUrl(url);
if (!playableUrl) {
console.error('DJ System: Could not process URL:', url);
notifyError('Could not process audio URL');
return;
}
// Set source and play
audioPlayer.src = playableUrl;
audioPlayer.load();
// Store current song info
currentSong = {
title: title,
url: url,
playableUrl: playableUrl
};
// Attempt to play
const playPromise = audioPlayer.play();
if (playPromise !== undefined) {
playPromise
.then(() => {
console.log('DJ System: Successfully started playing:', title);
isPlaying = true;
})
.catch(error => {
console.error('DJ System: Play failed:', error);
notifyError('Playback failed: ' + error.message);
});
}
} catch (error) {
console.error('DJ System: Error in playMusic:', error);
notifyError('Error playing music: ' + error.message);
}
}
// Process different URL types
async function processUrl(url) {
try {
// Check if it's a YouTube URL
if (isYouTubeUrl(url)) {
console.log('DJ System: Processing YouTube URL');
return await convertYouTubeUrl(url);
}
// Check if it's a direct audio URL
if (isDirectAudioUrl(url)) {
console.log('DJ System: Direct audio URL detected');
return url;
}
// Try to use URL as-is (might be pre-converted)
console.log('DJ System: Using URL as-is');
return url;
} catch (error) {
console.error('DJ System: Error processing URL:', error);
return null;
}
}
// Check if URL is YouTube
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));
}
// Check if URL is direct audio
function isDirectAudioUrl(url) {
const audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'];
const lowerUrl = url.toLowerCase();
return audioExtensions.some(ext => lowerUrl.includes(ext)) ||
lowerUrl.includes('audio/') ||
lowerUrl.includes('stream');
}
// Convert YouTube URL (this would be handled server-side in real implementation)
async function convertYouTubeUrl(url) {
try {
// Extract video ID
const videoId = extractYouTubeVideoId(url);
if (!videoId) {
throw new Error('Could not extract YouTube video ID');
}
console.log('DJ System: YouTube Video ID:', videoId);
// In a real implementation, this would call your server-side converter
// For now, we'll return the original URL and let the server handle it
return url;
} catch (error) {
console.error('DJ System: YouTube conversion error:', error);
return null;
}
}
// Extract YouTube video ID
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;
}
// Stop music
function stopMusic() {
if (!audioPlayer) return;
try {
audioPlayer.pause();
audioPlayer.currentTime = 0;
audioPlayer.src = '';
isPlaying = false;
currentSong = null;
// Clear any fade effects
if (fadeInterval) {
clearInterval(fadeInterval);
fadeInterval = null;
}
console.log('DJ System: Music stopped');
} catch (error) {
console.error('DJ System: Error stopping music:', error);
}
}
// Pause music
function pauseMusic() {
if (!audioPlayer || !isPlaying) return;
try {
audioPlayer.pause();
isPlaying = false;
console.log('DJ System: Music paused');
} catch (error) {
console.error('DJ System: Error pausing music:', error);
}
}
// Resume music
function resumeMusic() {
if (!audioPlayer || isPlaying) return;
try {
const playPromise = audioPlayer.play();
if (playPromise !== undefined) {
playPromise
.then(() => {
isPlaying = true;
console.log('DJ System: Music resumed');
})
.catch(error => {
console.error('DJ System: Resume failed:', error);
});
}
} catch (error) {
console.error('DJ System: Error resuming music:', error);
}
}
// Set volume
function setVolume(volume) {
if (!audioPlayer) return;
try {
currentVolume = Math.max(0, Math.min(100, volume));
audioPlayer.volume = currentVolume / 100;
console.log('DJ System: Volume set to', currentVolume + '%');
} catch (error) {
console.error('DJ System: Error setting volume:', error);
}
}
// Fade out effect (when player moves away)
function fadeOut() {
if (!audioPlayer || !isPlaying) return;
if (fadeInterval) {
clearInterval(fadeInterval);
}
let currentVol = audioPlayer.volume;
const targetVol = 0;
const fadeStep = 0.05;
fadeInterval = setInterval(() => {
currentVol -= fadeStep;
if (currentVol <= targetVol) {
currentVol = targetVol;
audioPlayer.volume = currentVol;
clearInterval(fadeInterval);
fadeInterval = null;
} else {
audioPlayer.volume = currentVol;
}
}, 50);
}
// Fade in effect (when player moves closer)
function fadeIn() {
if (!audioPlayer || !isPlaying) return;
if (fadeInterval) {
clearInterval(fadeInterval);
}
let currentVol = audioPlayer.volume;
const targetVol = currentVolume / 100;
const fadeStep = 0.05;
fadeInterval = setInterval(() => {
currentVol += fadeStep;
if (currentVol >= targetVol) {
currentVol = targetVol;
audioPlayer.volume = currentVol;
clearInterval(fadeInterval);
fadeInterval = null;
} else {
audioPlayer.volume = currentVol;
}
}, 50);
}
// Notify FiveM about errors
function notifyError(message) {
fetch(`https://${GetParentResourceName()}/audioError`, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify({
error: message
})
}).catch(err => {
console.error('DJ System: Failed to notify error:', err);
});
}
// Get current resource name
function GetParentResourceName() {
return window.location.hostname;
}
// Debug functions (can be called from browser console)
window.djDebug = {
getCurrentSong: () => currentSong,
getVolume: () => currentVolume,
isPlaying: () => isPlaying,
getAudioPlayer: () => audioPlayer,
testPlay: (url) => playMusic(url, 50, 'Test Song'),
testStop: () => stopMusic(),
testVolume: (vol) => setVolume(vol)
};
// Log when script is loaded
console.log('DJ System: Script loaded and ready');

View file

@ -0,0 +1,72 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: transparent;
font-family: 'Arial', sans-serif;
overflow: hidden;
}
#music-player {
position: absolute;
top: -9999px;
left: -9999px;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
#audio-player {
width: 100%;
height: auto;
}
/* Optional progress bar styles */
#progress-container {
width: 300px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
}
#progress-bar {
height: 100%;
background: linear-gradient(90deg, #ff6b6b, #4ecdc4);
width: 0%;
transition: width 0.1s ease;
}
/* Optional song info styles */
#song-info {
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
}
#song-title {
font-weight: bold;
margin-right: 10px;
}
#song-time {
opacity: 0.7;
}
/* Responsive design */
@media (max-width: 768px) {
#progress-container {
width: 250px;
}
#song-info {
font-size: 11px;
padding: 8px;
}
}