window. onerror = function ( message, source, lineno, colno, error ) {
console. error ( "An error occurred:" , message, "at" , source, ":" , lineno) ;
return true ;
} ;
if ( typeof particlesJS !== 'undefined' ) {
document. addEventListener ( 'DOMContentLoaded' , function ( ) {
particlesJS ( 'particles-js' , {
particles : {
number : { value : 80 , density : { enable : true , value_area : 800 } } ,
color : { value : '#ffffff' } ,
shape : { type : 'circle' , stroke : { width : 0 , color : '#000000' } } ,
opacity : { value : 0.5 , random : true , anim : { enable : true , speed : 1 , opacity_min : 0.1 , sync : false } } ,
size : { value : 3 , random : true , anim : { enable : true , speed : 4 , size_min : 0.3 , sync : false } } ,
line_linked : { enable : true , distance : 150 , color : '#ffffff' , opacity : 0.4 , width : 1 } ,
move : { enable : true , speed : 1 , direction : 'none' , random : true , straight : false , out_mode : 'out' , bounce : false }
} ,
interactivity : {
detect_on : 'canvas' ,
events : { onhover : { enable : true , mode : 'repulse' } , onclick : { enable : true , mode : 'push' } , resize : true } ,
modes : { repulse : { distance : 100 , duration : 0.4 } , push : { particles_nb : 4 } }
} ,
retina_detect : true
} ) ;
console. log ( 'Particles initialized' ) ;
} ) ;
} else {
console. error ( "particlesJS is not defined. The library may not have loaded correctly." ) ;
}
let wavesurfer;
function initWaveSurfer ( ) {
wavesurfer = WaveSurfer. create ( {
container : '#visualizer' ,
waveColor : 'rgba(255, 255, 255, 0.5)' ,
progressColor : 'rgba(29, 185, 84, 0.7)' ,
cursorColor : 'transparent' ,
barWidth : 2 ,
barRadius : 3 ,
cursorWidth : 0 ,
height : 40 ,
barGap : 2 ,
responsive : true
} ) ;
wavesurfer. on ( 'ready' , onWavesurferReady) ;
wavesurfer. on ( 'audioprocess' , updateProgress) ;
wavesurfer. on ( 'audioprocess' , updateLyrics) ;
console. log ( 'WaveSurfer initialized' ) ;
bindPlayPauseButton ( ) ;
}
function bindPlayPauseButton ( ) {
playPauseButton. addEventListener ( 'click' , togglePlayPause) ;
}
function togglePlayPause ( ) {
if ( wavesurfer) {
wavesurfer. playPause ( ) ;
updatePlayPauseIcon ( ) ;
} else {
console. error ( 'WaveSurfer is not initialized' ) ;
}
}
function updatePlayPauseIcon ( ) {
const icon = playPauseButton. querySelector ( 'i' ) ;
if ( wavesurfer && wavesurfer. isPlaying ( ) ) {
icon. classList. remove ( 'fa-play' ) ;
icon. classList. add ( 'fa-pause' ) ;
} else {
icon. classList. remove ( 'fa-pause' ) ;
icon. classList. add ( 'fa-play' ) ;
}
}
document. addEventListener ( 'click' , function initAudioContext ( ) {
if ( ! wavesurfer) {
initWaveSurfer ( ) ;
}
document. removeEventListener ( 'click' , initAudioContext) ;
} , { once : true } ) ;
const playPauseButton = document. getElementById ( 'play-pause' ) ;
const prevButton = document. getElementById ( 'prev' ) ;
const nextButton = document. getElementById ( 'next' ) ;
const shuffleButton = document. getElementById ( 'shuffle' ) ;
const repeatButton = document. getElementById ( 'repeat' ) ;
const fileInput = document. getElementById ( 'file-input' ) ;
const songTitle = document. getElementById ( 'song-title' ) ;
const artistName = document. getElementById ( 'artist-name' ) ;
const albumCover = document. getElementById ( 'album-cover' ) ;
const lyricsContainer = document. getElementById ( 'lyrics' ) ;
const progressContainer = document. getElementById ( 'progress-container' ) ;
const progressBar = document. getElementById ( 'progress-bar' ) ;
const currentTime = document. getElementById ( 'current-time' ) ;
const totalTime = document. getElementById ( 'total-time' ) ;
function initializeControls ( ) {
playPauseButton. addEventListener ( 'click' , togglePlayPause) ;
prevButton. addEventListener ( 'click' , playPreviousTrack) ;
nextButton. addEventListener ( 'click' , playNextTrack) ;
shuffleButton. addEventListener ( 'click' , toggleShuffle) ;
repeatButton. addEventListener ( 'click' , toggleRepeat) ;
progressContainer. addEventListener ( 'click' , seekToPosition) ;
}
fileInput. addEventListener ( 'change' , ( e ) => {
const file = e. target. files[ 0 ] ;
if ( file) {
if ( ! wavesurfer) {
initWaveSurfer ( ) ;
}
const objectURL = URL . createObjectURL ( file) ;
wavesurfer. load ( objectURL) ;
updateSongInfo ( file) ;
console. log ( 'File loaded:' , file. name) ;
}
} ) ;
function updateSongInfo ( file ) {
songTitle. textContent = file. name. replace ( / \.[^/.]+$ / , "" ) ;
artistName. textContent = "未知艺术家" ;
console. log ( 'Song info updated' ) ;
}
function updateProgress ( ) {
if ( wavesurfer) {
const progress = wavesurfer. getCurrentTime ( ) / wavesurfer. getDuration ( ) ;
progressBar. style. width = ` ${ progress * 100 } % ` ;
currentTime. textContent = formatTime ( wavesurfer. getCurrentTime ( ) ) ;
}
}
progressContainer. addEventListener ( 'click' , function ( e ) {
if ( wavesurfer) {
const clickPosition = ( e. clientX - this . getBoundingClientRect ( ) . left) / this . offsetWidth;
wavesurfer. seekTo ( clickPosition) ;
}
} ) ;
function formatTime ( time ) {
const minutes = Math. floor ( time / 60 ) ;
const seconds = Math. floor ( time % 60 ) ;
return ` ${ minutes} : ${ seconds. toString ( ) . padStart ( 2 , '0' ) } ` ;
}
function onWavesurferReady ( ) {
playPauseButton. disabled = false ;
totalTime. textContent = formatTime ( wavesurfer. getDuration ( ) ) ;
updatePlayPauseIcon ( ) ;
console. log ( 'Wavesurfer ready' ) ;
wavesurfer. play ( ) ;
}
function updateLyrics ( time ) {
if ( ! showingLyrics) return ;
if ( window. parsedLyrics && window. parsedLyrics. length > 0 ) {
const currentLyric = window. parsedLyrics. find ( lyric => lyric. time <= time) ;
if ( currentLyric) {
lyricsContainer. textContent = currentLyric. text;
}
} else {
lyricsContainer. textContent = ` 正在播放: ${ Math. floor ( time) } 秒 ` ;
}
console. log ( 'Lyrics updated:' , lyricsContainer. textContent) ;
}
prevButton. addEventListener ( 'click' , ( ) => console. log ( '上一首' ) ) ;
nextButton. addEventListener ( 'click' , ( ) => console. log ( '下一首' ) ) ;
shuffleButton. addEventListener ( 'click' , ( ) => console. log ( '随机播放' ) ) ;
repeatButton. addEventListener ( 'click' , ( ) => console. log ( '重复播放' ) ) ;
document. addEventListener ( 'DOMContentLoaded' , ( ) => {
initializeControls ( ) ;
initWaveSurfer ( ) ;
} ) ;
const coverLyricsContainer = document. getElementById ( 'cover-lyrics-container' ) ;
const lyricsInput = document. getElementById ( 'lyrics-input' ) ;
let showingLyrics = false ;
coverLyricsContainer. addEventListener ( 'click' , toggleLyrics) ;
function toggleLyrics ( ) {
showingLyrics = ! showingLyrics;
albumCover. style. display = showingLyrics ? 'none' : 'block' ;
lyricsContainer. style. display = showingLyrics ? 'flex' : 'none' ;
console. log ( 'Lyrics toggled:' , showingLyrics) ;
}
lyricsInput. addEventListener ( 'change' , ( e ) => {
const file = e. target. files[ 0 ] ;
if ( file) {
const reader = new FileReader ( ) ;
reader. onload = function ( e ) {
const cueContent = e. target. result;
const lyrics = parseCueFile ( cueContent) ;
updateLyricsDisplay ( lyrics) ;
} ;
reader. readAsText ( file) ;
}
} ) ;
function parseCueFile ( cueContent ) {
const lines = cueContent. split ( '\n' ) ;
const lyrics = [ ] ;
let currentTime = 0 ;
for ( let line of lines) {
if ( line. startsWith ( ' TRACK' ) ) {
currentTime = 0 ;
} else if ( line. startsWith ( ' INDEX 01' ) ) {
const timeStr = line. split ( 'INDEX 01' ) [ 1 ] . trim ( ) ;
const [ min, sec, frame] = timeStr. split ( ':' ) . map ( Number) ;
currentTime = min * 60 + sec + frame / 75 ;
} else if ( line. startsWith ( ' TITLE' ) ) {
const lyric = line. split ( 'TITLE' ) [ 1 ] . trim ( ) . replace ( / " / g , '' ) ;
lyrics. push ( { time : currentTime, text : lyric } ) ;
}
}
return lyrics;
}
function updateLyricsDisplay ( lyrics ) {
lyricsContainer. innerHTML = '' ;
for ( let lyric of lyrics) {
const p = document. createElement ( 'p' ) ;
p. textContent = lyric. text;
lyricsContainer. appendChild ( p) ;
}
}
const fileInputContainer = document. getElementById ( 'file-input-container' ) ;
let isDragging = false ;
let startX, startY, initialLeft, initialTop;
fileInputContainer. addEventListener ( 'mousedown' , startDragging) ;
document. addEventListener ( 'mousemove' , drag) ;
document. addEventListener ( 'mouseup' , stopDragging) ;
function startDragging ( e ) {
isDragging = true ;
startX = e. clientX;
startY = e. clientY;
initialLeft = fileInputContainer. offsetLeft;
initialTop = fileInputContainer. offsetTop;
}
function drag ( e ) {
if ( ! isDragging) return ;
e. preventDefault ( ) ;
const dx = e. clientX - startX;
const dy = e. clientY - startY;
fileInputContainer. style. left = ` ${ initialLeft + dx} px ` ;
fileInputContainer. style. top = ` ${ initialTop + dy} px ` ;
}
function stopDragging ( ) {
isDragging = false ;
}
body {
margin : 0;
display : flex;
justify-content : center;
align-items : center;
height : 100vh;
background : linear-gradient ( 45deg, #1a1a1a, #2a2a2a) ;
font-family : 'Poppins' , sans-serif;
color : white;
}
#player {
position : relative;
width : 340px;
height : 600px;
background-color : rgba ( 255, 255, 255, 0.1) ;
border-radius : 30px;
overflow : hidden;
box-shadow : 0 10px 30px rgba ( 0, 0, 0, 0.3) ;
transition : all 0.3s ease;
padding : 20px;
display : flex;
flex-direction : column;
justify-content : space-between;
z-index : 1;
}
#album-cover {
width : 220px;
height : 220px;
border-radius : 50%;
margin : 20px auto;
display : block;
box-shadow : 0 10px 30px rgba ( 0, 0, 0, 0.3) ;
animation : rotate 20s linear infinite;
transition : all 0.3s ease;
}
#info-container {
text-align : center;
margin-bottom : 15px;
}
#song-title {
font-size : 22px;
margin : 0;
font-weight : 600;
text-shadow : 0 2px 4px rgba ( 0, 0, 0, 0.3) ;
}
#artist-name {
font-size : 16px;
margin : 5px 0 0;
opacity : 0.8;
}
#lyrics {
height : 40px;
overflow-y : auto;
margin : 10px 0;
text-align : center;
font-size : 14px;
opacity : 0.9;
}
#progress-container {
position : relative;
width : 100%;
height : 40px;
background-color : rgba ( 255, 255, 255, 0.1) ;
margin : 10px 0;
border-radius : 20px;
overflow : hidden;
cursor : pointer;
}
#time-info {
display : flex;
justify-content : space-between;
width : 100%;
margin : 5px 0;
font-size : 12px;
opacity : 0.8;
}
#controls {
display : flex;
justify-content : space-between;
align-items : center;
margin-top : 15px;
}
.control-btn {
background : none;
border : none;
color : white;
font-size : 20px;
cursor : pointer;
transition : all 0.2s;
opacity : 0.8;
}
.main-btn {
font-size : 40px;
opacity : 1;
}
#file-input-container {
position : absolute;
bottom : 20px;
right : 20px;
z-index : 10;
cursor : move;
display : flex;
flex-direction : column;
align-items : center;
}
#file-input {
display : none;
}
.file-input-label, .lyrics-input-label {
width : 40px;
height : 40px;
font-size : 18px;
display : flex;
justify-content : center;
align-items : center;
background-color : rgba ( 128, 128, 128, 0.8) ;
color : white;
border-radius : 50%;
cursor : pointer;
transition : all 0.2s;
box-shadow : 0 2px 5px rgba ( 0, 0, 0, 0.2) ;
margin-bottom : 10px;
}
.file-input-label:hover, .lyrics-input-label:hover {
background-color : rgba ( 160, 160, 160, 0.9) ;
transform : scale ( 1.1) ;
box-shadow : 0 4px 8px rgba ( 0, 0, 0, 0.3) ;
}
.file-input-label::after, .lyrics-input-label::after {
content : none;
}
.file-input-label:hover::after, .lyrics-input-label:hover::after {
opacity : 1;
}
#particle-container, #blur-overlay, #visualizer {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
}
#blur-overlay {
backdrop-filter : blur ( 5px) ;
z-index : 1;
}
#visualizer {
z-index : 2;
}
#album-cover:hover {
transform : scale ( 1.05) ;
box-shadow : 0 15px 35px rgba ( 0, 0, 0, 0.4) ;
}
@keyframes rotate {
from { transform : rotate ( 0deg) ; }
to { transform : rotate ( 360deg) ; }
}
#progress-bar {
position : absolute;
top : 0;
left : 0;
height : 100%;
background-color : rgba ( 29, 185, 84, 0.5) ;
z-index : 3;
transition : width 0.1s linear;
}
#time-info {
display : flex;
justify-content : space-between;
width : 100%;
margin : 5px 0;
font-size : 12px;
opacity : 0.8;
}
#controls {
display : flex;
justify-content : space-between;
align-items : center;
margin-top : 15px;
}
.control-btn:hover {
transform : scale ( 1.2) ;
opacity : 1;
}
.main-btn {
font-size : 40px;
opacity : 1;
}
#file-input-container {
position : absolute;
bottom : 20px;
right : 20px;
z-index : 10;
}
#file-input {
display : none;
}
.file-input-label {
width : 40px;
height : 40px;
font-size : 18px;
}
#cover-lyrics-container {
position : relative;
width : 220px;
height : 220px;
margin : 20px auto;
cursor : pointer;
}
#album-cover, #lyrics {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
border-radius : 50%;
transition : all 0.3s ease;
}
#lyrics {
background-color : rgba ( 0, 0, 0, 0.7) ;
color : white;
display : flex;
align-items : center;
justify-content : center;
text-align : center;
padding : 10px;
overflow-y : auto;
font-size : 14px;
}
.lyrics-input-label {
width : 40px;
height : 40px;
font-size : 18px;
display : flex;
justify-content : center;
align-items : center;
background-color : rgba ( 29, 185, 84, 0.8) ;
color : white;
border-radius : 50%;
cursor : pointer;
transition : all 0.2s;
box-shadow : 0 2px 5px rgba ( 0, 0, 0, 0.2) ;
margin-top : 10px;
}
.lyrics-input-label:hover {
background-color : rgba ( 30, 215, 96, 0.9) ;
transform : scale ( 1.1) ;
box-shadow : 0 4px 8px rgba ( 0, 0, 0, 0.3) ;
}
#particles-js {
position : fixed;
top : 0;
left : 0;
width : 100%;
height : 100%;
z-index : -1;
}