{"id":27952310,"url":"https://github.com/regosen/gapless-5","last_synced_at":"2025-05-07T17:00:24.411Z","repository":{"id":13436312,"uuid":"16125396","full_name":"regosen/Gapless-5","owner":"regosen","description":"Gapless JavaScript audio player using HTML5 and WebAudio","archived":false,"fork":false,"pushed_at":"2024-04-19T18:03:35.000Z","size":403,"stargazers_count":149,"open_issues_count":11,"forks_count":24,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-20T08:04:43.043Z","etag":null,"topics":["audio-player","crossfade","javascript","loop","seamless","shuffle","ui","web","webaudio"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/regosen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-01-22T01:58:00.000Z","updated_at":"2025-03-01T15:01:18.000Z","dependencies_parsed_at":"2023-12-19T20:52:42.224Z","dependency_job_id":"751ed0a6-a14f-47c2-9cd6-9f4b8616161a","html_url":"https://github.com/regosen/Gapless-5","commit_stats":{"total_commits":288,"total_committers":5,"mean_commits":57.6,"dds":0.3159722222222222,"last_synced_commit":"de833fff05f491b586a04da2e3ca5a61497f2ee2"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regosen%2FGapless-5","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regosen%2FGapless-5/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regosen%2FGapless-5/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regosen%2FGapless-5/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/regosen","download_url":"https://codeload.github.com/regosen/Gapless-5/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252921978,"owners_count":21825632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-player","crossfade","javascript","loop","seamless","shuffle","ui","web","webaudio"],"created_at":"2025-05-07T17:00:20.476Z","updated_at":"2025-05-07T17:00:24.385Z","avatar_url":"https://github.com/regosen.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gapless 5 \u0026nbsp; \u003cimg src=\"https://ccrma.stanford.edu/~regosen/gapless5.gif\" width=\"123\" height=\"51\"\u003e\n\nA gapless JavaScript audio player using HTML5 and WebAudio.\n\n\u003c!-- vscode-markdown-toc --\u003e\n* 1. [Demos](#1-demos)\n* 2. [Features](#2-features)\n\t* 2.1. [Browser Support](#21-browser-support)\n* 3. [Installation](#3-installation)\n\t* 3.1. [Using npm](#31-using-npm)\n\t* 3.2. [Using direct HTML](#32-using-direct-html)\n* 4. [Usage](#4-usage)\n\t* 4.1. [Options](#41-options)\n\t* 4.2. [Functions](#42-functions)\n\t\t* 4.2.1. [Parameterized Functions](#421-parameterized-functions)\n\t\t* 4.2.2. [Accessors](#422-accessors)\n\t\t* 4.2.3. [Actions](#423-actions)\n\t* 4.3. [Callbacks](#43-callbacks)\n\t* 4.4. [GUI Customization](#44-gui-customization)\n* 5. [License](#5-license)\n\n\u003c!-- vscode-markdown-toc-config\n\tnumbering=true\n\tautoSave=true\n\t/vscode-markdown-toc-config --\u003e\n\u003c!-- /vscode-markdown-toc --\u003e\n\n**PROBLEM**: There are 2 modern APIs for playing audio through the web, and both of them have problems:\n\n- **HTML5 Audio**: the last chunk of audio gets cut off, making gapless transitions impossible\n- **WebAudio**: can't play a file until it's fully loaded\n\n**SOLUTION**: Use both!\n\n- If WebAudio hasn't fully loaded yet, it begins playback with HTML5 Audio, then seamlessly switches to WebAudio once loaded.\n\n##  1. Demos\n\nThe following sites utilize Gapless 5.  If you'd like to be featured here, please contact the repo owner or start a new issue!\n\n- [Gapless 5 Demonstration Page](https://ccrma.stanford.edu/~regosen/gapless5): Utilizes key mappings for cueing and other features.\n\n- [THE402](https://the402.wertstahl.de/player): An electronic music looping experience.\n\n- [Woodhelm](https://solemensis.github.io/Woodhelm/): A live ambience mixing website.\n\n- [Bernardo.fm](https://beta.bernardo.fm/#!page=music): Featuring electronic and hip-hop artists.\n\n- [This is Nerdpop](http://www.zenfingerpainting.com): Interactive listening page for Zen Finger Painting's indie pop album.\n\n##  2. Features\n\n- Players can have multiple tracks\n- Pages can have multiple players\n- Memory management (see `loadLimit` under [Options](#Options))\n- Seamless transitions between tracks\n  - Pre-loading of subsequent tracks\n  - Files don't need to be fully loaded to start playback\n- Cross-fade support\n- Track shuffling during playback\n- Optional built-in UI\n\n###  2.1. Browser Support\n\n- Safari (including iOS)\n- Chrome (including Android)\n- Firefox\n- Other browers (UI untested, but they probably work as well)\n\n*NOTE for Boostrap users: Bootstrap's CSS will mess up the optional built-in UI.  If you don't need Bootstrap in its entirety, try using Twitter customize to get just the subset of rules you need.*\n\n##  3. Installation\n\n###  3.1. Using npm\n\n1. Install the [npm package](https://www.npmjs.com/package/@regosen/gapless-5):\n```shell\n$ npm install @regosen/gapless-5\n```\n\n2. Import `Gapless5` from the module:\n```js\nconst { Gapless5 } = require('@regosen/gapless-5');\n```\n\n###  3.2. Using direct HTML\n\nA. If not using built-in UI, just add and reference `Gapless5.js` from your HTML head.\n```html\n\u003cscript src=\"gapless5.js\" language=\"JavaScript\" type=\"text/javascript\"\u003e\u003c/script\u003e\n```\n\nB. If using the built-in UI, add and reference `Gapless5.js` and `Gapless5.css`.  Also, create a `\u003cdiv\u003e` or `\u003cspan\u003e` element where you want the player to appear.  Give it a particular id.\n\n```html\n\u003clink href=\"gapless5.css\" rel=\"stylesheet\" type=\"text/css\" /\u003e\n\u003cscript src=\"gapless5.js\" language=\"JavaScript\" type=\"text/javascript\"\u003e\u003c/script\u003e\n\n\u003c!-- then in body: --\u003e\n\u003cdiv id=\"gapless5-player-id\" /\u003e\n```\n\n##  4. Usage\n\n1. Create a `Gapless5` object with an optional parameter object\n    - If you want the built-in UI, pass in the element ID as `guiId` under options.\n2. Add tracks via options in constructor or `addTrack()`\n3. Optional stuff:\n    - Add a cross-fade between tracks using `crossfade` under options.\n      - TIP: try setting this between 25 and 50 ms if you still hear gaps between your tracks.  Gap sizes depend on the audio format, browser, etc.\n    - Manipulate tracklist with `insertTrack()`, `removeTrack()`, and more.\n    - Register your own callbacks.\n    - Connect key presses to actions using `mapKeys()` or options in constructor.\n\n```js\nconst player = new Gapless5({ guiId: 'gapless5-player-id' });\n\n// You can add tracks by relative or absolute URL:\nplayer.addTrack('audio/song1.mp3');\nplayer.addTrack('https://my-audio-site.org/song2.m4a');\n\n// You can also let the user upload tracks from a file loader like this:\nconst files = document.getElementById('my-file-input').files;\nfiles.forEach(file =\u003e {\n  player.addTrack(URL.createObjectURL(file)); // this creates a 'blob://' URL\n});\nplayer.play();\n```\n_If you want the user to upload tracks from a file loader, here's an example of that:_\n```html\n\u003cform\u003e\n  \u003cinput type=\"file\" id=\"my-file-input\" accept=\"audio/*\"\u003e\n\u003c/form\u003e\n```\n\n###  4.1. Options\nThese can be passed into a `Gapless5` constructor, or (with the exception of `tracks` and `guiId`) set later on the object.\n\n- **guiId**\n  - id of existing element (i.e. `div` or `span`) where you want the player to appear.\n  - if empty or not provided, Gapless5 won't use built-in UI.\n- **tracks**\n  - path to audio file(s) or blob URL(s), see examples above\n  - can be a single track as a string, an array, or a JSON object containing an array of JSON objects\n- **loop**\n  - default = false\n  - loops after end of list/track\n- **singleMode**\n  - default = false\n  - plays/loops single track only\n- **exclusive**\n  - default = false\n  - stops other Gapless5 players when this one is playing\n- **startingTrack**\n  - default: 0\n  - either an array index into the tracks array, or the string `random` for a random index\n- **shuffleButton**\n  - default = true\n  - adds shuffle button to the player UI\n- **shuffle**\n  - default = false\n  - enables shuffle mode immediately after playlist load\n- **useHTML5Audio**\n  - default = true\n  - if you don't care about immediate playback, set useHTML5Audio to false for lower memory usage\n- **useWebAudio**\n  - default = true\n  - if you don't care about gapless playback, set useWebAudio to false for better performance\n  - see NOTE below\n- **loadLimit**\n  - default = no limit\n  - limits how many tracks can be loaded at once.  If you have a large playlist, set to a low number (like 2-5) to save on memory\n  - caveat: you will hear gaps/loading delays if you skip tracks quickly enough or jump to arbitrary tracks\n  - see NOTE below\n- **volume**\n  - default = 1.0 (0 = silent, 1.0 = loudest)\n- **crossfade**\n  - crossfade duration in milliseconds\n  - note: crossfades happen only when transitioning between tracks, not when you start playback\n- **crossfadeShape**\n  - `CrossfadeShape.None` (default, overlaps both tracks at full volume)\n  - `CrossfadeShape.Linear`\n  - `CrossfadeShape.EqualPower` (curved, louder than linear)\n- **playbackRate**\n  - default = 1.0\n  - multiplier for the playback speed, higher = plays faster, lower = plays slower\n- **mapKeys**\n  - pressing specified key (case-insensitive) will trigger any Action function listed above.\n- **logLevel**\n  - minimum logging level (default = `LogLevel.Info`)\n  - set this to `LogLevel.Debug` for more verbose logging\n\nExample:\n\n```js\nconst player = new Gapless5({\n  tracks: ['loop1.mp3', 'loop2.mp3'],\n  loop: true,\n  loadLimit: 2,\n  mapKeys: {prev: 'a', playpause: 's', stop: 'd', next: 'f'},\n});\n```\n\n_NOTE 1: if you need audio to keep playing on iOS with Safari in the background, set `useWebAudio` to `false`._\n\n_NOTE 2: if you use `loadLimit` with `useWebAudio` set to `false`, Safari on iOS may fail to play subsequent tracks.  This is due to user interaction requirements, and JS console will show a warning if this happens._\n\n###  4.2. Functions\nYou can call these functions on `Gapless5` objects.\n\n####  4.2.1. Parameterized Functions\n- **addTrack(audioPath)**\n  - adds track to end of playlist\n  - `audioPath`: path to audio file(s) or blob URL(s), see examples above\n- **insertTrack(index, audioPath)**\n  - inserts track at location `index`\n  - `audioPath`: same as in addTrack\n- **replaceTrack(index, audioPath)**\n  - replaces track at location `index`\n  - `audioPath`: same as in addTrack\n- **gotoTrack(indexOrPath)**\n  - jumps to specified track\n  - `indexOrPath` can be the numerical index, or audio path\n- **queueTrack(indexOrPath)**\n  - similar to `gotoPath`, but waits for current track to finish first\n- **removeTrack(indexOrPath)**\n  - removes specified track from playlist\n  - `indexOrPath` can be the numerical index, or audio path\n- **setPosition(position)**\n  - updates the current position (in milliseconds)\n- **setVolume(volume)**\n  - updates the volume in real time (between 0 and 1)\n- **setCrossfade(duration)**\n  - sets the crossfade duration in milliseconds\n- **setCrossfadeShape(shape)**\n  - sets the crossfade curve shape\n- **setPlaybackRate(playbackRate)**\n  - updates the playback speed in real time (see `playbackRate` option)\n- **mapKeys(jsonMapping)**\n  - pressing specified key (case-insensitive) will trigger any Action function listed below.\n  - `jsonMapping` maps an action to a key, see example code below\n\n####  4.2.2. Accessors\n- **isShuffled()**\n  - returns true if shuffled\n- **getTracks()**\n  - returns list of audioPaths in play order\n  - if shuffled, the shuffled order will be reflected here\n- **getTrack()**\n  - returns current track's audioPath ('' if unavailable)\n- **getIndex()**\n  - returns current index in the playlist (-1 if unavailable)\n- **getPosition()**\n  - returns current play position in milliseconds\n- **getSeekablePercent()**\n  - returns percent of current track that's seekable (0-1)\n- **findTrack(audioPath)**\n  - returns index of track in playlist\n\n####  4.2.3. Actions\n\nAll actions can be mapped to keys via `mapKeys`.\n\n*These correspond to built-in UI buttons*\n- **prev()**: matches behavior of \"prev\" button (scrubs to start if you've progressed into a track)\n- **playpause()**: matches behavior of \"play/pause\" button\n- **stop()**: matches behavior of \"stop\" button\n- **toggleShuffle()**: switches between shuffled and un-shuffled\n  - subsequent shuffles will be different each time\n- **next()**: matches behavior of \"next\" button\n\n*These do not correspond to built-in UI buttons*\n- **prevtrack()**: unlike \"prev\" button, this will always jump to the previous track\n- **cue()**: play from start\n- **play()**: non-togglable \"play\"\n- **pause()**: non-togglable \"pause\"\n- **shuffle(preserveCurrent = true)**: non-togglable shuffle, re-shuffles if previously shuffled\n  - if **preserveCurrent** is false, it will shuffle all tracks (without preserving current track)\n- **removeAllTracks()**: clears entire playlist\n\nExamples:\n```js\nplayer.mapKeys({cue: '7', stop: '8', next: '9'});\n\nplayer.play();\nplayer.pause();\n\n// indexes start at 0\nplayer.replaceTrack(0, 'audio/song1_alt.flac');\nplayer.insertTrack(1, 'audio/transition.wav');\n\nplayer.gotoTrack(1);\nplayer.gotoTrack('audio/song1_alt.flac'); // can also goto track by path\n\nplayer.removeTrack(2);\nplayer.removeTrack('audio/transition.wav'); // can also remove track by path\nplayer.removeAllTracks();\n```\n###  4.3. Callbacks\nYou can set these on a `Gapless5` object.  All callbacks include the affected track's audio path except where indicated.\n\n```ts\n// audio position has changed\nontimeupdate = (current_track_time: number, current_track_index: number) =\u003e void\n\n// play requested by user\nonplayrequest = (track_path: string) =\u003e void\n\n// play actually starts\nonplay = (track_path: string) =\u003e void \n\n// play is paused\nonpause = (track_path: string) =\u003e void\n\n// play is stopped\nonstop = (track_path: string) =\u003e void\n\n// prev track, where:\n//   from_track = track that we're switching from\n//   to_track = track that we're switching to\nonprev = (from_track: string, to_track: string) =\u003e void\n\n// next track, where:\n//   from_track = track that we're switching from\n//   to_track = track that we're switching to\nonnext = (from_track: string, to_track: string) =\u003e void\n\n// loading started\nonloadstart = (track_path: string) =\u003e void \n\n// loading completed\n//   fully_loaded = true for WebAudio data, false for HTML5 Audio data\n//   NOTE: this triggers twice per track when both WebAudio and HTML5 are enabled\nonload = (track_path: string, fully_loaded: boolean) =\u003e void\n\n// track unloaded (to save memory)\nonunload = (track_path: string) =\u003e void\n\n// track failed to load or play\nonerror = (track_path: string, error?: Error | string) =\u003e void\n\n// track finished playing\nonfinishedtrack = (track_path: string) =\u003e void\n\n// entire playlist finished playing\nonfinishedall = () =\u003e void\n```\n\nExample:\n\n```js\nfunction nextCallback(from_track, to_track) {\n  console.log(`User skipped to next track (from ${from_track} to ${to_track})`);\n}\n\nconst player = new Gapless5({guiId: 'gapless5-player-id', tracks: ['track1.mp3', 'track2.mp3']});\nplayer.onnext = nextCallback;\nplayer.onplay = function (track_path) { console.log(`Now playing ${track_path}`); };\n```\n\n###  4.4. GUI Customization\n\nWhile Gapless provides its own GUI, you can also customize it in CSS, or even create your own spans of text controlled by Gapless5.\n- `.g5positionbar` by class will affect the entire text above all Gapless5 players on your page\n- `#g5positionbar-[ID]` by id (where `[ID]` of the guiId you provided) will also customize the entire text for the current player\n- a span with `#g5position-[ID]` will be set to the current position (e.g. \"04:04.95\") \n- a span with `#g5duration-[ID]` will be set to the track's duration\n- a span with `#g5index-[ID]` will be set to the track's index in the playlist\n- a span with `#g5numtracks-[ID]` will be set to the number of tracks in the playlist\n- a span with `#g5trackname-[ID]` will be set to the current track name (filename without extension)\n\nExample:\nin CSS, hide the built-in gapless5 text:\n```\n  #g5positionbar-MyID {\n    display: none;\n  }\n```\nand then create your own elements to be controlled by Gapless5:\n```\n\u003cp\u003e\n  Now Playing: \u003cspan id=\"#g5trackname-MyID\"\u003e\u003cspan\u003e \n  (\u003cspan id=\"#g5position-MyID\"\u003e\u003cspan\u003e)\n\u003c/p\u003e\n```\nSee an example of customized player text [here](http://www.zenfingerpainting.com).\n\n##  5. License\n\nLicensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fregosen%2Fgapless-5","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fregosen%2Fgapless-5","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fregosen%2Fgapless-5/lists"}