Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Build Status

Peaks.js

A client-side JavaScript component to display and interact with audio waveforms in the browser

Peaks.js was developed by BBC R&D to allow users to make accurate clippings of audio content in the browser, using a backend API that serves the waveform data.

Peaks.js uses the HTML canvas element to display the waveform at different zoom levels, and has configuration options to allow you to customise the waveform views. Peaks.js allows users to interact with the waveform views, including zooming and scrolling, and creating point or segment markers that denote content to be clipped or for reference, e.g., distinguishing music from speech or identifying different music tracks.

Features

  • Zoomable and scrollable waveform view
  • Fixed width waveform view
  • Mouse, touch, scroll wheel, and keyboard interaction
  • Client-side waveform computation, using the Web Audio API, for convenience
  • Server-side waveform computation, for efficiency
  • Mono, stereo, or multi-channel waveform views
  • Create point or segment marker annotations
  • Customisable waveform views

You can read more about the project and see a demo here.

Contents

Installation

  • npm: npm install --save peaks.js
  • bower: bower install --save peaks.js
  • Browserify CDN: http://wzrd.in/standalone/peaks.js
  • cdnjs: https://cdnjs.com/libraries/peaks.js

Demos

The demo folder contains some working examples of Peaks.js in use. To view these, enter the following commands:

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install
npm start

and then open your browser at http://localhost:8080.

Using Peaks.js in your own project

Peaks.js can be included in any web page by following these steps:

  1. include it your web page
  2. include a media element and its waveform data file
  3. initialise Peaks.js
<divid="peaks-container"><divid="zoomview-container"></div><divid="overview-container"></div></div><audio><sourcesrc="test_data/sample.mp3" type="audio/mpeg"><sourcesrc="test_data/sample.ogg" type="audio/ogg"></audio><scriptsrc="bower_components/requirejs/require.js" data-main="app.js"></script>

Note that the container divs should be left empty, as shown above, as their content will be replaced by the waveform view canvas elements.

Start using AMD and require.js

AMD modules work out of the box without any optimiser.

// in app.js// configure peaks pathrequirejs.config({paths: {peaks: 'bower_components/peaks.js/src/main',EventEmitter: 'bower_components/eventemitter2/lib/eventemitter2',Konva: 'bower_components/konvajs/konva','waveform-data': 'bower_components/waveform-data/dist/waveform-data.min'}});// require itrequire(['peaks'],function(Peaks){constoptions={containers: {overview: document.getElementById('overview-container'),zoomview: document.getElementById('zoomview-container')}mediaElement: document.querySelector('audio'),dataUri: 'test_data/sample.json'};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready.});});

Start using ES2015 module loader

This works well with systems such as Meteor, webpack and browserify (with babelify transform).

importPeaksfrom'peaks.js';constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using CommonJS module loader

This works well with systems such as Meteor, webpack and browserify.

varPeaks=require('peaks.js');constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});

Start using vanilla JavaScript

<scriptsrc="node_modules/peaks.js/peaks.js"></script><script>(function(Peaks){constoptions={ ... };Peaks.init(options,function(err,peaks){// ...});})(peaks);</script>

Generate waveform data

Peaks.js uses waveform data files produced by audiowaveform. These can be generated in either binary (.dat) or JSON format. Binary format is preferred because of the smaller file size, but this is only compatible with browsers that support Typed Arrays.

You should also use the -b 8 option when generating waveform data files, as Peaks.js does not currently support 16-bit waveform data files, and also to minimise file size.

To generate a binary waveform data file:

audiowaveform -i sample.mp3 -o sample.dat -b 8

To generate a JSON format waveform data file:

audiowaveform -i sample.mp3 -o sample.json -b 8

Refer to the man page audiowaveform(1) for full details of the available command line options.

Web Audio based waveforms

Peaks.js can use the Web Audio API to generate waveforms, which means you do not have to pre-generate a dat or json file beforehand. However, note that this requires the browser to download the entire audio file before the waveform can be shown, and this process can be CPU intensive, so is not recommended for long audio files.

To use Web Audio, omit the dataUri option and instead pass a webAudio object that contains an AudioContext instance. Your browser must support the Web Audio API.

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioContext: audioContext}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});

Alternatively, if you have an AudioBuffer containing decoded audio samples, e.g., from AudioContext.decodeAudioData then an AudioContext is not needed:

constAudioContext=window.AudioContext||window.webkitAudioContext;constaudioContext=newAudioContext();// arrayBuffer contains the encoded audio (e.g., MP3 format)audioContext.decodeAudioData(arrayBuffer).then(function(audioBuffer){constoptions={containers: {overview: document.getElementById('overview-waveform'),zoomview: document.getElementById('zoomview-waveform')},mediaElement: document.querySelector('audio'),webAudio: {audioBuffer: audioBuffer}};Peaks.init(options,function(err,peaks){// Do something when the waveform is displayed and ready});});

Configuration

The available options for configuration of the viewer are as follows:

varoptions={/** REQUIRED OPTIONS **/// Containing element: eithercontainer: document.getElementById('peaks-container'),// or (preferred):containers: {zoomview: document.getElementById('zoomview-container'),overview: document.getElementById('overview-container')},// HTML5 Media element containing an audio trackmediaElement: document.querySelector('audio'),/** Optional config with defaults **/// URI to waveform data file in binary or JSONdataUri: {arraybuffer: '../test_data/sample.dat',json: '../test_data/sample.json',},// If true, Peaks.js will send credentials with all network requests,// i.e., when fetching waveform data.withCredentials: false,webAudio: {// A Web Audio AudioContext instance which can be used// to render the waveform if dataUri is not providedaudioContext: newAudioContext(),// Alternatively, provide an AudioBuffer containing the decoded audio// samples. In this case, an AudioContext is not neededaudioBuffer: null,// If true, the waveform will show all available channels.// If false, the audio is shown as a single channel waveform.multiChannel: false},// async logging functionlogger: console.error.bind(console),// if true, emit cue events on the Peaks instance (see Cue Events)emitCueEvents: false,// default height of the waveform canvases in pixelsheight: 200,// Array of zoom levels in samples per pixel (big >> small)zoomLevels: [512,1024,2048,4096],// Bind keyboard controlskeyboard: false,// Keyboard nudge increment in seconds (left arrow/right arrow)nudgeIncrement: 0.01,// Colour for the in marker of segmentsinMarkerColor: '#a0a0a0',// Colour for the out marker of segmentsoutMarkerColor: '#a0a0a0',// Colour for the zoomed in waveformzoomWaveformColor: 'rgba(0, 225, 128, 1)',// Colour for the overview waveformoverviewWaveformColor: 'rgba(0,0,0,0.2)',// Colour for the overview waveform rectangle// that shows what the zoom view showsoverviewHighlightRectangleColor: 'grey',// Colour for segments on the waveformsegmentColor: 'rgba(255, 161, 39, 1)',// Colour of the play headplayheadColor: 'rgba(0, 0, 0, 1)',// Colour of the play head textplayheadTextColor: '#aaa',// Show current time next to the play head// (zoom view only)showPlayheadTime: false,// the color of a point markerpointMarkerColor: '#FF0000',// Colour of the axis gridlinesaxisGridlineColor: '#ccc',// Colour of the axis labelsaxisLabelColor: '#aaa',// Random colour per segment (overrides segmentColor)randomizeSegmentColor: true,// Array of initial segment objects with startTime and// endTime in seconds and a boolean for editable.// See below.segments: [{startTime: 120,endTime: 140,editable: true,color: "#ff0000",labelText: "My label"},{startTime: 220,endTime: 240,editable: false,color: "#00ff00",labelText: "My Second label"}],// Array of initial point objectspoints: [{time: 150,editable: true,color: "#00ff00",labelText: "A point"},{time: 160,editable: true,color: "#00ff00",labelText: "Another point"}]}

Advanced configuration

The marker and label Konva.js objects may be overridden to give the segment markers or label your own custom appearance (see main.js / waveform.mixins.js, Konva Polygon Example and Konva Text Example):

{segmentInMarker: mixins.defaultInMarker(p.options),segmentOutMarker: mixins.defaultOutMarker(p.options),segmentLabelDraw: mixins.defaultSegmentLabelDraw(p.options)}

Note: This part of the API is not yet stable, and so may change at any time.

API

Initialisation

The top level Peaks object exposes a factory function to create new Peaks instances.

Peaks.init(options, callback)

Returns a new Peaks instance with the assigned options. The callback is invoked after the instance has been created and initialised. You can create and manage several Peaks instances within a single page with one or several configurations.

constoptions={ ... };Peaks.init(options,function(err,peaks){console.log(peaks.player.getCurrentTime());});

For backwards compatibility, you can still create a new Peaks instance using:

constpeaks=Peaks.init({ ... });peaks.on('ready',function(){console.log(peaks.player.getCurrentTime());});

instance.setSource(options, callback)

Changes the audio or video media source associated with the Peaks instance. Depending on the options specified, the waveform is either requested from a server or is generated by the browser using the Web Audio API.

The options parameter is an object with the following keys. Either dataUri or webAudio must be specified, but not both.

  • mediaUrl: Audio or video media URL
  • dataUri: (optional) If requesting waveform data from a server, this should be an object containing arraybuffer and/or json values
    • arraybuffer: (optional) URL of the binary format waveform data (.dat) to request
    • json: (optional) URL of the JSON format waveform data to request
  • webAudio: (optional) If using the Web Audio API to generate the waveform, this should be an object containing the following values:
    • audioContext: (optional) A Web Audio AudioContext instance, used to compute the waveform data from the media
    • audioBuffer: (optional) A Web Audio AudioBuffer instance, containing the decoded audio samples. If present, this audio data is used and the mediaUrl is not fetched.
    • multiChannel: (optional) If true, the waveform will show all available channels. If false (the default), the audio is shown as a single channel waveform.
  • withCredentials: (optional) If true, Peaks.js will send credentials when requesting the waveform data from a server
  • zoomLevels: (optional) Array of zoom levels in samples per pixel. If not present, the values passed to Peaks.init() will be used

For example, to change the media URL and request pre-computed waveform data from the server:

constpeaks=Peaks.init({ ... });constoptions={mediaUrl: '/sample.mp3',dataUri: {arraybuffer: '/sample.dat',json: '/sample.json',}};peaks.setSource(options,function(error){// Waveform updated});

Or, to change the media URL and use the Web Audio API to generate the waveform:

constpeaks=Peaks.init({ ... });constaudioContext=newAudioContext();constoptions={mediaUrl: '/sample.mp3',webAudio: {audioContext: audioContext,multiChannel: true}};peaks.setSource(options,function(error){// Waveform updated});

Player API

instance.player.play()

Starts media playback, from the current time position.

instance.player.play();

instance.player.pause()

Pauses media playback.

instance.player.pause();

instance.player.getCurrentTime()

Returns the current time from the associated media element, in seconds.

consttime=instance.player.getCurrentTime();

instance.player.getDuration()

Returns the duration of the media, in seconds.

constduration=instance.player.getDuration();

instance.player.seek(time)

Seeks the media element to the given time, in seconds.

instance.player.seek(5.85);consttime=instance.player.getCurrentTime();

instance.player.playSegment(segment)

Plays a given segment of the media.

constsegment=instance.segments.add({startTime: 5.0,endTime: 15.0,editable: true});// Plays from 5.0 to 15.0, then stops.instance.player.playSegment(segment);

Views API

A single Peaks instance may have up to two associated waveform views: a zoomable view, or "zoomview", and a non-zoomable view, or "overview".

The Views API allows you to create or obtain references to these views.

instance.views.getView(name)

Returns a reference to one of the views. The name parameter can be omitted if there is only one view, otherwise it should be set to either 'zoomview' or 'overview'.

constview=instance.views.getView('zoomview');

instance.views.createZoomview(container)

Creates a zoomable waveform view in the given container element.

constcontainer=document.getElementById('zoomview-container');constview=instance.views.createZoomview(container);

instance.views.createOverview(container)

Creates a non-zoomable ("overview") waveform view in the given container element.

constcontainer=document.getElementById('overview-container');constview=instance.views.createOverview(container);

Zoom API

instance.zoom.zoomOut()

Zooms in the waveform zoom view by one level.

Assuming the Peaks instance has been created with zoom levels: 512, 1024, 2048, 4096

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();// zoom level is now 1024

instance.zoom.zoomIn()

Zooms in the waveform zoom view by one level.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomIn();// zoom level is still 512instance.zoom.zoomOut();// zoom level is now 1024instance.zoom.zoomIn();// zoom level is now 512 again

instance.zoom.setZoom(index)

Sets the zoom level to the element in the options.zoomLevels array at index index.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.setZoom(3);// zoom level is now 4096

instance.zoom.getZoom()

Returns the current zoom level, as an index into the options.zoomLevels array.

constinstance=Peaks.init({ ...,zoomLevels: [512,1024,2048,4096]});instance.zoom.zoomOut();console.log(instance.zoom.getZoom());// -> 1

Segments API

Segments give the ability to visually tag timed portions of the audio media. This is a great way to provide visual cues to your users.

instance.segments.add({startTime, endTime, editable, color, labelText, id})

instance.segments.add(segment[])

Adds a segment to the waveform timeline. Accepts the following parameters:

  • startTime: the segment start time (seconds)
  • endTime: the segment end time (seconds)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to false)
  • color: (optional) the segment color. If not specified, the segment is given a default color (see the segmentColor and randomizeSegmentColoroptions)
  • labelText: (option) a text label which is displayed when the user hovers the mouse pointer over the segment
  • id: (optional) the segment identifier. If not specified, the segment is automatically given a unique identifier
// Add non-editable segment, from 0 to 10.5 seconds, with a random colorinstance.segments.add({startTime: 0,endTime: 10.5});

Alternatively, provide an array of segment objects to add all those segments at once.

instance.segments.add([{startTime: 0,endTime: 10.5,labelText: '0 to 10.5 seconds non-editable demo segment'},{startTime: 3.14,endTime: 4.2,color: '#666'}]);

instance.segments.getSegments()

Returns an array of all segments present on the timeline.

constsegments=instance.segments.getSegments();

instance.segments.getSegment(id)

Returns the segment with the given id, or null if not found.

constsegment=instance.segments.getSegment('peaks.segment.3');

instance.segments.removeByTime(startTime[, endTime])

Removes any segment which starts at startTime (seconds), and which optionally ends at endTime (seconds).

The return value indicates the number of deleted segments.

instance.segments.add([{startTime: 10,endTime: 12},{startTime: 10,endTime: 20}]);// Remove both segments as they start at `10`instance.segments.removeByTime(10);// Remove only the first segmentinstance.segments.removeByTime(10,12);

instance.segments.removeById(segmentId)

Removes segments with the given identifier.

instance.segments.removeById('peaks.segment.3');

instance.segments.removeAll()

Removes all segments.

instance.segments.removeAll();

Segment API

A segment's properties can be updated programatically.

segment.update({startTime, endTime, labelText, color, editable})

Updates an existing segment. Accepts a single parameter - options - with the following keys:

  • startTime: (optional) the segment start time (seconds, defaults to current value)
  • endTime: (optional) the segment end time (seconds, defaults to current value)
  • editable: (optional) sets whether the segment is user editable (boolean, defaults to current value)
  • color: (optional) the segment color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the segment (defaults to current value)
constinstance=Peaks.init({ ... });instance.segments.add({ ... });constsegment=instance.segments.getSegments()[0]// Or use instance.segments.getSegment(id)segment.update({startTime: 7});segment.update({startTime: 7,labelText: "new label text"});segment.udpate({startTime: 7,endTime: 9,labelText: 'new label text'});// etc.

Points API

Points give the ability to visually tag points in time of the audio media.

instance.points.add({time, editable, color, labelText, id})

instance.points.add(point[])

Adds one or more points to the waveform timeline. Accepts the following parameters:

  • time: the point time (seconds)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to false)
  • color: (optional) the point color. If not specified, the point is given a default color (see the pointMarkerColoroption)
  • labelText: (optional) a text label which is displayed next to the segment. If not given, the point's time is displayed
  • id: (optional) the point identifier. If not specified, the point is automatically given a unique identifier
// Add non-editable point, with a random colorinstance.points.add({time: 3.5});

Alternatively, provide an array of point objects to add several at once.

instance.points.add([{time: 3.5,labelText: 'Test point',color: '#666'},{time: 5.6,labelTect: 'Another test point',color: '#666'}]);

instance.points.getPoints()

Returns an array of all points present on the timeline.

constpoints=instance.points.getPoints();

instance.points.getPoint(id)

Returns the point with the given id, or null if not found.

constpoint=instance.points.getPoint('peaks.point.3');

instance.points.removeByTime(time)

Removes any point at the given time (seconds).

instance.points.removeByTime(10);

instance.points.removeById(pointId)

Removes points with the given identifier.

instance.points.removeById('peaks.point.3');

instance.points.removeAll()

Removes all points.

instance.points.removeAll();

Point API

A point's properties can be updated programatically.

point.update({time, labelText, color, editable})

Updates an existing point. Accepts a single parameter - options - with the following keys:

  • time: (optional) the point's time (seconds, defaults to current value)
  • editable: (optional) sets whether the point is user editable (boolean, defaults to current value)
  • color: (optional) the point color (defaults to current value)
  • labelText: (optional) a text label which is displayed when the user hovers the mouse pointer over the point (defaults to current value)
constinstance=Peaks.init({ ... });instance.points.add({ ... });constpoint=instance.points.getPoints()[0]// Or use instance.points.getPoint(id)point.update({time: 7});point.update({time: 7,labelText: "new label text"});// etc.

View Settings API

Some view properties can be updated programmatically.

view.setAmplitudeScale(scale)

Changes the amplitude (vertical) waveform scale. The default scale is 1.0. If greater than 1.0, the waveform is increased in height. If between 0.0 and 1.0, the waveform is reduced in height.

constview=instance.views.getView('zoomview');view.setAmplitudeScale(1.0);

view.setWaveformColor(color)

Sets the waveform color, as a string containing any valid CSS color value.

The initial color is controlled by the zoomWaveformColor and overviewWaveformColor configuration options.

constview=instance.views.getView('zoomview');view.setWaveformColor('#800080');// Purple

view.showPlayheadTime(show)

Shows or hides the current playback time, shown next to the playhead.

The initial setting is false, for the overview waveform view, or controlled by the showPlayheadTime configuration option for the zoomable waveform view.

constview=instance.views.getView('zoomview');view.showPlayeadTime(false);// Remove the time from the playhead marker.

view.enableAutoScroll(enable)

Enables or disables auto-scroll behaviour (enabled by default). This only applies to the zoomable waveform view.

constview=instance.views.getView('zoomview');view.enableAutoScroll(false);

Cue events

Emit events when the playhead reaches a point or segment boundary.

constpeaks=Peaks.init({ ...,emitCueEvents: true});peaks.on('points.enter',function(point){ ... });peaks.on('segments.enter',function(segment){ ... });peaks.on('segments.exit',function(segment){ ... });

Destruction

instance.destroy()

Releases resources used by an instance. This can be useful when reinitialising Peaks.js within a single page application.

instance.destroy();

Events

Peaks instances emit events to enable you to extend its behaviour according to your needs.

Media / User interactions

Event nameArguments
peaks.ready(none)

Waveforms

Event nameArguments
zoom.updateNumber currentZoomLevel, Number previousZoomLevel

Segments

Event nameArguments
segments.addArray<Segment> segments
segments.removeArray<Segment> segments
segments.remove_all(none)
segments.draggedSegment segment
segments.mouseenterSegment segment
segments.mouseleaveSegment segment
segments.clickSegment segment

Points

Event nameArguments
points.addArray<Point> points
points.removeArray<Point> points
points.remove_all(none)
points.dragstartPoint point
points.dragmovePoint point
points.dragendPoint point
points.mouseenterPoint point
points.mouseleavePoint point
points.dblclickPoint point

Cue Events

To enable cue events, call Peaks.init() with the { emitCueEvents: true } option. When the playhead reaches a point or segment boundary, a cue event is emitted.

Event nameArguments
points.enterPoint point
segments.enterSegment segment
segments.exitSegment segment

Building Peaks.js

You might want to build a minified standalone version of Peaks.js, to test a contribution or to run additional tests. The project bundles everything you need to do so.

Prerequisites

git clone git@github.com:bbc/peaks.js.git
cd peaks.js
npm install

Building

This command will produce a UMD-compatible minified standalone version of Peaks.js, which allows you to use it with AMD or CommonJS module loaders, or even as vanilla JavaScript.

npm run build

The output of the build is a file named peaks.js, alongside its associated source map.

Testing

Tests run in Karma using Mocha + Chai + Sinon.

  • npm test should work for simple one time testing.
  • npm test -- --glob %pattern% to run selected test suite(s) only
  • npm run test-watch if you are developing and want to repeatedly run tests in a browser on your machine.
  • npm run test-watch -- --glob %pattern% is also available

Contributing

If you'd like to contribute to Peaks.js, please take a look at our contributer guidelines.

License

See COPYING.

This project includes sample audio from the radio show Desert Island Discs, used under the terms of the Creative Commons 3.0 Unported License.

Credits

Copyright 2019 British Broadcasting Corporation

About

JavaScript UI component for interacting with audio waveforms

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages