Timeline plugin
The Timeline plugin draws a ruler of timestamps and tick marks beneath (or above) the waveform. It updates automatically as the waveform is resized and works with both fixed-duration and streaming audio.
See it in action: live Timeline example.
Setup
npm
import WaveSurfer from 'wavesurfer.js'
import TimelinePlugin from 'wavesurfer.js/dist/plugins/timeline.esm.js'
CDN (ESM)
import TimelinePlugin from 'https://unpkg.com/wavesurfer.js@7/dist/plugins/timeline.esm.js'
Register the plugin
Pass the plugin instance in the plugins array when creating WaveSurfer:
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
plugins: [
TimelinePlugin.create(),
],
})
That’s all that is required — the timeline renders below the waveform with sensible defaults.
Positioning
By default the timeline is appended below the waveform. To place it above, set insertPosition to 'beforebegin':
TimelinePlugin.create({
insertPosition: 'beforebegin',
})
insertPosition accepts any InsertPosition string ('beforebegin', 'afterbegin', 'beforeend', 'afterend'), but in practice only 'beforebegin' (above) and the default undefined/omitted (below) are commonly used.
When insertPosition is 'beforebegin' the timeline is absolutely positioned at the top of the waveform wrapper and overlays the waveform — adjust height and waveform padding accordingly if you do not want overlap.
You can also render the timeline into a completely separate DOM element by supplying a container (CSS selector string or HTMLElement):
TimelinePlugin.create({
container: '#my-timeline-div',
})
Intervals and labels
Three options control the tick density and which ticks receive numeric labels:
| Option | Type | Description |
|---|---|---|
timeInterval |
number |
Seconds between every tick mark. Calculated automatically from zoom level when omitted. |
primaryLabelInterval |
number |
Seconds between primary (full-opacity) labels. |
secondaryLabelInterval |
number |
Seconds between secondary (reduced-opacity) labels. |
Example — fixed tick every 5 s, primary label every 30 s, secondary label every 10 s:
TimelinePlugin.create({
timeInterval: 5,
primaryLabelInterval: 30,
secondaryLabelInterval: 10,
})
You can also express the label cadence as a count of ticks rather than a duration in seconds, using primaryLabelSpacing and secondaryLabelSpacing:
TimelinePlugin.create({
timeInterval: 0.5,
primaryLabelSpacing: 10, // label every 10th tick (= every 5 s)
secondaryLabelSpacing: 5, // label every 5th tick (= every 2.5 s)
})
Fractional or high-precision timeInterval values (e.g. 0.333, 1/3) can cause misalignment between ticks and labels because the plugin compares positions using modular arithmetic on floating-point numbers. Stick to values that are exact in decimal notation — 0.5, 0.25, 1, 5 — or use the primaryLabelSpacing / secondaryLabelSpacing count-based options instead.
The height option sets the timeline bar height in pixels (default 20). The style option accepts either a CSS string or a partial CSSStyleDeclaration object for additional inline styling:
TimelinePlugin.create({
height: 32,
style: { color: '#4F4A85', borderTop: '1px solid #4F4A85' },
})
Custom time format
Use formatTimeCallback to control how each label is rendered. The v7 signature takes one argument — the time in seconds — and must return a string:
TimelinePlugin.create({
formatTimeCallback: (seconds) => {
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
const ms = Math.round((seconds % 1) * 10)
return ms > 0
? `${m}:${String(s).padStart(2, '0')}.${ms}`
: `${m}:${String(s).padStart(2, '0')}`
},
})
In wavesurfer.js v6 formatTimeCallback received two arguments: (seconds, pxPerSec). In v7 the second argument (pxPerSec) was removed — the callback now receives only seconds: number. If you are migrating from v6, remove the second parameter from your callback to avoid relying on an undefined value.