Skip to content

Instantly share code, notes, and snippets.

@chrisn
Created August 31, 2023 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisn/376c73de59b1bcacb6f44bba45ea2811 to your computer and use it in GitHub Desktop.
Save chrisn/376c73de59b1bcacb6f44bba45ea2811 to your computer and use it in GitHub Desktop.
Peaks.js with multiple audio tracks
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Peaks.js Demo Page</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="titles">
<h1>Peaks.js</h1>
<p>
Peaks.js is a JavaScript library that allows you to display and
interact with audio waveforms in the browser.
</p>
<h2>Demo pages</h2>
<p>
The following pages demonstrate various configuration options:
</p>
<p>
<a href="/index.html">Precomputed Waveform Data</a> |
<a href="/webaudio.html">Web Audio API</a> |
<a href="/zoomable-waveform.html">Single Zoomable Waveform</a> |
<a href="/overview-waveform.html">Single Fixed Waveform</a> |
<a href="/cue-events.html">Cue Events</a> |
<a href="/set-source.html">Changing the Media URL</a> |
<a href="/multi-channel.html">Multi-Channel Waveform</a> |
<a href="/custom-markers">Custom Point and Segment Markers</a> |
<a href="/overlay-segments.html">Overlay Segments</a> |
External Audio Player |
<a href="/scrollbar.html">Scrollbar</a>
</p>
<h2>Demo: External Player</h2>
<p>
This demo shows how to use an external player for audio playback with Peaks.js.
A Tone.js player is set up in the example.
</p>
</div>
<div class="waveform-container">
<div id="zoomview-container"></div>
</div>
<div class="waveform-container">
<div id="overview-container"></div>
</div>
<div id="demo-controls">
<div id="controls">
<div>
<button data-action="play">Play</button>
<button data-action="pause">Pause</button>
<input type="text" id="seek-time" value="0.0">
<button data-action="seek">Seek</button>
<button data-action="zoom-in">Zoom in</button>
<button data-action="zoom-out">Zoom out</button>
<input type="text" id="segment-start" value="2.0">
<input type="text" id="segment-end" value="5.0">
<button data-action="play-segment">Play segment</button>
</div>
<div>
<label for="select-audio">Select audio:</label>
<select id="select-audio"></select>
</div>
</div>
</div>
<script src="/peaks.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js"></script>
<script>
function getSources() {
return [
{
title: 'Tree of Life',
mediaUrl: '/TOL_6min_720p_download.mp3',
},
{
title: 'Desert Island Discs',
mediaUrl: '/sample.mp3',
},
{
title: 'BBC Sound Effect (Cars)',
mediaUrl: '/07023003.mp3',
}
];
}
function fetchAndDecode(audioContext, url) {
// Load audio file into an AudioBuffer
return fetch(url)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(buffer) {
return audioContext.decodeAudioData(buffer);
});
}
function fetchAndUpdateWaveform(peaksInstance, audioContext, source) {
return fetchAndDecode(audioContext, source.mediaUrl)
.then(function(audioBuffer) {
// Update the Peaks.js waveform view with the new AudioBuffer.
// The AudioBuffer is passed through to the external player's setSource()
// method, to update the player.
peaksInstance.setSource({
webAudio: {
audioBuffer: audioBuffer,
scale: 128,
multiChannel: false
}
}, function(error) {
if (error) {
console.error('setSource error', error);
}
});
});
}
(async function(Peaks) {
async function initPeaks(options) {
return new Promise(function(resolve, reject) {
Peaks.init(options, function(err, peaks) {
if (err) {
reject(err);
}
else {
resolve(peaks);
}
});
});
}
const audioContext = Tone.context;
const audioBuffer1 = await fetchAndDecode(audioContext, "/TOL_6min_720p_download.mp3");
const audioBuffer2 = await fetchAndDecode(audioContext, "/sample.mp3");
const player = {
externalPlayer1: new Tone.Player(audioBuffer1).toDestination(),
externalPlayer2: new Tone.Player(audioBuffer2).toDestination(),
eventEmitter: null,
init: function(eventEmitter) {
this.eventEmitter = eventEmitter;
this.externalPlayer1.sync();
this.externalPlayer2.sync();
this.externalPlayer1.start();
this.externalPlayer2.start();
Tone.Transport.scheduleRepeat(() => {
const time = this.getCurrentTime();
eventEmitter.emit('player.timeupdate', time);
if (time >= this.getDuration()) {
Tone.Transport.stop();
}
}, 0.25);
return Promise.resolve();
},
destroy: function() {
Tone.context.dispose();
this.externalPlayer1 = null;
this.externalPlayer2 = null;
this.eventEmitter = null;
},
setSource: function(opts) {
if (this.isPlaying()) {
this.pause();
}
// TODO: Update the Tone.js Player object with the new AudioBuffer
return Promise.reject();
},
play: function() {
return Tone.start().then(() => {
Tone.Transport.start();
this.eventEmitter.emit('player.playing', this.getCurrentTime());
});
},
pause: function() {
Tone.Transport.pause();
this.eventEmitter.emit('player.pause', this.getCurrentTime());
},
isPlaying: function() {
return Tone.Transport.state === "started";
},
seek: function(time) {
Tone.Transport.seconds = time;
this.eventEmitter.emit('player.seeked', this.getCurrentTime());
this.eventEmitter.emit('player.timeupdate', this.getCurrentTime());
},
isSeeking: function() {
return false;
},
getCurrentTime: function() {
return Tone.Transport.seconds;
},
getDuration: function() {
const duration1 = this.externalPlayer1.buffer.duration;
const duration2 = this.externalPlayer2.buffer.duration;
return duration1 > duration2 ? duration1 : duration2;
}
};
const peaksInstance = await initPeaks({
zoomview: {
container: document.getElementById('zoomview-container')
},
player: player,
webAudio: {
audioBuffer: audioBuffer1,
scale: 128,
multiChannel: false
},
keyboard: true,
showPlayheadTime: true,
zoomLevels: [128, 256, 512, 1024, 2048, 4096]
});
const peaksInstance2 = await initPeaks({
zoomview: {
container: document.getElementById('overview-container')
},
player: player,
webAudio: {
audioBuffer: audioBuffer2,
scale: 128,
multiChannel: false
},
keyboard: true,
showPlayheadTime: true,
zoomLevels: [128, 256, 512, 1024, 2048, 4096]
});
console.log('Peaks instance ready');
document.querySelector('[data-action="play"]').addEventListener('click', function() {
peaksInstance.player.play();
});
document.querySelector('[data-action="pause"]').addEventListener('click', function() {
peaksInstance.player.pause();
});
document.querySelector('button[data-action="seek"]').addEventListener('click', function(event) {
const time = document.getElementById('seek-time').value;
const seconds = parseFloat(time);
if (!Number.isNaN(seconds)) {
peaksInstance.player.seek(seconds);
}
});
document.querySelector('[data-action="zoom-in"]').addEventListener('click', function() {
peaksInstance.zoom.zoomIn();
});
document.querySelector('[data-action="zoom-out"]').addEventListener('click', function() {
peaksInstance.zoom.zoomOut();
});
document.querySelector('[data-action="play-segment"]').addEventListener('click', function () {
const start = document.getElementById('segment-start').value;
const startInSeconds = parseFloat(start);
const end = document.getElementById('segment-end').value;
const endInSeconds = parseFloat(end);
peaksInstance.player.playSegment({ startTime: startInSeconds, endTime: endInSeconds, editable: true });
});
const select = document.getElementById('select-audio');
const sources = getSources();
for (let i = 0; i < sources.length; i++) {
select.options[i] = new Option(sources[i].title, i);
}
select.addEventListener('change', function(event) {
const source = sources[event.target.value];
fetchAndUpdateWaveform(peaksInstance, audioContext, source);
});
})(peaks);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment