|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
|
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>LRC & YouTube Player</title> |
|
<style> |
|
|
|
body { |
|
font-family: Arial, sans-serif; |
|
max-width: 800px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
background-color: #f5f5f5; |
|
} |
|
h1 { |
|
color: #333; |
|
text-align: center; |
|
} |
|
#songList { |
|
list-style: none; |
|
padding: 0; |
|
background: white; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
margin-top: 20px; |
|
} |
|
#songList li { |
|
cursor: pointer; |
|
padding: 12px 20px; |
|
margin: 0; |
|
border-bottom: 1px solid #eee; |
|
transition: background-color 0.2s; |
|
} |
|
#songList li:last-child { |
|
border-bottom: none; |
|
} |
|
#songList li:hover { |
|
background-color: #f0f0f0; |
|
} |
|
#playerContainer { |
|
margin-top: 20px; |
|
display: none; |
|
background: white; |
|
padding: 20px; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
} |
|
#youtubePlayer { |
|
width: 100%; |
|
max-width: 640px; |
|
height: 360px; |
|
margin: 0 auto; |
|
display: block; |
|
} |
|
#lyricsDisplay { |
|
margin-top: 20px; |
|
white-space: pre-wrap; |
|
border: 1px solid #ddd; |
|
padding: 15px; |
|
border-radius: 4px; |
|
overflow-y: auto; |
|
height: 300px; |
|
font-size: 16px; |
|
line-height: 1.6; |
|
background: #fff; |
|
} |
|
.highlighted-lyric { |
|
background-color: #fff3cd; |
|
padding: 2px 5px; |
|
border-radius: 3px; |
|
transition: background-color 0.3s; |
|
} |
|
#errorDisplay { |
|
color: red; |
|
margin: 10px 0; |
|
padding: 10px; |
|
border: 1px solid red; |
|
display: none; |
|
} |
|
#manualInput { |
|
width: 100%; |
|
height: 100px; |
|
margin: 10px 0; |
|
padding: 10px; |
|
border: 1px solid #ddd; |
|
border-radius: 4px; |
|
font-family: monospace; |
|
} |
|
#processButton { |
|
background-color: #4CAF50; |
|
color: white; |
|
padding: 10px 20px; |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
font-size: 16px; |
|
} |
|
#processButton:hover { |
|
background-color: #45a049; |
|
} |
|
.input-section { |
|
background: white; |
|
padding: 20px; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
margin-bottom: 20px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<h1>LRC & YouTube Player</h1> |
|
<div id="errorDisplay"></div> |
|
|
|
<div class="input-section"> |
|
<h3>Data Input (filename.lrc|youtubeURL format)</h3> |
|
<textarea id="manualInput" placeholder="Enter data in format: filename1.lrc|https://youtube.com/watch?v=... filename2.lrc|https://youtube.com/watch?v=..."></textarea> |
|
<button id="processButton">Process Input</button> |
|
</div> |
|
|
|
<ul id="songList"></ul> |
|
<div id="playerContainer"> |
|
<div id="youtubePlayer"></div> |
|
<div id="lyricsDisplay"></div> |
|
</div> |
|
|
|
<script> |
|
let player; |
|
let currentLyrics = []; |
|
let currentLyricIndex = -1; |
|
let syncInterval; |
|
|
|
function showError(message) { |
|
const errorDisplay = document.getElementById('errorDisplay'); |
|
errorDisplay.textContent = message; |
|
errorDisplay.style.display = 'block'; |
|
console.error(message); |
|
} |
|
|
|
|
|
console.log('Starting to load data.txt...'); |
|
fetch('data.txt') |
|
.then(response => { |
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); |
|
return response.text(); |
|
}) |
|
.then(data => { |
|
document.getElementById('manualInput').value = data; |
|
processInput(data); |
|
}) |
|
.catch(error => { |
|
console.log('Could not load data.txt, waiting for manual input'); |
|
}); |
|
|
|
|
|
document.getElementById('processButton').onclick = () => { |
|
const input = document.getElementById('manualInput').value; |
|
processInput(input); |
|
}; |
|
|
|
function processInput(data) { |
|
console.log('Processing input data'); |
|
const songList = document.getElementById('songList'); |
|
songList.innerHTML = ''; |
|
|
|
if (!data.trim()) { |
|
showError('No data provided'); |
|
return; |
|
} |
|
|
|
data.trim().split('\n').forEach((line, index) => { |
|
console.log(`Processing line ${index}:`, line); |
|
const [filename, youtubeLink] = line.split('|'); |
|
if (!filename || !youtubeLink) { |
|
showError(`Invalid line format: ${line}`); |
|
return; |
|
} |
|
|
|
const li = document.createElement('li'); |
|
li.textContent = filename.replace('.lrc', ''); |
|
li.dataset.filename = filename; |
|
li.dataset.youtubeLink = youtubeLink.trim(); |
|
li.onclick = () => loadSongFromFile(filename, youtubeLink.trim()); |
|
songList.appendChild(li); |
|
}); |
|
} |
|
|
|
function loadSongFromFile(filename, youtubeLink) { |
|
console.log('Attempting to load LRC file:', filename); |
|
fetch(filename) |
|
.then(response => { |
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); |
|
return response.text(); |
|
}) |
|
.then(lrcContent => { |
|
loadSong(filename, youtubeLink, lrcContent); |
|
}) |
|
.catch(error => { |
|
console.error('Error loading LRC file:', error); |
|
showError(`Could not load ${filename}. Make sure the file exists in the same directory.`); |
|
}); |
|
} |
|
|
|
|
|
const tag = document.createElement('script'); |
|
tag.src = "https://www.youtube.com/iframe_api"; |
|
document.head.appendChild(tag); |
|
|
|
window.onYouTubeIframeAPIReady = function() { |
|
console.log('YouTube IFrame API Ready'); |
|
player = new YT.Player('youtubePlayer', { |
|
height: '360', |
|
width: '640', |
|
videoId: '', |
|
events: { |
|
'onReady': onPlayerReady, |
|
'onStateChange': onPlayerStateChange, |
|
'onError': onPlayerError |
|
} |
|
}); |
|
}; |
|
|
|
function onPlayerReady(event) { |
|
console.log('YouTube player ready'); |
|
} |
|
|
|
function onPlayerError(event) { |
|
console.error('YouTube player error:', event.data); |
|
showError('Error loading YouTube video'); |
|
} |
|
|
|
function loadSong(filename, youtubeLink, lrcContent) { |
|
console.log('Loading song:', filename, youtubeLink); |
|
|
|
const videoId = youtubeLink.match(/[?&]v=([^&]+)/)?.[1] || |
|
youtubeLink.match(/youtu\.be\/([^?]+)/)?.[1]; |
|
|
|
if (!videoId) { |
|
showError('Invalid YouTube URL'); |
|
return; |
|
} |
|
|
|
try { |
|
currentLyrics = parseLRC(lrcContent); |
|
console.log('Parsed lyrics:', currentLyrics); |
|
displayLyrics(); |
|
currentLyricIndex = -1; |
|
|
|
if (player && player.loadVideoById) { |
|
console.log('Loading YouTube video:', videoId); |
|
player.loadVideoById(videoId); |
|
document.getElementById('playerContainer').style.display = 'block'; |
|
} else { |
|
showError('YouTube player not ready'); |
|
} |
|
} catch (error) { |
|
showError(`Error processing lyrics: ${error.message}`); |
|
} |
|
} |
|
|
|
function parseLRC(text) { |
|
const lines = text.split('\n'); |
|
const lyrics = []; |
|
const timeRegex = /\[(\d+):(\d+)\.(\d+)\](.*)/; |
|
|
|
lines.forEach((line, index) => { |
|
const match = line.match(timeRegex); |
|
if (match) { |
|
const minutes = parseInt(match[1]); |
|
const seconds = parseInt(match[2]); |
|
const milliseconds = parseInt(match[3]); |
|
const time = minutes * 60 + seconds + milliseconds / 1000; |
|
const text = match[4].trim(); |
|
if (text) { |
|
lyrics.push({ time, text }); |
|
} |
|
} |
|
}); |
|
|
|
return lyrics.sort((a, b) => a.time - b.time); |
|
} |
|
|
|
function displayLyrics() { |
|
const container = document.getElementById('lyricsDisplay'); |
|
container.innerHTML = currentLyrics |
|
.map((lyric, index) => `<p data-index="${index}">${lyric.text}</p>`) |
|
.join(''); |
|
} |
|
|
|
function syncLyrics() { |
|
if (!player || !player.getCurrentTime) return; |
|
|
|
const currentTime = player.getCurrentTime(); |
|
let index = currentLyrics.findIndex(lyric => lyric.time > currentTime) - 1; |
|
|
|
if (index !== currentLyricIndex) { |
|
const prevHighlight = document.querySelector('.highlighted-lyric'); |
|
if (prevHighlight) { |
|
prevHighlight.classList.remove('highlighted-lyric'); |
|
} |
|
|
|
if (index >= 0) { |
|
const element = document.querySelector(`p[data-index="${index}"]`); |
|
if (element) { |
|
element.classList.add('highlighted-lyric'); |
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
|
} |
|
} |
|
currentLyricIndex = index; |
|
} |
|
} |
|
|
|
function onPlayerStateChange(event) { |
|
console.log('Player state changed:', event.data); |
|
if (event.data === YT.PlayerState.PLAYING) { |
|
if (syncInterval) clearInterval(syncInterval); |
|
syncInterval = setInterval(syncLyrics, 100); |
|
} else if (event.data === YT.PlayerState.PAUSED || event.data === YT.PlayerState.ENDED) { |
|
if (syncInterval) clearInterval(syncInterval); |
|
} |
|
} |
|
</script> |
|
</body> |
|
</html> |
|
|