Skip to content

sidcraftscode/music-player

Repository files navigation

Music Player

A browser-based music player built as a Progressive Web App (PWA). You can import your own music files, organise them into playlists, and play them back with full playback controls. Everything runs locally in the browser with no server or backend required.

Screenshots

Songs Albums Artists
Songs Albums Artists
Playlists Settings Changelog
Playlists Settings Changelog

How It Works

The app is built with plain HTML, CSS, and JavaScript. There is no build step, no bundler, no framework. It uses native ES modules (import/export) so the browser handles all the module resolution itself. You just open index.html and it works.

Storage (IndexedDB)

All music data is stored in IndexedDB, which is basically a database that lives inside your browser. The database has five object stores:

  • tracks - metadata for each song (title, artist, album, duration, etc.)
  • files - the actual audio file blobs, stored by track ID
  • artwork - album art blobs, also stored by track ID
  • playlists - user-created playlists with arrays of track IDs
  • state - general app state like playback position

The reason we use IndexedDB instead of something like localStorage is that localStorage can only store strings and has a size limit of about 5-10MB. IndexedDB can store binary blobs (like audio files) and has a much higher storage limit, usually hundreds of megabytes or more depending on the browser. The MusicDB class in js/db.js wraps all the IndexedDB operations into simple async methods like addTrack(), getFile(), deleteTrack(), etc.

When you import a song, the app reads the file, extracts metadata (title, artist, album, artwork), and stores everything in IndexedDB. The original file on your computer is not referenced again after import. The audio data is fully copied into the browser's storage.

Service Worker and Offline Support

The file sw.js is a service worker that makes the app work offline. When you first load the app, the service worker installs itself and caches all the static assets (HTML, CSS, JS files, icons). On subsequent visits, even without internet, the service worker intercepts network requests and serves the cached files instead.

The caching strategy is:

  1. On install, cache all static assets listed in the STATIC_ASSETS array
  2. On fetch, try the cache first. If it is not cached, fetch from the network and cache the response for next time
  3. When the app updates, the cache name is bumped (e.g. music-v10 to music-v11), which triggers a new install and clears the old cache

There is also a "Check for Updates" button in Settings that manually unregisters the service worker and clears all caches, then reloads the page. This forces the browser to fetch everything fresh.

Audio Playback

The AudioEngine class in js/audio-engine.js handles all audio playback. It wraps the browser's built-in <audio> element and adds queue management, shuffle, and repeat logic on top.

When you play a song, the engine:

  1. Loads the audio blob from IndexedDB
  2. Creates an object URL from the blob using URL.createObjectURL()
  3. Sets that URL as the audio element's src
  4. Calls audio.play()

The engine maintains a queue (just an array of track IDs) and a current index. Shuffle works by keeping a copy of the original queue order and shuffling a separate array. Repeat has three modes: off, all (loop the queue), and one (loop the current track).

The engine uses an event emitter pattern. UI code subscribes to events like statechange, trackchange, and timeupdate, and updates the interface accordingly.

Metadata Parsing

When you import a music file, the app needs to extract the title, artist, album, track number, and cover art from the file itself. This is handled in js/metadata.js.

For MP3, M4A, FLAC, and similar formats, the app uses the jsmediatags library (loaded as a vendor script). This library can read ID3v1, ID3v2, and MP4 tag formats.

For OGG and Opus files, jsmediatags does not work because these formats use a completely different metadata system called Vorbis Comments. So there is a custom parser that reads the raw bytes of the OGG container, finds the second OGG page (which contains the comment header), and extracts the key-value pairs. Cover art in OGG files is stored as a base64-encoded FLAC picture block, which the parser also decodes.

Duration is always obtained separately by loading the file into a temporary <audio> element and reading its loadedmetadata event, since this is more reliable than trying to calculate it from the file bytes.

Folder Import

When importing a folder, the app uses a hidden <input type="file"> element with the webkitdirectory attribute. This tells the browser to let the user pick a directory instead of individual files. The browser then returns all files in that directory and its subdirectories recursively. The app filters these to only keep audio files (by checking the file extension) before importing them.

Project Structure

music-player/
  index.html              # main HTML, all the markup
  sw.js                   # service worker for offline caching
  manifest.json           # PWA manifest (app name, icons, theme)

  js/
    app.js                # entry point, boot logic, event bindings
    music-app.js          # MusicApp class definition
    db.js                 # MusicDB class (IndexedDB wrapper)
    audio-engine.js       # AudioEngine class (playback, queue, shuffle)
    metadata.js           # metadata parsing (ID3, Vorbis Comments)
    helpers.js            # utility functions (time formatting, escaping)
    ui/
      navigation.js       # tab switching, detail views, panels
      player.js           # playback UI, media session, scrubber
      renderers.js        # rendering song lists, album grids, etc.
      modals.js           # modals (new playlist, edit metadata, etc.)
      action-sheet.js     # action sheet for track options
      changelog.js        # changelog overlay and notification dot
      settings.js         # settings overlay and data management

  css/
    main.css              # barrel file that imports all other CSS
    base.css              # CSS variables, reset, shared components
    layout.css            # main layout, header, tabs
    tracks.css            # song list styling
    albums.css            # album grid and detail view
    artists.css           # artist list and detail view
    mini-player.css       # mini player bar
    now-playing.css       # full now playing screen
    modals.css            # modals and action sheets
    misc.css              # empty states, responsive, utilities
    changelog.css         # changelog overlay
    settings.css          # settings overlay
    welcome.css           # welcome/onboarding screen

  vendor/
    jsmediatags.min.js    # third-party metadata parsing library

Architecture Pattern

The app uses a single MusicApp class as the central object that holds all state (tracks, playlists, albums, artists, the audio engine, the database connection, etc.). This class is defined in music-app.js.

Each UI module in js/ui/ imports the MusicApp class and adds methods to its prototype. For example, navigation.js adds _switchTab() and _showDetail() to MusicApp.prototype. This means every module can access this.tracks, this.engine, this.db, etc. without needing to pass references around.

The entry point js/app.js imports all the UI modules (which register their methods as a side effect), defines the remaining instance methods like init() and _importFiles(), then creates a single MusicApp instance and calls init().

This pattern avoids circular dependency issues. If app.js both defined the class and exported it, the UI modules would try to import from app.js while app.js is trying to import from them. By putting the class definition in a separate file (music-app.js), all modules can safely import from it.

Running Locally

You can serve the project with any static file server. For example:

npx serve -p 3000

Then open http://localhost:3000 in your browser.

There is no build step. The browser loads everything directly via ES module imports.


README polished by Claude Opus 4.6

About

A browser-based music player built as a Progressive Web App (PWA). You can import your own music files, organise them into playlists, and play them back with full playback controls. Everything runs locally in the browser with no server or backend required.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors