[proxy] github.com← back | site home | direct (HTTPS) ↗ | proxy home | ◑ dark◐ light

GitHub - RelistenNet/gapless.js: Gapless audio playback javascript plugin (for seamless audio playback)

RelistenNet

gapless.js

Gapless audio player for the web. Takes an array of audio tracks and uses HTML5 audio with the Web Audio API to enable seamless, gapless transitions between tracks.

Though the earnest goal is not bundle-size driven, it has only one production dependency (xstate) so it operates in a rigid manner according to a well-designed state machine.

It has a dead simple API and is easy to get up and running.

Built for Relisten.net, where playing back gapless live tracks is paramount.

Live Demo

Install

pnpm install gapless

Quick Start

import Queue from 'gapless';

const player = new Queue({
  tracks: [
    'https://example.com/track1.mp3',
    'https://example.com/track2.mp3',
    'https://example.com/track3.mp3',
  ],
  onProgress: (track) => {
    console.log(`${track.currentTime} / ${track.duration}`);
  },
  onEnded: () => {
    console.log('Queue finished');
  },
});

player.play();

API

Constructor Options (GaplessOptions)

const player = new Queue({
  tracks: [],              // Initial list of track URLs
  onProgress: (info) => {},     // Called at ~60fps while playing
  onEnded: () => {},            // Called when the last track ends
  onPlayNextTrack: (info) => {},    // Called when advancing to next track
  onPlayPreviousTrack: (info) => {},// Called when going to previous track
  onStartNewTrack: (info) => {},    // Called whenever a new track becomes current
  onError: (error) => {},       // Called on audio errors
  onPlayBlocked: () => {},      // Called when autoplay is blocked by the browser
  onDebug: (msg) => {},         // Internal debug messages (development only)
  playbackMethod: 'HYBRID',     // 'HYBRID' | 'HTML5_ONLY' | 'WEBAUDIO_ONLY'
  trackMetadata: [],            // Per-track metadata (aligned by index)
  volume: 1,                    // Initial volume, 0.0–1.0
  preloadNumTracks: 2,              // Number of tracks to preload ahead (0 to disable)
  playbackRate: 1,              // Initial playback rate, 0.25–4.0
});

Methods

Method Description
play() Start or resume playback
pause() Pause playback
togglePlayPause() Toggle between play and pause
next() Advance to the next track
previous() Go to previous track (restarts current track if > 8s in)
gotoTrack(index, playImmediately?) Jump to a track by index
seek(time) Seek to a position in seconds
setVolume(volume) Set volume (0.0–1.0)
setPlaybackRate(rate) Set playback rate (0.25–4.0), reschedules gapless transitions
addTrack(url, options?) Add a track to the end of the queue
removeTrack(index) Remove a track by index
resumeAudioContext() Resume the AudioContext (for browsers that require user gesture)
destroy() Clean up all resources

Getters

Getter Type Description
currentTrack TrackInfo | undefined Snapshot of the current track
currentTrackIndex number Index of the current track
tracks readonly TrackInfo[] Snapshot of all tracks
isPlaying boolean Whether the queue is playing
isPaused boolean Whether the queue is paused
volume number Current volume
playbackRate number Current playback rate
preloadNumTracks number Number of tracks to preload ahead (read/write)

TrackInfo

All callbacks and getters return TrackInfo objects — plain data snapshots with no methods:

interface TrackInfo {
  index: number;                    // Position in the queue
  currentTime: number;              // Playback position in seconds
  duration: number;                 // Total duration (NaN until loaded)
  isPlaying: boolean;
  isPaused: boolean;
  volume: number;
  trackUrl: string;                 // Resolved audio URL
  playbackType: 'HTML5' | 'WEBAUDIO';
  webAudioLoadingState: 'NONE' | 'LOADING' | 'LOADED' | 'ERROR';
  metadata?: TrackMetadata;
  playbackRate: number;               // Current playback rate
  machineState: string;             // Internal state machine state
}

AddTrackOptions

player.addTrack('https://example.com/track.mp3', {
  skipHEAD: true,       // Skip HEAD request for URL resolution
  metadata: {
    title: 'Track Title',
    artist: 'Artist',
    album: 'Album',
    artwork: [{ src: 'https://example.com/art.jpg', sizes: '512x512', type: 'image/jpeg' }],
  },
});

TrackMetadata

Metadata is used for the Media Session API (lock screen controls, browser media UI) and can contain arbitrary additional fields:

interface TrackMetadata {
  title?: string;
  artist?: string;
  album?: string;
  artwork?: MediaImage[];
  [key: string]: unknown;
}

Playback Method

The playbackMethod option controls how audio is rendered:

Value Behavior Use case
'HYBRID' (default) Starts with HTML5 audio, switches to Web Audio after decode Remote files — instant playback + gapless transitions
'HTML5_ONLY' HTML5 audio exclusively, no Web Audio When Web Audio is unavailable or unwanted; gapless playback disabled
'WEBAUDIO_ONLY' Web Audio API exclusively, no HTML5 fallback Very small or local files where buffering is instant
// Web Audio only — waits for decode before playing
const player = new Queue({
  tracks: ['track1.mp3', 'track2.mp3'],
  playbackMethod: 'WEBAUDIO_ONLY',
});

player.play(); // Waits for decode, then plays via Web Audio

Preload Count

Control how many tracks are preloaded ahead of the current track:

const player = new Queue({
  tracks: ['a.mp3', 'b.mp3', 'c.mp3', 'd.mp3'],
  preloadNumTracks: 1, // Only preload 1 track ahead (default: 2)
});

// Can also be changed at runtime:
player.preloadNumTracks = 0; // Disable preloading
player.preloadNumTracks = 3; // Preload 3 ahead

Playback Rate

Control the speed of playback (0.25x to 4x). Gapless scheduling automatically adjusts for the current rate:

const player = new Queue({
  tracks: ['a.mp3', 'b.mp3'],
  playbackRate: 1.5, // Start at 1.5x
});

player.play();
player.setPlaybackRate(2); // Change to 2x mid-playback
console.log(player.playbackRate); // 2

Migration from v3

v4 is a complete rewrite. The public API has changed:

v3 v4
import GaplessQueue from 'gapless.js' import Queue from 'gapless' (or import { Queue })
player.playNext() player.next()
player.playPrevious() player.previous()
player.resetCurrentTrack() player.seek(0)
player.disableWebAudio() Pass playbackMethod: 'HTML5_ONLY' in constructor
player.nextTrack player.tracks[player.currentTrackIndex + 1]
track.completeState Callbacks now receive TrackInfo objects
Callbacks receive Track instances Callbacks receive plain TrackInfo data snapshots

Key differences

  • State machines: Internally uses XState for queue and track state management. XState is bundled — no extra dependency needed.
  • ESM only: Published as ES module only. No CommonJS build.
  • TrackInfo: All callbacks and getters return plain data objects (TrackInfo) instead of Track class instances.
  • Media Session: Built-in support for the Media Session API via trackMetadata.
  • Volume: Volume is now set via setVolume(n) and readable via the volume getter.

License

MIT