Skip to content

WordPress

WaveformPlayer is a single CSS file and a single self-initialising script, so it drops into any WordPress theme or plugin with wp_enqueue_style / wp_enqueue_script. Once the script is on the page it scans for [data-waveform-player] on DOMContentLoaded and upgrades every matching element — no inline JS required.

The recommended flow:

  1. Enqueue the stylesheet and script (from a CDN, or bundled in your theme/plugin).
  2. Emit the markup — a <div data-waveform-player …> — from post content, a shortcode, or a block.

The browser build is an IIFE that exposes window.WaveformPlayer, so it works as a plain classic script (no type="module" needed). Enqueue both files on wp_enqueue_scripts.

The fastest path — point WordPress at jsDelivr or unpkg. Pin to a major (@1) for automatic patch/minor updates, or to an exact version (@1.14.0) for reproducible builds.

// functions.php (theme) or your plugin's main file
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'waveform-player',
'https://cdn.jsdelivr.net/npm/@arraypress/waveform-player@1/dist/waveform-player.css',
array(),
'1.14.0'
);
wp_enqueue_script(
'waveform-player',
'https://cdn.jsdelivr.net/npm/@arraypress/waveform-player@1/dist/waveform-player.min.js',
array(), // no dependencies
'1.14.0',
true // load in the footer
);
} );

To avoid a third-party CDN, copy the two files out of the package into your asset folder and enqueue them locally. Install the package to grab the dist/ files:

Terminal window
npm install @arraypress/waveform-player

Copy node_modules/@arraypress/waveform-player/dist/waveform-player.css and …/dist/waveform-player.min.js into your theme (e.g. assets/), then enqueue from there:

// In a theme — use get_theme_file_uri()
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'waveform-player',
get_theme_file_uri( 'assets/waveform-player.css' ),
array(),
'1.14.0'
);
wp_enqueue_script(
'waveform-player',
get_theme_file_uri( 'assets/waveform-player.min.js' ),
array(),
'1.14.0',
true
);
} );
// In a plugin — use plugins_url() relative to the plugin file
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'waveform-player',
plugins_url( 'assets/waveform-player.css', __FILE__ ),
array(),
'1.14.0'
);
wp_enqueue_script(
'waveform-player',
plugins_url( 'assets/waveform-player.min.js', __FILE__ ),
array(),
'1.14.0',
true
);
} );

A player is just a marker <div> carrying data-* attributes. The minimum is data-waveform-player plus an audio source:

<div
data-waveform-player
data-src="https://example.com/audio/track.mp3"
data-waveform-style="mirror"
data-title="Midnight Drive"
data-show-bpm="true"
></div>

There are three ways to get that markup into a WordPress page.

Option A — Custom HTML block / post content

Section titled “Option A — Custom HTML block / post content”

In the block editor, paste the <div> into a Custom HTML block. It will render and auto-initialise on the front end.

A shortcode is the most portable route: it works in the classic editor, the Shortcode block, widgets, and template calls via do_shortcode(), and it escapes every value server-side. This example maps a handful of shortcode attributes onto the player’s data-* contract and prints the <div>:

/**
* [waveform src="…" style="mirror" title="…" artist="…" height="64" show-bpm="false" peaks="…"]
*
* Renders a WaveformPlayer container. The enqueued script upgrades it on load.
*/
function wfp_waveform_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'src' => '', // audio URL (required) -> data-src
'style' => 'mirror', // bars|mirror|line|blocks|dots|seekbar
'title' => '', // -> data-title
'artist' => '', // -> data-artist
'height' => '64', // -> data-height
'show-bpm' => 'false', // -> data-show-bpm
'peaks' => '', // pre-computed peaks: .json URL, CSV, or JSON array -> data-waveform
),
$atts,
'waveform'
);
if ( empty( $atts['src'] ) ) {
return '';
}
// Build the attribute map; only emit what was provided.
$attributes = array(
'data-waveform-player' => '',
'data-src' => esc_url( $atts['src'] ),
'data-waveform-style' => sanitize_html_class( $atts['style'] ),
'data-height' => absint( $atts['height'] ),
'data-show-bpm' => ( 'true' === $atts['show-bpm'] ) ? 'true' : 'false',
);
if ( '' !== $atts['title'] ) {
$attributes['data-title'] = sanitize_text_field( $atts['title'] );
}
if ( '' !== $atts['artist'] ) {
$attributes['data-artist'] = sanitize_text_field( $atts['artist'] );
}
if ( '' !== $atts['peaks'] ) {
$attributes['data-waveform'] = $atts['peaks'];
}
$html = '';
foreach ( $attributes as $name => $value ) {
$html .= sprintf( ' %s="%s"', $name, esc_attr( $value ) );
}
return '<div' . $html . '></div>';
}
add_shortcode( 'waveform', 'wfp_waveform_shortcode' );

