ed
This commit is contained in:
parent
0e29315dd1
commit
e0a3b21c61
7 changed files with 1463 additions and 0 deletions
34
resources/[tools]/nordi_dj/html/index.html
Normal file
34
resources/[tools]/nordi_dj/html/index.html
Normal 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>
|
401
resources/[tools]/nordi_dj/html/script.js
Normal file
401
resources/[tools]/nordi_dj/html/script.js
Normal 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');
|
72
resources/[tools]/nordi_dj/html/style.css
Normal file
72
resources/[tools]/nordi_dj/html/style.css
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue