Spaces:
Running
Running
<html> | |
<head> | |
<title>Music Player</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style> | |
:root { | |
--primary: #6200ee; | |
--surface: #fff; | |
--on-surface: #121212; | |
--accent: #03dac6; | |
--error: #b00020; | |
} | |
* { | |
box-sizing: border-box; | |
-webkit-tap-highlight-color: transparent; | |
margin: 0; | |
padding: 0; | |
} | |
body { | |
font-family: -apple-system, system-ui, BlinkMacSystemFont; | |
//background: #fafafa; | |
color: var(--on-surface); | |
line-height: 1.5; | |
} | |
.sw-container { | |
padding: 0px; | |
max-width: 600px; | |
margin: 0 auto; | |
} | |
.sw-player { | |
background: var(--surface); | |
border-radius: 0px; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
padding: 10px; | |
margin-bottom: 16px; | |
} | |
.sw-header { | |
font-size: 1.25rem; | |
font-weight: 600; | |
text-align: center; | |
margin-bottom: 20px; | |
} | |
.sw-audio { | |
width: 100%; | |
height: 48px; | |
margin: 16px 0; | |
} | |
.sw-btn-group { | |
display: grid; | |
grid-template-columns: repeat(2, 1fr); | |
gap: 12px; | |
margin: 16px 0; | |
} | |
.sw-btn { | |
background: var(--primary); | |
color: white; | |
border: none; | |
border-radius: 24px; | |
padding: 12px 24px; | |
font-size: 0.9rem; | |
font-weight: 500; | |
text-transform: uppercase; | |
letter-spacing: 0.5px; | |
touch-action: manipulation; | |
transition: transform 0.2s; | |
} | |
.sw-btn.active { | |
transform: scale(0.96); | |
background: #7722ff; | |
position: relative; | |
order: 2px solid var(--primary); | |
} | |
.sw-dropdown { | |
width: 100%; | |
padding: 12px; | |
border: 2px solid #e0e0e0; | |
border-radius: 12px; | |
font-size: 1rem; | |
margin-bottom: 16px; | |
} | |
.sw-tracklist { | |
margin-top: 20px; | |
} | |
.sw-track { | |
padding: 16px; | |
margin: 8px 0; | |
border-radius: 12px; | |
background: #f5f5f5; | |
transition: background 0.2s; | |
font-size: 0.85rem; /* Added this line to reduce font size */ | |
} | |
.sw-track.active { | |
background: #e8f5ff; | |
border-left: 4px solid var(--primary); | |
} | |
.sw-modal { | |
position: fixed; | |
inset: 0; | |
background: rgba(0,0,0,0.6); | |
display: none; | |
align-items: center; | |
justify-content: center; | |
padding: 16px; | |
} | |
.sw-modal-content { | |
background: var(--surface); | |
width: 100%; | |
max-width: 400px; | |
border-radius: 16px; | |
padding: 24px; | |
} | |
.sw-input { | |
width: 100%; | |
padding: 12px; | |
border: 2px solid #e0e0e0; | |
border-radius: 12px; | |
font-size: 1rem; | |
margin: 16px 0; | |
} | |
.sw-checkbox-list { | |
max-height: 300px; | |
overflow-y: auto; | |
margin: 16px 0; | |
border: 1px solid #e0e0e0; | |
border-radius: 12px; | |
} | |
.sw-checkbox-item { | |
padding: 12px 16px; | |
border-bottom: 1px solid #e0e0e0; | |
} | |
.sw-now-playing { | |
text-align: center; | |
font-size: 0.9rem; | |
color: #666; | |
margin-bottom: 16px; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
.sw-now-playing span { | |
font-weight: 500; | |
color: var(--primary); | |
} | |
@media (hover: hover) { | |
.sw-btn:hover { | |
background: #7722ff; | |
} | |
.sw-btn.active:hover { | |
background: #6200ee; | |
} | |
.sw-track { | |
padding: 20px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="sw-container"> | |
<div class="sw-player"> | |
<h1 class="sw-header">🎵 Music Player</h1> | |
<div class="sw-now-playing"><span id="swCurrentSong">No song selected</span></div> | |
<audio id="swAudio" class="sw-audio" controls preload="none" autoplay="false"> | |
Your browser does not support audio playback. | |
</audio> | |
<div class="sw-btn-group"> | |
<button class="sw-btn" onclick="swPrevious()">Previous</button> | |
<button class="sw-btn" onclick="swNext()">Next</button> | |
<button class="sw-btn" id="swLoopBtn" onclick="swToggleLoop()">🔁 Loop</button> | |
<button class="sw-btn" id="swShuffleBtn" onclick="swToggleShuffle()">🔀 Shuffle</button> | |
</div> | |
<select class="sw-dropdown" id="swPlaylistSelect" onchange="swChangePlaylist()"> | |
<option value="all">All Songs</option> | |
</select> | |
<div class="sw-btn-group"> | |
<button class="sw-btn" onclick="swShowPlaylistModal()">Create Playlist</button> | |
<button class="sw-btn" onclick="swDeletePlaylist()">Delete Playlist</button> | |
</div> | |
<!-- Rendered by the JavaScript swUpdateTracks() --> | |
<div class="sw-tracklist"></div> | |
</div> | |
</div> | |
<div class="sw-modal" id="swPlaylistModal"> | |
<div class="sw-modal-content"> | |
<h2>Create New Playlist</h2> | |
<input type="text" class="sw-input" id="swPlaylistName" placeholder="Playlist Name"> | |
<div class="sw-checkbox-list" id="swSongCheckboxes"></div> | |
<div class="sw-btn-group"> | |
<button class="sw-btn" onclick="swCreatePlaylist()">Save</button> | |
<button class="sw-btn" onclick="swCloseModal()">Cancel</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
const swAudio = document.getElementById('swAudio'); | |
swAudio.addEventListener('ended', swNext); | |
const swTracks = {{ audio_files|tojson|safe }}; | |
let swCurrentIndex = 0; | |
function swPlay(file) { | |
const currentPlaylistTracks = swGetCurrentTracks(); | |
const idx = currentPlaylistTracks.indexOf(file); | |
if (idx < 0) { | |
console.error("File not found in current playlist:", file); | |
return; | |
} | |
swCurrentIndex = idx; | |
swAudio.src = `/audio/${file}`; | |
swAudio.play(); | |
document.getElementById('swCurrentSong').textContent = file; | |
swUpdateTracks(); | |
} | |
function swNext() { | |
const currentPlaylistTracks = swGetCurrentTracks(); | |
if (swIsShuffling) { | |
const currentShuffleIndex = swShuffledIndices.indexOf(swCurrentIndex); | |
if (currentShuffleIndex < swShuffledIndices.length - 1) { | |
swCurrentIndex = swShuffledIndices[currentShuffleIndex + 1]; | |
swPlay(currentPlaylistTracks[swCurrentIndex]); | |
} else if (swIsLooping) { | |
generateShuffledIndices(); | |
swCurrentIndex = swShuffledIndices[0]; | |
swPlay(currentPlaylistTracks[swCurrentIndex]); | |
} | |
} else { | |
if (swCurrentIndex < currentPlaylistTracks.length - 1) { | |
swCurrentIndex++; | |
swPlay(currentPlaylistTracks[swCurrentIndex]); | |
} else if (swIsLooping) { | |
swCurrentIndex = 0; | |
swPlay(currentPlaylistTracks[swCurrentIndex]); | |
} | |
} | |
} | |
function swPrevious() { | |
const currentPlaylistTracks = swGetCurrentTracks(); | |
if (swCurrentIndex > 0) { | |
swCurrentIndex--; | |
swPlay(currentPlaylistTracks[swCurrentIndex]); | |
} | |
} | |
function swLoadPlaylists() { | |
const select = document.getElementById('swPlaylistSelect'); | |
select.innerHTML = '<option value="all">All Songs</option>'; | |
Object.keys(swPlaylists).forEach(name => { | |
select.innerHTML += `<option value="${name}">${name}</option>`; | |
}); | |
} | |
function swShowPlaylistModal() { | |
const modal = document.getElementById('swPlaylistModal'); | |
const checkboxes = document.getElementById('swSongCheckboxes'); | |
checkboxes.innerHTML = swTracks.map(song => | |
`<div class="sw-checkbox-item"> | |
<input type="checkbox" id="${song}" value="${song}"> | |
<label for="${song}">${song}</label> | |
</div>` | |
).join(''); | |
modal.style.display = 'flex'; | |
} | |
function swCloseModal() { | |
document.getElementById('swPlaylistModal').style.display = 'none'; | |
} | |
function swCreatePlaylist() { | |
const name = document.getElementById('swPlaylistName').value; | |
if (!name) return alert('Please enter playlist name'); | |
const songs = Array.from(document.querySelectorAll('#swSongCheckboxes input:checked')) | |
.map(cb => cb.value); | |
if (!songs.length) return alert('Select at least one song'); | |
swPlaylists[name] = songs; | |
localStorage.setItem('swPlaylists', JSON.stringify(swPlaylists)); | |
swLoadPlaylists(); | |
swCloseModal(); | |
} | |
function swChangePlaylist() { | |
swCurrentPlaylist = document.getElementById('swPlaylistSelect').value; | |
swCurrentIndex = 0; | |
if (swIsShuffling) { | |
generateShuffledIndices(); | |
} | |
swUpdateTracks(); | |
if (swGetCurrentTracks().length > 0) { | |
swPlay(swGetCurrentTracks()[0]); | |
} | |
} | |
function swDeletePlaylist() { | |
if (swCurrentPlaylist === 'all') return; | |
delete swPlaylists[swCurrentPlaylist]; | |
localStorage.setItem('swPlaylists', JSON.stringify(swPlaylists)); | |
swCurrentPlaylist = 'all'; | |
swLoadPlaylists(); | |
swChangePlaylist(); | |
} | |
function swGetCurrentTracks() { | |
return swCurrentPlaylist === 'all' ? swTracks : swPlaylists[swCurrentPlaylist]; | |
} | |
function swUpdateTracks() { | |
const tracks = swGetCurrentTracks(); | |
const trackList = document.querySelector('.sw-tracklist'); | |
trackList.innerHTML = tracks.map((file, i) => | |
`<div class="sw-track ${i === swCurrentIndex ? 'active' : ''}" onclick="swPlay('${file}')"> | |
${file} | |
</div>` | |
).join(''); | |
} | |
const swPlaylists = JSON.parse(localStorage.getItem('swPlaylists') || '{}'); | |
let swCurrentPlaylist = 'all'; | |
document.addEventListener('DOMContentLoaded', function() { | |
swLoadPlaylists(); | |
swUpdateTracks(); | |
if (swTracks.length > 0) { | |
swPlay(swTracks[0]); | |
} else { | |
document.getElementById('swCurrentSong').textContent = 'No song selected'; | |
} | |
const audioElements = document.getElementsByTagName('audio'); | |
for (let audio of audioElements) { | |
audio.autoplay = false; | |
audio.pause(); | |
audio.currentTime = 0; | |
} | |
}); | |
// State variables | |
let swIsLooping = false; | |
let swIsShuffling = false; | |
let swShuffledIndices = []; | |
// Load saved preferences | |
document.addEventListener('DOMContentLoaded', function() { | |
// ... existing code ... | |
swIsLooping = JSON.parse(localStorage.getItem('swIsLooping') || 'false'); | |
swIsShuffling = JSON.parse(localStorage.getItem('swIsShuffling') || 'false'); | |
updateControlButtons(); | |
}); | |
function swToggleLoop() { | |
swIsLooping = !swIsLooping; | |
localStorage.setItem('swIsLooping', swIsLooping); | |
updateControlButtons(); | |
} | |
function swToggleShuffle() { | |
swIsShuffling = !swIsShuffling; | |
localStorage.setItem('swIsShuffling', swIsShuffling); | |
if (swIsShuffling) { | |
generateShuffledIndices(); | |
} | |
updateControlButtons(); | |
} | |
function updateControlButtons() { | |
document.getElementById('swLoopBtn').classList.toggle('active', swIsLooping); | |
document.getElementById('swShuffleBtn').classList.toggle('active', swIsShuffling); | |
} | |
function generateShuffledIndices() { | |
const tracks = swGetCurrentTracks(); | |
swShuffledIndices = Array.from({length: tracks.length}, (_, i) => i); | |
// Fisher-Yates shuffle | |
for (let i = swShuffledIndices.length - 1; i > 0; i--) { | |
const j = Math.floor(Math.random() * (i + 1)); | |
[swShuffledIndices[i], swShuffledIndices[j]] = [swShuffledIndices[j], swShuffledIndices[i]]; | |
} | |
} | |
</script> | |
</body> | |
</html> | |