展示HN:Gapless.js – 无缝隙的网页音频播放
Show HN: Gapless.js – gapless web audio playback

原始链接: https://github.com/RelistenNet/gapless.js

## Gapless:一款用于无缝播放的 Web 音频播放器 Gapless 是一个轻量级的 JavaScript 库,专为网页上的无缝音频播放而设计,最初是为 Relisten.net 构建的。它利用 HTML5 音频和 Web Audio API 在曲目之间创建流畅的过渡。尽管优先考虑功能,但它仍然保持了较小的包大小,仅依赖一个生产依赖项(xstate)来实现强大的状态管理。 该库提供了一个简单的 API,可以通过回调函数自定义播放行为,用于处理进度、曲目结束和错误处理。关键方法包括 `play()`、`pause()`、`next()`、`previous()` 和 `addTrack()`。它提供了详细的 `TrackInfo` 对象,包含诸如当前时间、持续时间和播放状态等元数据。 **主要特点:** * **无缝播放:** 核心功能,使用 Web Audio API。 * **简单 API:** 易于集成和控制。 * **媒体会话 API 支持:** 利用曲目元数据进行锁屏控制。 * **状态管理:** 由 Xstate 提供支持,以确保可靠运行。 * **ES 模块:** 作为 ES 模块发布。 提供了一个实时演示和全面的文档,展示了其易用性和强大功能。版本 4 包含 API 变更,特别是将 `playNext()` 等方法重命名为 `next()`,并返回纯数据对象而不是类实例。

## Gapless.js:无缝Web音频播放 开发者switz发布了Gapless.js的4.0版本,这是一个用于在浏览器中实现真正无缝音频播放的库,最初是为音乐会录音流媒体服务Relisten.net构建的。 该库利用Web Audio API,预加载音频缓冲区以实现曲目之间的平滑过渡。它会智能地在HTML5和Web Audio播放之间切换,在支持的情况下优先使用Web Audio以获得无缝性能。虽然移动Web Audio支持有限,但该库会优雅地回退到HTML5。 Gapless.js依赖性轻(仅使用xstate作为其状态机),并且是无头的,这意味着开发者可以将其与他们自己的用户界面集成。它建立在早期“Gapless-5”项目的基础上,提供了一个更专注、与UI无关的解决方案。演示地址:[https://gapless.saewitz.com](https://gapless.saewitz.com)。
相关文章

原文

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

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();

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)
  webAudioIsDisabled: false,    // Disable Web Audio API (disables gapless playback)
  trackMetadata: [],            // Per-track metadata (aligned by index)
  volume: 1,                    // Initial volume, 0.0–1.0
});
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)
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
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

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;
  machineState: string;             // Internal state machine state
}
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' }],
  },
});

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;
}

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 webAudioIsDisabled: true 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
  • 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.

MIT

联系我们 contact @ memedata.com