New browser based Music player

Apurva Singh

 File structure:

/songs:

    /xyz/

        xyz_cover.jpeg

        xyz.json

        xyz.mp3



HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Local Music Folder Player</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<h1>Local Music Player</h1>
<div id="status">Click anywhere to select your "songs" folder...</div>
<div class="song-list" id="songList"></div>
<div id="player-bar">
  <img id="now-cover" src="" alt="cover"/>
  <div id="now-info">
    <div id="now-title"></div>
    <div id="now-artist"></div>
  </div>
  <audio id="audio" controls></audio>
</div>
<script src="song.js"></script>
</body>
</html>




JAVASCRIPT




const status = document.getElementById('status');
const songList = document.getElementById('songList');
const playerBar = document.getElementById('player-bar');
const nowCover = document.getElementById('now-cover');
const nowTitle = document.getElementById('now-title');
const nowArtist = document.getElementById('now-artist');
const audio = document.getElementById('audio');

let currentSong = null;

document.addEventListener('click', initApp, { once: true });

async function initApp() {
  status.textContent = 'Opening folder picker...';

  let dirHandle;
  try {
    dirHandle = await window.showDirectoryPicker({ mode: 'read' });
  } catch (err) {
    status.textContent = 'Folder access cancelled or failed :(';
    return;
  }

  status.textContent = 'Scanning songs... please wait';

  const songs = [];

  for await (const entry of dirHandle.values()) {
    if (entry.kind !== 'directory') continue;

    let mp3 = null, json = null, cover = null;

    for await (const file of entry.values()) {
      if (file.kind !== 'file') continue;
      const name = file.name.toLowerCase();

      if (name.endsWith('.mp3')) mp3 = file;
      else if (name.endsWith('.json')) json = file;
      else if (/\.(jpg|jpeg|png|webp)$/i.test(name)) {
        const fname = file.name.toLowerCase();
        if (
          fname.includes('cover') ||
          fname.includes('folder') ||
          fname === `${entry.name.toLowerCase()}.jpg` ||
          fname === `${entry.name.toLowerCase()}.jpeg` ||
          fname === `${entry.name.toLowerCase()}.png` ||
          fname === `${entry.name.toLowerCase()}.webp`
        ) {
          cover = file;
        }
      }
    }

    if (!mp3 || !json) continue;

    try {
      const jsonFile = await json.getFile();
      const text = await jsonFile.text();
      const meta = JSON.parse(text);

      const song = {
        title: meta.title || entry.name,
        artist: meta.artist || 'Unknown Artist',
        mp3Handle: mp3,
        coverHandle: cover,
        folderName: entry.name
      };

      songs.push(song);
    } catch (e) {
      console.warn(`Bad JSON in ${entry.name}`, e);
    }
  }

  if (songs.length === 0) {
    status.innerHTML = '<span style="color:#f66">No valid songs found<br>(need both .mp3 + .json in each folder)</span>';
    return;
  }

  status.textContent = `Found ${songs.length} songs........ Click to play`;

  songList.innerHTML = '';

  for (const song of songs) {
    const el = document.createElement('div');
    el.className = 'song';
    el.innerHTML = `
      <img class="cover" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt="cover"/>
      <div class="info">
        <div class="title">${song.title}</div>
        <div class="artist">${song.artist}</div>
      </div>
    `;

    const img = el.querySelector('img');

   
    if (song.coverHandle) {
      try {
        const file = await song.coverHandle.getFile();
        img.src = URL.createObjectURL(file);
        song.coverUrl = img.src; // save for player
      } catch {}
    }

    el.onclick = () => playSong(song, el);

    songList.appendChild(el);
  }
}

async function playSong(song, element) {
  try {
    // Highlight current
    document.querySelectorAll('.song').forEach(s => s.classList.remove('playing'));
    element.classList.add('playing');

    const file = await song.mp3Handle.getFile();
    const url = URL.createObjectURL(file);

    audio.src = url;
    audio.play().catch(err => {
      alert('Playback failed: ' + err.message);
    });

    nowTitle.textContent = song.title;
    nowArtist.textContent = song.artist;

    if (song.coverUrl) {
      nowCover.src = song.coverUrl;
    } else {
      nowCover.src = 'https://via.placeholder.com/56/222/888?text=♪';
    }

    playerBar.classList.add('show');

    currentSong = song;

  } catch (err) {
    alert('Cannot load song: ' + err.message);
  }
}

// Cleanup old blob urls when page unloads
window.addEventListener('unload', () => {
  if (audio.src.startsWith('blob:')) {
    URL.revokeObjectURL(audio.src);
  }
});


Post a Comment

0Comments
Post a Comment (0)

#buttons=(I Understand) #days=(1)

Our website uses cookies to enhance your experience. Google Translate and other Google services on this are governed by Google's Privacy Policy. Some of the posts might have been fetched from Syndication Networks
Accept !