Skip to content

Commit dce35a4

Browse files
committed
Better demo
1 parent 2f5f0c9 commit dce35a4

File tree

1 file changed

+282
-32
lines changed

1 file changed

+282
-32
lines changed

index.html

Lines changed: 282 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,305 @@
11
<!DOCTYPE html>
2-
<html>
3-
2+
<html lang="en">
43
<head>
5-
<title></title>
6-
<style type="text/css">
7-
html,
8-
body {
9-
width: 600px;
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Gapless.js Demo - Seamless Audio Playback</title>
7+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
8+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
9+
<style>
10+
@keyframes pulse-ring {
11+
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
12+
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
13+
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
1014
}
15+
.playing-indicator { animation: pulse-ring 2s ease-in-out infinite; }
1116

12-
.loaded {
13-
color: green;
17+
@keyframes soundwave {
18+
0%, 100% { height: 5px; }
19+
50% { height: 20px; }
20+
}
21+
.soundwave-bar {
22+
width: 3px;
23+
background-color: #3b82f6;
24+
margin: 0 1px;
25+
transition: height 0.1s ease;
1426
}
27+
.soundwave-bar:nth-child(1) { animation: soundwave 0.5s ease-in-out infinite; }
28+
.soundwave-bar:nth-child(2) { animation: soundwave 0.5s ease-in-out 0.1s infinite; }
29+
.soundwave-bar:nth-child(3) { animation: soundwave 0.5s ease-in-out 0.2s infinite; }
30+
.soundwave-bar:nth-child(4) { animation: soundwave 0.5s ease-in-out 0.3s infinite; }
31+
.soundwave-bar:nth-child(5) { animation: soundwave 0.5s ease-in-out 0.4s infinite; }
32+
33+
.track-item { transition: all 0.3s ease; }
34+
.track-item:hover { transform: translateX(5px); }
35+
36+
.progress-bar { transition: width 0.1s linear; }
1537
</style>
1638
</head>
39+
<body class="bg-gray-900 text-white min-h-screen">
40+
<div class="max-w-4xl mx-auto p-6">
41+
<!-- Header -->
42+
<header class="text-center mb-10">
43+
<h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent">
44+
Gapless.js Demo
45+
</h1>
46+
<p class="text-gray-400">Experience seamless audio playback with zero gaps between tracks</p>
47+
</header>
48+
49+
<!-- Main Player -->
50+
<div class="bg-gray-800 rounded-2xl shadow-2xl p-8 mb-6">
51+
<!-- Current Track Info -->
52+
<div class="text-center mb-6">
53+
<h2 id="currentTrackTitle" class="text-2xl font-semibold mb-2">No track loaded</h2>
54+
<div id="trackStatus" class="text-sm text-gray-400 mb-4">
55+
<span id="currentTime">0:00</span> / <span id="duration">0:00</span>
56+
</div>
57+
</div>
58+
59+
<!-- Visualizer -->
60+
<div class="flex justify-center items-center h-12 mb-6" id="visualizer">
61+
<div class="flex items-end h-full" id="soundwaveBars" style="display: none;">
62+
<div class="soundwave-bar"></div>
63+
<div class="soundwave-bar"></div>
64+
<div class="soundwave-bar"></div>
65+
<div class="soundwave-bar"></div>
66+
<div class="soundwave-bar"></div>
67+
</div>
68+
</div>
69+
70+
<!-- Progress Bar -->
71+
<div class="mb-6">
72+
<div class="bg-gray-700 rounded-full h-3 overflow-hidden cursor-pointer" id="progressContainer">
73+
<div class="progress-bar bg-gradient-to-r from-blue-500 to-purple-500 h-full rounded-full" id="progressBar" style="width: 0%"></div>
74+
</div>
75+
</div>
1776

18-
<body>
19-
<h4>
20-
You'll want to wait until WebAudio is loaded for both the current track and the next track before jumping to the
21-
end. Otherwise you may notice a slight gap. If both WebAudios are fully loaded you should hear no discernable gap
22-
most of the time.
23-
</h4>
24-
25-
<div onClick="player.currentTrack.seekToEnd()">Click to jump to end of track</div>
26-
<div onClick="player.currentTrack.togglePlayPause()">Toggle Play/Pause</div>
27-
<pre id="ready">Both tracks are not loaded</pre>
28-
<pre id="status"></pre>
29-
<script type="text/javascript" src="./dist/cjs/index.cjs" type="module"></script>
77+
<!-- Controls -->
78+
<div class="flex justify-center items-center space-x-6 mb-6">
79+
<button id="prevBtn" class="p-3 rounded-full bg-gray-700 hover:bg-gray-600 transition-colors">
80+
<i class="fas fa-backward text-xl"></i>
81+
</button>
82+
<button id="playPauseBtn" class="p-4 rounded-full bg-blue-600 hover:bg-blue-700 transition-colors playing-indicator">
83+
<i class="fas fa-play text-2xl w-8 text-center" id="playPauseIcon"></i>
84+
</button>
85+
<button id="nextBtn" class="p-3 rounded-full bg-gray-700 hover:bg-gray-600 transition-colors">
86+
<i class="fas fa-forward text-xl"></i>
87+
</button>
88+
</div>
89+
90+
<!-- Volume Control -->
91+
<div class="flex items-center justify-center space-x-3">
92+
<i class="fas fa-volume-down text-gray-400"></i>
93+
<input type="range" id="volumeSlider" min="0" max="100" value="70"
94+
class="w-32 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
95+
<i class="fas fa-volume-up text-gray-400"></i>
96+
</div>
97+
</div>
98+
99+
<!-- Playlist -->
100+
<div class="bg-gray-800 rounded-2xl shadow-2xl p-6">
101+
<h3 class="text-xl font-semibold mb-4 flex items-center">
102+
<i class="fas fa-list-music mr-2"></i> Playlist
103+
</h3>
104+
<div id="playlist" class="space-y-2">
105+
<!-- Tracks will be inserted here -->
106+
</div>
107+
</div>
108+
109+
<!-- Status Information -->
110+
<div class="mt-6 bg-gray-800 rounded-2xl shadow-2xl p-6">
111+
<h3 class="text-xl font-semibold mb-4 flex items-center">
112+
<i class="fas fa-info-circle mr-2"></i> Technical Status
113+
</h3>
114+
<div class="grid grid-cols-2 gap-4 text-sm">
115+
<div>
116+
<span class="text-gray-400">WebAudio Status:</span>
117+
<span id="webAudioStatus" class="ml-2 font-mono">Checking...</span>
118+
</div>
119+
<div>
120+
<span class="text-gray-400">Buffer Status:</span>
121+
<span id="bufferStatus" class="ml-2 font-mono">N/A</span>
122+
</div>
123+
<div>
124+
<span class="text-gray-400">Current Track:</span>
125+
<span id="currentTrackIdx" class="ml-2 font-mono">N/A</span>
126+
</div>
127+
<div>
128+
<span class="text-gray-400">Next Track Loaded:</span>
129+
<span id="nextTrackLoaded" class="ml-2 font-mono">N/A</span>
130+
</div>
131+
</div>
132+
</div>
133+
</div>
134+
135+
<script type="text/javascript" src="./dist/cjs/index.cjs"></script>
30136
<script type="text/javascript">
137+
// Track metadata
138+
const trackMetadata = [
139+
{ title: "Electric Dreams", artist: "JRAD", album: "Live 2017-03-17" },
140+
{ title: "Cosmic Highway", artist: "JRAD", album: "Live 2017-03-17" },
141+
{ title: "Starlight Serenade", artist: "JRAD", album: "Live 2017-03-17" },
142+
{ title: "Neon Nights", artist: "JRAD", album: "Live 2017-03-17" }
143+
];
144+
145+
// Initialize player
31146
window.player = new Gapless.Queue({
32147
tracks: [
33148
"https://archive.org/download/jrad2017-03-17.cmc621.cmc64.sbd.matrix.flac16/jrad2017-03-17.cmc621.cmc64.sbd.matrix-s2t03.mp3",
34149
"https://archive.org/download/jrad2017-03-17.cmc621.cmc64.sbd.matrix.flac16/jrad2017-03-17.cmc621.cmc64.sbd.matrix-s2t04.mp3",
35150
"https://archive.org/download/jrad2017-03-17.cmc621.cmc64.sbd.matrix.flac16/jrad2017-03-17.cmc621.cmc64.sbd.matrix-s2t05.mp3",
36151
"https://archive.org/download/jrad2017-03-17.cmc621.cmc64.sbd.matrix.flac16/jrad2017-03-17.cmc621.cmc64.sbd.matrix-s2t06.mp3"
37152
],
38-
onProgress: function (track) {
39-
if (player.nextTrack && player.nextTrack.webAudioLoadingState === 'LOADED') {
40-
document.querySelector('#ready').classList.add('loaded');
41-
document.querySelector('#ready').innerHTML = 'Both tracks are LOADED';
42-
}
43-
document.querySelector('#status').innerHTML = JSON.stringify({
44-
currentTrack: track ? track.completeState : {},
45-
nextTrack: player.nextTrack ? player.nextTrack.completeState : {}
46-
}, null, 2);
153+
onProgress: updateProgress,
154+
onPlayNextTrack: updateTrackDisplay,
155+
onPlayPreviousTrack: updateTrackDisplay,
156+
onStartNewTrack: updateTrackDisplay,
157+
onEnded: () => {
158+
console.log('Playlist ended');
159+
updatePlayPauseButton(false);
47160
}
48161
});
49162

50-
player.play();
163+
// Add metadata to tracks
164+
player.tracks.forEach((track, idx) => {
165+
track.metadata = trackMetadata[idx];
166+
});
167+
168+
// DOM elements
169+
const playPauseBtn = document.getElementById('playPauseBtn');
170+
const playPauseIcon = document.getElementById('playPauseIcon');
171+
const prevBtn = document.getElementById('prevBtn');
172+
const nextBtn = document.getElementById('nextBtn');
173+
const progressBar = document.getElementById('progressBar');
174+
const progressContainer = document.getElementById('progressContainer');
175+
const currentTimeEl = document.getElementById('currentTime');
176+
const durationEl = document.getElementById('duration');
177+
const volumeSlider = document.getElementById('volumeSlider');
178+
const soundwaveBars = document.getElementById('soundwaveBars');
179+
const currentTrackTitle = document.getElementById('currentTrackTitle');
180+
const playlist = document.getElementById('playlist');
181+
182+
// Event listeners
183+
playPauseBtn.addEventListener('click', () => {
184+
player.togglePlayPause();
185+
updatePlayPauseButton(!player.currentTrack?.isPaused);
186+
});
187+
188+
prevBtn.addEventListener('click', () => player.playPrevious());
189+
nextBtn.addEventListener('click', () => player.playNext());
190+
191+
volumeSlider.addEventListener('input', (e) => {
192+
player.setVolume(e.target.value / 100);
193+
});
51194

195+
progressContainer.addEventListener('click', (e) => {
196+
if (player.currentTrack && player.currentTrack.duration) {
197+
const rect = progressContainer.getBoundingClientRect();
198+
const x = e.clientX - rect.left;
199+
const percentage = x / rect.width;
200+
const newTime = percentage * player.currentTrack.duration;
201+
player.currentTrack.seek(newTime);
202+
}
203+
});
204+
205+
// Helper functions
206+
function formatTime(seconds) {
207+
if (!seconds || isNaN(seconds)) return '0:00';
208+
const mins = Math.floor(seconds / 60);
209+
const secs = Math.floor(seconds % 60);
210+
return `${mins}:${secs.toString().padStart(2, '0')}`;
211+
}
212+
213+
function updatePlayPauseButton(isPlaying) {
214+
playPauseIcon.className = isPlaying ? 'fas fa-pause text-2xl w-8 text-center' : 'fas fa-play text-2xl w-8 text-center';
215+
if (isPlaying) {
216+
playPauseBtn.classList.add('playing-indicator');
217+
soundwaveBars.style.display = 'flex';
218+
} else {
219+
playPauseBtn.classList.remove('playing-indicator');
220+
soundwaveBars.style.display = 'none';
221+
}
222+
}
223+
224+
function updateProgress(track) {
225+
if (!track) return;
226+
227+
// Update time display
228+
currentTimeEl.textContent = formatTime(track.currentTime);
229+
durationEl.textContent = formatTime(track.duration);
230+
231+
// Update progress bar
232+
if (track.duration) {
233+
const percentage = (track.currentTime / track.duration) * 100;
234+
progressBar.style.width = `${percentage}%`;
235+
}
236+
237+
// Update technical status
238+
document.getElementById('webAudioStatus').textContent = track.isUsingWebAudio ? 'Enabled' : 'Disabled';
239+
document.getElementById('webAudioStatus').className = track.isUsingWebAudio ? 'ml-2 font-mono text-green-400' : 'ml-2 font-mono text-yellow-400';
240+
241+
document.getElementById('bufferStatus').textContent = track.isLoaded ? 'Ready' : 'Loading...';
242+
document.getElementById('bufferStatus').className = track.isLoaded ? 'ml-2 font-mono text-green-400' : 'ml-2 font-mono text-yellow-400';
243+
244+
document.getElementById('currentTrackIdx').textContent = `${track.idx + 1} / ${player.tracks.length}`;
245+
246+
const nextTrackStatus = player.nextTrack?.isLoaded ? 'Yes' : 'No';
247+
document.getElementById('nextTrackLoaded').textContent = nextTrackStatus;
248+
document.getElementById('nextTrackLoaded').className = player.nextTrack?.isLoaded ? 'ml-2 font-mono text-green-400' : 'ml-2 font-mono text-yellow-400';
249+
}
250+
251+
function updateTrackDisplay(track) {
252+
if (!track) return;
253+
254+
// Update current track title
255+
const metadata = track.metadata || {};
256+
currentTrackTitle.textContent = metadata.title || `Track ${track.idx + 1}`;
257+
258+
// Update playlist highlighting
259+
renderPlaylist();
260+
261+
// Update play/pause button
262+
updatePlayPauseButton(!track.isPaused);
263+
}
264+
265+
function renderPlaylist() {
266+
playlist.innerHTML = '';
267+
player.tracks.forEach((track, idx) => {
268+
const metadata = track.metadata || {};
269+
const isActive = player.currentTrack?.idx === idx;
270+
271+
const trackEl = document.createElement('div');
272+
trackEl.className = `track-item p-3 rounded-lg cursor-pointer flex items-center space-x-3 ${
273+
isActive ? 'bg-blue-600 bg-opacity-20 border border-blue-500' : 'bg-gray-700 hover:bg-gray-600'
274+
}`;
275+
276+
trackEl.innerHTML = `
277+
<div class="text-2xl w-8 text-center">
278+
${isActive && !player.currentTrack.isPaused ? '<i class="fas fa-volume-up text-blue-400"></i>' : `<span class="text-gray-500">${idx + 1}</span>`}
279+
</div>
280+
<div class="flex-1">
281+
<div class="font-semibold">${metadata.title || `Track ${idx + 1}`}</div>
282+
<div class="text-sm text-gray-400">${metadata.artist || 'Unknown Artist'}</div>
283+
</div>
284+
<div class="text-sm text-gray-400">
285+
${track.duration ? formatTime(track.duration) : '--:--'}
286+
</div>
287+
`;
288+
289+
trackEl.addEventListener('click', () => {
290+
player.gotoTrack(idx, true);
291+
});
292+
293+
playlist.appendChild(trackEl);
294+
});
295+
}
296+
297+
// Initialize display
298+
renderPlaylist();
299+
player.setVolume(0.7);
300+
301+
// Auto-play on load
302+
player.play();
52303
</script>
53304
</body>
54-
55305
</html>

0 commit comments

Comments
 (0)