Authors then write:

[waveform src="https://example.com/audio/track.mp3" style="mirror" title="Midnight Drive" show-bpm="true"]

For a native block, register a dynamic block whose render_callback emits the same markup. The cleanest approach is to delegate to the shortcode function so there’s a single source of truth:

add_action( 'init', function () {
register_block_type( 'arraypress/waveform', array(
'api_version' => 3,
'attributes' => array(
'src' => array( 'type' => 'string', 'default' => '' ),
'style' => array( 'type' => 'string', 'default' => 'mirror' ),
'title' => array( 'type' => 'string', 'default' => '' ),
'showBpm' => array( 'type' => 'boolean', 'default' => false ),
),
'render_callback' => function ( $attributes ) {
return wfp_waveform_shortcode( array(
'src' => $attributes['src'] ?? '',
'style' => $attributes['style'] ?? 'mirror',
'title' => $attributes['title'] ?? '',
'show-bpm' => ! empty( $attributes['showBpm'] ) ? 'true' : 'false',
) );
},
) );
} );

Pair this with a block.json and an edit component for the editor UI (standard @wordpress/create-block scaffolding). The front-end output is identical to the shortcode, so the enqueued script upgrades it the same way.

Auto-init only fires once, on DOMContentLoaded. If markup arrives later — AJAX, infinite scroll, a block preview re-render, or a tabbed/accordion panel revealed after load — re-scan the DOM yourself. WaveformPlayer.init() is idempotent (it sets data-waveform-initialized="true" and skips anything already upgraded):

// After injecting new [data-waveform-player] markup into the page
window.WaveformPlayer.init();

By default the player downloads the audio and decodes it with Web Audio to build the waveform. For a media-heavy WordPress site, generate the peaks once and serve them as a sibling .json file, then pass it via data-waveform (the peaks shortcode attribute above). This skips the Web Audio decode entirely and draws instantly:

<div
data-waveform-player
data-src="https://example.com/audio/track.mp3"
data-waveform="https://example.com/audio/track.json"
></div>

The .json may be a bare peaks array or an object with embedded { peaks, markers }. See pre-computed peaks for generating the file (including WaveformPlayer.getPeaksUrl() to derive the sibling URL).

A quick reference for the attributes you’ll reach for most in shortcodes and blocks. The canonical key always wins over its alias.

Attribute Maps to Notes
data-src / data-url audio source data-url is canonical; data-src is the alias.
data-waveform-style / data-style visual style bars, mirror, line, blocks, dots, seekbar.
data-title / data-artist info block Title falls back to the filename if omitted.
data-artwork artwork image 40×40 thumbnail next to the title.
data-height canvas height (px) Default 64.
data-color-preset dark / light Omit for auto theme detection.
data-waveform-color / data-progress-color colours CSS colour, or a JSON array of gradient stops.
data-show-bpm BPM badge Boolean string "true". Pair with data-bpm to set a known value.
data-waveform pre-computed peaks .json URL, CSV, or JSON array — skips decoding.
data-markers cue markers JSON array of { time, label, color? }.

See the full data attributes reference for every key, and options for the JavaScript equivalents.

  • One global, classic script. The enqueued build defines window.WaveformPlayer, so it is a non-module script — wp_enqueue_script with the default args is correct. Don’t add type="module" via script_loader_tag.
  • Footer vs. head. Enqueue in the footer (true). The script attaches a DOMContentLoaded listener, so it upgrades players regardless of where it lands, but the footer keeps it out of the critical path.
  • Escaping is your job. WordPress doesn’t escape shortcode output — always run URLs through esc_url() and other values through esc_attr() / sanitize_text_field(), as in the example above.
  • Console prefix. Every log and error the library emits is prefixed [WaveformPlayer], which makes front-end debugging in WordPress straightforward.

Installation

All install paths — CDN, bundler, and the data-attribute auto-init contract. Getting started

Data attributes

The complete declarative API surface. Reference

Options

The JavaScript constructor options behind every attribute. Options

Pre-computed peaks

Generate and serve .json peaks to skip decoding. Peaks