npm / @arraypress/waveform-player
@arraypress/waveform-player
What changed in @arraypress/waveform-player 1, from its official release notes, upgrade guides and advisories. Sources checked 2026-10-20.
17 breaking2 security64 new APIs and options1 performance45 behavior and defaults1 tooling64 fixes and other
See which of these apply to your code, ranked, in your agent:
npx @unstale/cli init@arraypress/waveform-player 1194 changes
Breaking changes 17
Bar drawers treat an unusable pitch as zero bars, including when mutable options become unusable after construction.
The canonical attribute now takes precedence over the legacy
data-color-presetalias.Destroying a player no longer emits
request-pauseor asks an external controller to pause playback.BPM detection returns
nullfor silence, pads, speech with fewer than two onsets, or when no interval falls in the 60–200 range.Loading a different track resets its duration and time readout to 0:00, including when using
load()with a different URL.A track change resets
bpmandalbumunless supplied, drops a detected BPM from the prior track, and hides the badge when the new track has no BPM.In external mode, progress updates wait for a drag to be released instead of moving the playhead beneath the cursor.
A new entry point omits the import-time document-wide scan, so consumers can initialize the library explicitly with WaveformPlayer.init(root).
Markers without a usable time or label are dropped or defaulted, including markers from peaks JSON sidecars.
Peaks strings containing junk now yield no peaks rather than NaN values.
The package no longer ships sourcemap files, reducing its packed and unpacked size.
Renames the subtitle option to artist, including data-subtitle to data-artist, .waveform-subtitle to .waveform-artist, and the loadTrack parameter; there is no back-compat alias.
Removes the buttonColor, buttonHoverColor, textColor, textSecondaryColor, and backgroundColor and borderColor options and their data-* attributes; use CSS variables to theme button and text colors.
The mirror style now uses barSpacing: 2, producing distinct thin bars rather than a solid fill.
formatTime()now uses H:MM:SS after one hour and clamps negative values, also correctingaria-valuetext.External mode now uses the self-mode argument order
(currentTime, duration, player)instead of(player, currentTime, duration); consumers relying on the previous order must update handlers.The JSON config file feature was reverted because it caused layout and sizing issues when used inside WaveformBar.
Security 2
Values interpolated into the initial markup are now escaped, preventing quotes in author-supplied values from closing attributes and injecting markup.
A static bridge exposes
formatTime,extractTitleFromUrl,escapeHtml, andisSafeHref; the URL helper allows HTTP, HTTPS, and relative URLs while rejecting script schemes.
New APIs and options 64
Provides the player's own peak extraction for code that decodes audio itself and wants matching drawings.
Set this attribute to false on the document element to suppress the automatic document-wide scan while keeping explicit WaveformPlayer.init() calls available.
Accepts an optional Document or Element root and scans that subtree, matching the root itself as well as its descendants; defaults to document.
Adds a container state class when waveform analysis fails and the player uses placeholder peaks; the class is removed when the next load starts, allowing integrators to detect and restyle the decorative waveform.
Controls the CORS mode of the self-mode <audio> element, accepting 'anonymous', 'use-credentials', or null.
Sets the CORS mode of the self-mode <audio> element through a data attribute.
Choose whether artwork renders in the info row or fills the play/pause button; the option is also settable with data-artwork-position.
Set the play/pause button's corner radius with a CSS length; it is also settable with data-button-radius.
Re-theme the button artwork glyph color; re-theme it together with --wfp-btn-artwork-scrim to retain the contrast guarantee.
Re-theme the scrim between button artwork and the glyph; re-theme it together with --wfp-btn-artwork-color to retain the contrast guarantee.
Adds an option, also settable via data-*, to localize the artwork image alt text; its default is 'Album artwork'.
Adds an option, also settable via data-*, to localize the play button aria-label; its default is 'Play/Pause'.
Adds a configurable, translatable seek slider aria-valuetext template, also settable via data-seek-value-text; the default preserves existing output.
Adds an option, also settable via data-*, to localize the speed button and menu aria-label; its default is 'Playback speed'.
Adds an option, also settable via data-*, to localize the Media Session title fallback when no title is set; its default is 'Unknown Track'.
Adds an option that registers a Media Session skip-track button, called with the player.
Adds an option that registers a Media Session skip-track button, called with the player.
Adds a configurable gradient axis for waveformColor and progressColor stop arrays: vertical by default, horizontal, or diagonal.
Adds an opt-in draggable circular handle to the seekbar style; it expands on hover and defaults to false.
Enabling this option now shows a tooltip that follows the pointer over the waveform and displays the time at that position.
Adds an option to size the play/pause button; numbers are treated as pixels and strings are used verbatim. It sets --wfp-btn-size to scale both button styles and is forwarded by the Astro/React wrappers.
A public API lets developers trigger theme refreshes manually.
Accepts a known BPM to show in the badge with showBPM, without decoding audio; a supplied value takes precedence over detection.
Adds a buttonStyle option with circle and minimal values; minimal renders a bare play/pause glyph and composes with the preview layout.
Adds a preview layout that centers the title under the waveform and trims the meta row for sample-pack previews and dense grids.
The keyboard-focus ring and speed-menu active state are now neutral by default; set this CSS variable to a brand hue to re-tint them.
Exposes a utility for wrappers to read the player's full data-* option surface from a host element without reimplementing the contract.
The new option rounds bar caps in bars and mirror styles; caps fall back to square where
roundRectis unavailable.parseDataAttributesnow reads this attribute on auto-init paths.parseDataAttributesnow reads this attribute on auto-init paths.parseDataAttributesnow reads this attribute on auto-init paths.parseDataAttributesnow reads this attribute on auto-init paths.parseDataAttributesnow reads this attribute on auto-init paths.parseDataAttributesnow reads this attribute on auto-init paths.Customize or localize the audio-load error message; it defaults to
Unable to load audio, is escaped before rendering, and can also be set withdata-error-text.loadTrackaccepts{ autoplay: false }to load, restore, or enqueue a track without forcing playback.A CommonJS bundle and
requireexport condition allow the package to be loaded under Node CJS.Highlights the current marker using a core-owned
.waveform-marker.activeclass, avoiding direct marker DOM manipulation.srcanddata-srcare shorthand aliases forurl; canonical names still work and take precedence when both are set.styleanddata-styleare shorthand aliases forwaveformStyle; canonical names still work and take precedence when both are set.A new export alias provides access to the package stylesheet.
A CSS custom property sets the body gap, defaulting to 8px, so embedders can retune layout without overriding internal classes.
A CSS custom property sets waveform line height, defaulting to 1.4, so embedders can retune layout without overriding internal classes.
A CSS custom property sets the track gap, defaulting to 12px, so embedders can retune layout without overriding internal classes.
A destroy event lets listeners release references when the player is torn down.
Adds a boolean option, defaulting to true, that enables the accessible seek slider; set it to false to keep the prior markup.
Adds a string option for the slider's accessible name, defaulting to null and falling back to the track title, then 'Seek'.
Static helper derives a peaks-JSON URL from an audio URL, preserving query strings and fragments; returns undefined for empty input or unrecognised extensions.
Adds external audio mode, which skips the player's audio element and delegates playback to an external controller; the default remains self.
Adds an external-state pump for the play/pause visual state that toggles the icon, starts or stops smooth updates, and dispatches play or pause events.
Adds an external-state pump for the scrubber, canvas, and time displays, driven by an external clock without touching audio.
External-mode pause interactions dispatch this cancelable event on the container so a controller can route playback to its own audio source.
External-mode play interactions dispatch this cancelable event on the container so a controller can route playback to its own audio source.
External-mode seek interactions dispatch this cancelable event on the container so a controller can route the action to its own audio source.
JSON waveform files can include markers, which are automatically loaded when no markers are set via data attributes.
The bar, block, and dot drawing styles are now accepted as aliases for bars, blocks, and dots.
Accepts a URL to a JSON file ending in .json, reads peaks from an array or a peaks object, and falls back to generating the waveform from audio if fetching fails.
Accepts a URL to a JSON file ending in .json, reads peaks from an array or a peaks object, and falls back to generating the waveform from audio if fetching fails.
Load track configuration from an external JSON file using the data-config attribute.
Load JSON configuration dynamically by passing a file path through options.config.
The JSON config meta object passes through for use by extensions.
Adds an option to hide the play/pause button for custom UI implementations, configurable through HTML data attributes or the JavaScript API.
Adds an option to hide the title, artist, time, and metadata bar, configurable through HTML data attributes or the JavaScript API.
Set data-color-preset to light or dark to override automatic theme detection.
Performance 1
Config files are cached in memory so subsequent loads of the same file are instant.
Behavior and default changes 45
Auto-theme now continues its backdrop walk from the shadow host, so players in shadow roots can theme against their surrounding card.
Peaks supplied during audio decoding now win over the decode or placeholder fallback; loads with peaks supplied before they start skip decoding.
Also sets the current track's waveform option, allowing same-track reloads to reuse peaks and external-mode request-play events to carry them; loadTrack() replaces them for a new track.
A sidecar's BPM is shown with
showBPMwhen nobpmoption is set; an explicit option still takes precedence.The option's type now accepts
null.The canonical attribute takes precedence over its legacy alias and accepts a JSON gradient stop array.
pause()now dispatches the cancelable event before changing the current player;preventDefault()vetoes the pause request and retains the player ascurrentlyPlaying.Markers are placed when the duration changes, allowing external-mode markers to render without manually calling
renderMarkers().For a sidecar URL, the method returns a
Promise<boolean>indicating whether the sidecar was applied; inline peaks still return nothing.Self-mode progress events continue in a background tab using native audio time updates when the animation frame loop has been quiet for 500ms.
The track title and seek slider accessible name are painted before waiting for audio metadata, preventing an empty info row during fetch; total time still waits for metadata.
Boolean options accept attribute-shaped strings, interpreting the string 'false' as false rather than as a truthy value.
Options are normalized after merging; unusable values resolve to their documented defaults and produce a warning instead of throwing.
Non-numeric geometry and playback values, including height, now fall back to defaults and clamp to sane ranges with a warning naming the option.
The option accepts a bare comma-separated list as well as a JSON array, including in the data-playback-rates attribute.
The method now clamps playback rates to 0.25–4 instead of 0.5–2, enabling audible 2.5x and 3x speeds.
The <audio> element no longer sets crossOrigin to 'anonymous' by default, allowing playback from origins that do not send Access-Control-Allow-Origin.
loadTrack() now adds, updates, or removes artist and artwork metadata in place, so consumers can switch tracks with differing metadata without recreating the player.
The speed selector now uses an accessible menu with menuitemradio options, arrow-key and Home/End navigation, and focus management.
Dragging to seek now updates the time readout and shows a tooltip live at the drag position; the tooltip appears on any waveform style, while showHoverTime still governs the hover tooltip.
Activating an interactive control keeps focus on that control; the container takes focus only when a click lands on a non-interactive area.
The speed toggle announces open and closed state, closes on Escape, and returns focus to the trigger after a rate is chosen.
Dragging on the waveform or seekbar scrubs without re-seeking during the drag; the seek commits on release.
The player adds and removes .show-label so an active marker label appears for about 2.5 seconds and fades out while the highlight remains; CSS can override the label visibility.
Markers are highlighted and their labels revealed automatically as playback passes them.
A failed artwork image now displays a muted music-note placeholder tile instead of the browser's broken-image icon.
The default source peak resolution for live decode rises from 256 to 1800, keeping wide or high-DPI waveforms crisp; supplied waveform peaks ignore this setting.
Auto-themed players re-detect the page theme and redraw when the page theme or OS color scheme changes; explicit presets and hand-set colours are not overridden.
The minimal button uses a larger play/pause glyph and a fixed 2.25rem button box, without shifting the waveform when toggled.
The default barRadius is now 1, giving bars soft caps.
The default height is now 64.
The default samples value is now 256 for more source fidelity; samples are resampled to fit the bar pitch.
The seek slider now toggles
aria-busywhile loading.A reduced-motion media query neutralizes transitions and animations for users who request reduced motion.
progressColornow accepts an array of CSS colour stops, rendered as a vertical canvas gradient; it works through constructor options, data attributes, and the React/Astro wrappers.The request-pause event detail now includes
markers,waveform, andartist, so controllers can use the published contract without re-fetching data.The request-play event detail now includes
markers,waveform, andartist, so controllers can use the published contract without re-fetching data.The request-seek event detail now includes
markers,waveform, andartist, so controllers can use the published contract without re-fetching data.The error node now has
role="alert"for accessibility.waveformColornow accepts an array of CSS colour stops, rendered as a vertical canvas gradient; it works through constructor options, data attributes, and the React/Astro wrappers.The ended event now carries
currentTimeandduration; external mode synthesizes it when progress reaches the end, firing once.The waveform surface is now a keyboard-operable ARIA slider, with synchronized value attributes and keyboard seeking in both self and external modes.
Configuration values are resolved in order: JSON config as the base, then data attributes, then JS options as overrides.
The waveform automatically fills the full width when controls are hidden.
The player automatically adapts to the website’s color scheme by checking background brightness, theme classes, and system preferences.
Tooling 1
The package ships hand-authored TypeScript definitions for its options, WaveformPlayer class API, and custom-event map, enabling IntelliSense and type-checking without runtime bytes.
Fixes and other changes 64
When the browser reports a target as unseekable, the player waits for the download to finish, reloads from cache, and continues from the new position, allowing Chromium to seek on hosts that ignore byte ranges.
Keyboard controls now work inside a shadow root, including Space on the focused player and the speed menu's arrow keys.
Peaks supplied immediately after construction are no longer overwritten by the first load frame drawing the waveform option.
The placeholder indicator clears when real peaks arrive through setWaveformData() after a failed decode.
Values below 60px now size the waveform container correctly, avoiding a taller container and stretched, blurry bars.
A zero bar width is normalized to 0.1, preventing an infinite bar count and a page hang when spacing is also zero.
Destroying the player during the metadata wait no longer throws or prevents
load()from settling.The declaration now types the resolved value as
number[], matching the peaks array returned at runtime.JSON sidecar URLs with query strings or fragments are recognized as URLs rather than parsed as inline peaks.
The declaration now accepts
nullandundefinedand returnsstring | undefined, as documented.A load now stops after an await if a newer load or destroy has started, preventing stale results, duplicate autoplay, and premature clearing of the loading state.
Calling
load(url)after a failed load clears the whole error state, including the disabled play button and error overlay.Calling
loadTrack()immediately after construction no longer causes the same file to be fetched and decoded twice.The lock-screen scrubber state updates on seeks and playback-rate changes, and only for the player that owns the session.
A self-mode audio failure now invokes
onErroronly once.With a JSON peaks sidecar,
onLoadfires after the sidecar has been applied.A non-2xx response, network error, or sidecar without peaks falls back to decoding the audio instead of leaving the canvas blank.
The configured playback rate and a speed selected from the menu now survive track changes.
The public method is now declared in the type declarations.
The public method is now declared in the type declarations.
seekTo()andseekToPercent()ignore non-finite targets instead of assigning an invalid current time and throwing.The declaration now allows
Promise<boolean> | voidto match the method's return behavior.At the end of a self-mode track, listeners now receive one pause event and
onPauseruns once.A player destroyed within its first frame no longer emits
readyor performs its initial audio fetch and decode.Calling load() now records the new URL in options.url at the start, so events during loading and failed loads report the track being loaded.
A construction failure now unregisters the player and drops listeners it bound before rethrowing, preventing later scans and shared instance operations from being blocked by a half-built player.
The initialization flag is cleared when a player is destroyed, allowing the same element to be initialized again.
The package metadata now declares the ESM/CJS/IIFE entry bundles as side-effectful so bundlers do not drop declarative-only imports; pure modules under src/js/ remain tree-shakeable.
Non-function callback values are rejected during configuration rather than throwing during playback.
Unrecognised enumerated option values resolve to their defaults, avoiding unintended browser behavior such as an unexpected CORS request for crossOrigin.
Rates outside the supported range are dropped from the speed menu so its buttons and highlighted rate match actual playback.
The loading indicator appears only while the canvas is empty; aria-busy continues to track the real loading state.
When preload is 'none', load() skips the metadata wait so it can settle; a later metadata event still fills in duration and re-renders markers.
Caller-supplied peaks now paint before waiting for audio metadata, so the canvas is not left blank while metadata loads.
Unrecognized alignment values are coerced to auto in createDOM(), covering both constructor options and data-button-align.
Initialization skips elements that already have a player instance, preventing programmatically constructed players from being re-initialized during a scan.
Non-finite and non-numeric input now formats as '0:00' instead of producing an invalid time string; numeric strings are still coerced.
An explicit buttonSize is now honored on narrow screens instead of being overridden by the 32px fallback.
During seek-drag, the current-time readout owns the live scrub time and the duplicate hover tooltip is suppressed; the tooltip still shows when there is no current-time readout.
A player now claims Media Session action handlers and metadata when it starts playing, so lock-screen controls target the track being played.
Prevents the synthetic click after a pointer drag from re-seeking using a stale coordinate and snapping the playhead.
Reasserts metadata on play and sets playbackState and position state, fixing blank iOS Now-Playing cards.
Space now starts playback when the waveform itself is focused; previously, play/pause only worked when the player root was focused.
Live decoding now scans every frame, matching WaveformGen’s offline waveform shape and making live and pre-generated renders identical.
Fixes the minimal play button glyph size so its sizing rule applies instead of the later, equally specific 16px rule.
The minimal play button now renders its glyph at the default circle button’s visual size while keeping a fixed width so the waveform does not shift on play/pause.
The minimal button now uses a fixed box, so playing or pausing no longer shifts the adjacent waveform or re-samples its bars.
Duration is now published unconditionally, enabling keyboard seeking and the ARIA slider in external mode even when
showTimeis off.External mode no longer crashes during initialization when
showPlaybackSpeedis enabled;updateSpeedUI()now no-ops without audio.Autoplay no longer emits an unhandled promise rejection when the browser blocks playback.
destroy()now tears down document, container, and seek listeners registered through an AbortController, fixing listeners left attached during teardown.IDs now hash the full URL plus a counter, avoiding same-host track collisions and errors on non-Latin1 or Unicode URLs.
The AudioContext now closes in a
finallyblock after failed decoding, preventing leaked contexts from exhausting browser limits.Marker rendering now uses a mode-agnostic duration, allowing chapter markers to render when audio is delegated.
setVolume()now coerces and guards non-finite input soNaNdoes not reachaudio.volume.SSR and Node consumers can import WaveformPlayer by name without an export error.
Marker elements from a previous track no longer persist when loading a new track without markers.
Markers from the previous track no longer persist when loading a new track without markers.
Removed inline style.height so the canvas can fill available width in flex containers; the container div still controls height.
The waveform canvas now fills the available width when embedded in flex containers because resizeCanvas() reads width from the container div instead of the canvas element.
Catches the play() promise internally when loadTrack triggers autoplay before user interaction, preventing an uncaught NotAllowedError.
play() now returns the Promise from HTMLMediaElement.play(), allowing callers to handle errors like AbortError.
All custom events now bubble for better framework integration.
Calling destroy() during resize events no longer causes a null reference error; the resize listener is cleaned up, and event handlers guard against destruction to prevent memory leaks and race conditions.