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.