wavesurfer.js
Advanced tools
Comparing version 0.1.7 to 0.1.8
{ | ||
"name" : "wavesurfer.js", | ||
"version" : "0.1.7", | ||
"version" : "0.1.8", | ||
"homepage" : "https://github.com/katspaugh/wavesurfer.js", | ||
@@ -5,0 +5,0 @@ "main" : "build/wavesurfer.min.js", |
@@ -0,3 +1,4 @@ | ||
var wavesurfer; | ||
document.addEventListener('DOMContentLoaded', function () { | ||
var wavesurfer = Object.create(WaveSurfer); | ||
wavesurfer = Object.create(WaveSurfer); | ||
wavesurfer.init({ | ||
@@ -17,3 +18,2 @@ container: '#wave', | ||
cursorWidth: 2, | ||
handlerSize: 25, | ||
}); | ||
@@ -20,0 +20,0 @@ |
{ | ||
"name": "wavesurfer.js", | ||
"version": "0.1.7", | ||
"version": "0.1.8", | ||
"description": "Interactive navigable audio visualization using Web Audio and Canvas", | ||
@@ -5,0 +5,0 @@ "main": "build/wavesurfer.cjs.js", |
@@ -15,16 +15,24 @@ wavesurfer.js | ||
var wavesurfer = Object.create(WaveSurfer); | ||
```javascript | ||
var wavesurfer = Object.create(WaveSurfer); | ||
``` | ||
Initialize it with a container element (plus some options): | ||
wavesurfer.init({ container: '#wave', waveColor: 'violet', | ||
progressColor: 'purple' }); | ||
```javascript | ||
wavesurfer.init({ container: '#wave', waveColor: 'violet', | ||
progressColor: 'purple' }); | ||
``` | ||
Subscribe to some events: | ||
wavesurfer.on('ready', function () { wavesurfer.play(); }); | ||
```javascript | ||
wavesurfer.on('ready', function () { wavesurfer.play(); }); | ||
``` | ||
Load an audio file from a URL: | ||
wavesurfer.load('example/media/demo.wav'); | ||
```javascript | ||
wavesurfer.load('example/media/demo.wav'); | ||
``` | ||
@@ -86,2 +94,4 @@ See the example code | ||
* `skipBackward()` | ||
* `seekTo(progress)` – seeks to a progress [0..1]. | ||
* `seekAndCenter(progress)` – seeks to a progress and centers view [0..1]. | ||
* `skip(offset)` – skips a number of seconds from the current | ||
@@ -96,5 +106,6 @@ position (use a negative value to go backwards). | ||
`width` (defaults to the global option `markerWidth`). Returns a | ||
marker object which you can update later | ||
marker object which you can update later. | ||
(`marker.update(options)`). | ||
* `clearMarks()` – removes all markers. | ||
* `clearRegions()` – removes all regions. | ||
* `empty()` – clears the waveform as if a zero-length audio is | ||
@@ -104,4 +115,9 @@ loaded. | ||
nodes. | ||
* `region(options)` – creates a region on the waveform. Options are `id` | ||
(random if not set), `startPosition` (in seconds), `endPosition` | ||
(in seconds) and `color`. Returns a region object which you can | ||
update later. | ||
* `toggleLoopSelection()` – toggles whether playback should loop | ||
inside the selection. | ||
* `toggleScroll()` – toggles scroll on parent | ||
* `getSelection()` – returns an object representing the current | ||
@@ -119,2 +135,3 @@ selection. This object will have the following keys: | ||
normal speed, `2` is double speed and so on). | ||
* `playPauseSelection()` – plays selection if paused, pauses if playing. | ||
@@ -127,4 +144,6 @@ Connecting filters | ||
var lowpass = wavesurfer.backend.ac.createBiquadFilter(); | ||
wavesurfer.backend.setFilter(lowpass); | ||
```javascript | ||
var lowpass = wavesurfer.backend.ac.createBiquadFilter(); | ||
wavesurfer.backend.setFilter(lowpass); | ||
``` | ||
@@ -145,2 +164,12 @@ Events | ||
* `mark` – when a mark is reached. Passes the mark object. | ||
* `marked` – when a mark is created. | ||
* `mark-update` – when a mark is updated. | ||
* `mark-removed` – when a mark is removed. | ||
* `region-in` – when entering a region. | ||
* `region-out`– when leaving a region. | ||
* `region-created` – when a region is created. | ||
* `region-updated` – when a region is updated. | ||
* `region-removed` – when a region is removed. | ||
* `selection-update` – when a selection is updated. Has an object parameter | ||
containig selection information or null if the selection is cleared. | ||
* `error` – on error, passes an error message. | ||
@@ -147,0 +176,0 @@ |
@@ -76,5 +76,5 @@ 'use strict'; | ||
this.progressCc.fillStyle = this.params.progressColor; | ||
var coef = this.height / max; | ||
var halfH = this.height / 2; | ||
var coef = halfH / max; | ||
var scale = this.width / peaks.length; | ||
@@ -122,6 +122,6 @@ | ||
if (this.params.draggableMarkers) { | ||
handler = document.createElement('div'); | ||
handler.id = mark.id + '_handler'; | ||
handler.innerHTML = '●'; | ||
if (mark.draggable) { | ||
handler = document.createElement('handler'); | ||
handler.id = mark.id + '-handler'; | ||
handler.className = 'wavesurfer-handler'; | ||
markEl.appendChild(handler); | ||
@@ -140,3 +140,3 @@ } | ||
this.params.draggableMarkers && (function () { | ||
mark.draggable && (function () { | ||
var drag = {}; | ||
@@ -169,8 +169,8 @@ | ||
if (this.params.draggableMarkers) { | ||
if (mark.draggable) { | ||
this.style(handler, { | ||
position: 'absolute', | ||
fontSize: this.params.handlerSize + "px", | ||
fontFamily: 'monospace', | ||
cursor: 'col-resize', | ||
width: '12px', | ||
height: '15px' | ||
}); | ||
@@ -180,3 +180,3 @@ this.style(handler, { | ||
top: markEl.offsetHeight / 2 - handler.offsetHeight / 2 + 'px', | ||
color: mark.color | ||
backgroundColor: mark.color | ||
}); | ||
@@ -208,2 +208,46 @@ } | ||
addRegion: function (region) { | ||
var my = this; | ||
var regionEl = document.createElement('region'); | ||
regionEl.id = region.id; | ||
this.wrapper.appendChild(regionEl); | ||
regionEl.addEventListener('mouseover', function (e) { | ||
my.fireEvent('region-over', region, e); | ||
}); | ||
regionEl.addEventListener('mouseleave', function (e) { | ||
my.fireEvent('region-leave', region, e); | ||
}); | ||
regionEl.addEventListener('click', function (e) { | ||
my.fireEvent('region-click', region, e); | ||
}); | ||
this.updateRegion(region); | ||
}, | ||
updateRegion: function (region) { | ||
var regionEl = document.getElementById(region.id); | ||
var left = Math.max(0, Math.round( | ||
region.startPercentage * this.scrollWidth)); | ||
var width = Math.max(0, Math.round( | ||
region.endPercentage * this.scrollWidth)) - left; | ||
this.style(regionEl, { | ||
height: '100%', | ||
position: 'absolute', | ||
zIndex: 4, | ||
left: left + 'px', | ||
top: '0px', | ||
width: width + 'px', | ||
backgroundColor: region.color | ||
}); | ||
}, | ||
removeRegion: function (region) { | ||
var regionEl = document.getElementById(region.id); | ||
if (regionEl) { | ||
this.wrapper.removeChild(regionEl); | ||
} | ||
}, | ||
drawSelection: function () { | ||
@@ -210,0 +254,0 @@ this.eraseSelection(); |
@@ -118,3 +118,3 @@ 'use strict'; | ||
recenter: function (percent) { | ||
var position = this.containerWidth * percent; | ||
var position = this.scrollWidth * percent; | ||
this.recenterOnPosition(position, true); | ||
@@ -216,2 +216,8 @@ }, | ||
addRegion: function (region) {}, | ||
removeRegion: function (region) {}, | ||
updateRegion: function (region) {}, | ||
drawSelection: function () {}, | ||
@@ -218,0 +224,0 @@ |
@@ -10,5 +10,5 @@ 'use strict'; | ||
selectionColor: '#0fc', | ||
selectionBorder: false, | ||
selectionForeground: false, | ||
selectionBorderColor: '#000', | ||
handlerSize : 15, | ||
cursorWidth : 1, | ||
@@ -29,4 +29,3 @@ markerWidth : 2, | ||
audioRate : 1, | ||
interact : true, | ||
draggableMarkers: false | ||
interact : true | ||
}, | ||
@@ -49,3 +48,7 @@ | ||
this.once('marked', this.bindMarks.bind(this)); | ||
this.once('region-created', this.bindRegions.bind(this)); | ||
// Region objects | ||
this.regions = {}; | ||
// Used to save the current volume when muting so we can | ||
@@ -134,20 +137,7 @@ // restore once unmuted | ||
}); | ||
this.drawer.on('drag-mark', function (drag, mark) { | ||
if (mark.type === 'selMark') { | ||
my.updateSelectionByMark(drag, mark); | ||
} | ||
else if (mark.type !== 'selMark') { | ||
my.moveMarker(drag, mark); | ||
} | ||
}); | ||
} | ||
// Drag Marker event | ||
if (this.params.dragMarkers) { | ||
this.drawer.on('drag-mark', function (drag, mark) { | ||
if (mark.type !== 'selMark') { | ||
my.moveMarker(drag, mark); | ||
} | ||
}); | ||
} | ||
this.drawer.on('drag-mark', function (drag, mark) { | ||
mark.fireEvent('drag', drag); | ||
}); | ||
@@ -237,2 +227,7 @@ // Mouseup for plugins | ||
seekAndCenter: function (progress) { | ||
this.seekTo(progress); | ||
this.drawer.recenter(progress); | ||
}, | ||
seekTo: function (progress) { | ||
@@ -244,6 +239,10 @@ var paused = this.backend.isPaused(); | ||
this.params.scrollParent = false; | ||
// avoid noise while seeking | ||
this.savedVolume = this.backend.getVolume(); | ||
this.backend.setVolume(0); | ||
} | ||
this.play(progress * this.getDuration()); | ||
this.play((progress * this.drawer.width) / this.realPxPerSec); | ||
if (paused) { | ||
this.pause(); | ||
this.backend.setVolume(this.savedVolume); | ||
} | ||
@@ -291,2 +290,6 @@ this.params.scrollParent = oldScrollParent; | ||
toggleScroll: function () { | ||
this.params.scrollParent = !this.params.scrollParent; | ||
}, | ||
mark: function (options) { | ||
@@ -321,3 +324,9 @@ var my = this; | ||
if (this.dragging) { | ||
mark.type = 'selMark'; | ||
mark.on('drag', function(drag){ | ||
my.updateSelectionByMark(drag, mark); | ||
}); | ||
} else { | ||
mark.on('drag', function(drag){ | ||
my.moveMark(drag, mark); | ||
}); | ||
} | ||
@@ -327,2 +336,3 @@ | ||
my.drawer.updateMark(mark); | ||
my.fireEvent('mark-updated', mark); | ||
}); | ||
@@ -332,2 +342,3 @@ mark.on('remove', function () { | ||
delete my.markers[mark.id]; | ||
my.fireEvent('mark-removed', mark); | ||
}); | ||
@@ -355,8 +366,2 @@ | ||
redrawMarks: function () { | ||
Object.keys(this.markers).forEach(function (id) { | ||
this.mark(this.markers[id]); | ||
}, this); | ||
}, | ||
clearMarks: function () { | ||
@@ -369,2 +374,65 @@ Object.keys(this.markers).forEach(function (id) { | ||
redrawRegions: function () { | ||
Object.keys(this.regions).forEach(function (id) { | ||
this.region(this.regions[id]); | ||
}, this); | ||
}, | ||
clearRegions: function() { | ||
Object.keys(this.regions).forEach(function (id) { | ||
this.regions[id].remove(); | ||
}, this); | ||
this.regions = {}; | ||
}, | ||
region: function(options) { | ||
var my = this; | ||
var opts = WaveSurfer.util.extend({ | ||
id: WaveSurfer.util.getId() | ||
}, options); | ||
opts.startPercentage = opts.startPosition / this.getDuration(); | ||
opts.endPercentage = opts.endPosition / this.getDuration(); | ||
// If exists, just update and exit early | ||
if (opts.id in this.regions) { | ||
return this.regions[opts.id].update(opts); | ||
} | ||
var region = Object.create(WaveSurfer.Region); | ||
region.init(opts); | ||
region.on('update', function () { | ||
my.drawer.updateRegion(region); | ||
my.fireEvent('region-updated', region); | ||
}); | ||
region.on('remove', function () { | ||
my.drawer.removeRegion(region); | ||
my.fireEvent('region-removed', region); | ||
delete my.regions[region.id]; | ||
}); | ||
this.drawer.addRegion(region); | ||
this.drawer.on('region-over', function (region, e) { | ||
region.fireEvent('over', e); | ||
my.fireEvent('region-over', region, e); | ||
}); | ||
this.drawer.on('region-leave', function (region, e) { | ||
region.fireEvent('leave', e); | ||
my.fireEvent('region-leave', region, e); | ||
}); | ||
this.drawer.on('region-click', function (region, e) { | ||
region.fireEvent('click', e); | ||
my.fireEvent('region-click', region, e); | ||
}); | ||
this.regions[region.id] = region; | ||
this.fireEvent('region-created', region); | ||
return region; | ||
}, | ||
timings: function (offset) { | ||
@@ -381,9 +449,7 @@ var position = this.getCurrentTime() || 0; | ||
} else { | ||
length = Math.round( | ||
this.getDuration() * this.minPxPerSec * this.params.pixelRatio | ||
); | ||
length = Math.round(this.getDuration() * this.minPxPerSec * this.params.pixelRatio); | ||
} | ||
this.realPxPerSec = length / this.getDuration(); | ||
this.drawer.drawPeaks(this.backend.getPeaks(length), length); | ||
this.redrawMarks(); | ||
this.fireEvent('redraw'); | ||
@@ -394,14 +460,23 @@ }, | ||
var my = this; | ||
this.realPxPerSec = this.minPxPerSec * this.params.pixelRatio; | ||
var frameTime = 1 / this.realPxPerSec; | ||
var prevTime = 0; | ||
var peaks; | ||
this.drawFrame = function () { | ||
this.drawFrame = function (time) { | ||
if (time > prevTime && time - prevTime < frameTime) { | ||
return; | ||
} | ||
prevTime = time; | ||
var duration = my.getDuration(); | ||
var time = my.getCurrentTime(); | ||
var length = Math.round(duration * my.minPxPerSec * my.params.pixelRatio); | ||
var x = ~~((time / duration) * length); | ||
if (!peaks) { | ||
peaks = new Uint8Array(length); | ||
if (duration < Infinity) { | ||
var length = Math.round(duration * my.realPxPerSec); | ||
peaks = peaks || new Uint8Array(length); | ||
} else { | ||
peaks = peaks || []; | ||
length = peaks.length; | ||
} | ||
// 0 is the default value in Uint8Array | ||
if (0 == peaks[x]) { | ||
peaks[x] = WaveSurfer.util.max(my.backend.waveform(), 128); | ||
var index = ~~(my.backend.getPlayedPercents() * length); | ||
if (!peaks[index]) { | ||
peaks[index] = WaveSurfer.util.max(my.backend.waveform(), 128); | ||
my.drawer.setWidth(length); | ||
@@ -412,3 +487,4 @@ my.drawer.clearWave(); | ||
}; | ||
this.on('progress', this.drawFrame); | ||
this.backend.on('audioprocess', this.drawFrame); | ||
}, | ||
@@ -545,2 +621,27 @@ | ||
bindRegions: function() { | ||
var my = this; | ||
this.backend.on('play', function () { | ||
Object.keys(my.regions).forEach(function (id) { | ||
my.regions[id].fired_in = false; | ||
my.regions[id].fired_out = false; | ||
}); | ||
}); | ||
this.backend.on('audioprocess', function (time) { | ||
Object.keys(my.regions).forEach(function (id) { | ||
var region = my.regions[id]; | ||
if (!region.fired_in && region.startPosition <= time && region.endPosition >= time) { | ||
my.fireEvent('region-in', region); | ||
region.fireEvent('in'); | ||
region.fired_in = true; | ||
} | ||
if (!region.fired_out && region.endPosition < time) { | ||
my.fireEvent('region-out', region); | ||
region.fireEvent('out'); | ||
region.fired_out = true; | ||
} | ||
}); | ||
}); | ||
}, | ||
/** | ||
@@ -560,2 +661,3 @@ * Display empty waveform. | ||
this.clearMarks(); | ||
this.clearRegions(); | ||
this.drawer.setWidth(0); | ||
@@ -571,2 +673,3 @@ this.drawer.drawPeaks({ length: this.drawer.getWidth() }, 0); | ||
this.clearMarks(); | ||
this.clearRegions(); | ||
this.unAll(); | ||
@@ -623,3 +726,4 @@ this.backend.destroy(); | ||
position: percent0 * this.getDuration(), | ||
color: color | ||
color: color, | ||
draggable: my.params.selectionBorder | ||
}); | ||
@@ -638,3 +742,4 @@ } | ||
position: percent1 * this.getDuration(), | ||
color: color | ||
color: color, | ||
draggable: my.params.selectionBorder | ||
}); | ||
@@ -651,3 +756,3 @@ } | ||
moveMarker: function (drag, mark) { | ||
moveMark: function (drag, mark) { | ||
mark.update({ | ||
@@ -674,2 +779,3 @@ percentage: drag.endPercentage, | ||
} | ||
this.fireEvent('selection-update', this.getSelection()); | ||
}, | ||
@@ -720,3 +826,4 @@ | ||
width: 1, | ||
color: '#333' | ||
color: '#333', | ||
draggable: false | ||
}, | ||
@@ -757,2 +864,40 @@ | ||
/* Region */ | ||
WaveSurfer.Region = { | ||
defaultParams: { | ||
id: null, | ||
startPosition: 0, | ||
endPosition: 0, | ||
startPercentage: 0, | ||
endPercentage: 0, | ||
color: 'rgba(0, 0, 255, 0.2)' | ||
}, | ||
init: function (options) { | ||
this.apply( | ||
WaveSurfer.util.extend({}, this.defaultParams, options) | ||
); | ||
return this; | ||
}, | ||
apply: function (options) { | ||
Object.keys(options).forEach(function (key) { | ||
if (key in this.defaultParams) { | ||
this[key] = options[key]; | ||
} | ||
}, this); | ||
}, | ||
update: function (options) { | ||
this.apply(options); | ||
this.fireEvent('update'); | ||
}, | ||
remove: function () { | ||
this.fireEvent('remove'); | ||
this.unAll(); | ||
} | ||
}; | ||
/* Observer */ | ||
@@ -901,1 +1046,2 @@ WaveSurfer.Observer = { | ||
WaveSurfer.util.extend(WaveSurfer.Mark, WaveSurfer.Observer); | ||
WaveSurfer.util.extend(WaveSurfer.Region, WaveSurfer.Observer); |
@@ -96,6 +96,2 @@ 'use strict'; | ||
if (time > this.getDuration()) { | ||
this.fireEvent('finish', time); | ||
} | ||
if (this.loop) { | ||
@@ -179,14 +175,13 @@ if ( | ||
var max = 0; | ||
var min = 0; | ||
for (var j = start; j < end; j += sampleStep) { | ||
var value = Math.abs(chan[j]); | ||
var value = chan[j]; | ||
if (value > max) { | ||
max = value; | ||
} else if (value < min) { | ||
min = value; | ||
// faster than Math.abs | ||
} else if (-value > max) { | ||
max = -value; | ||
} | ||
} | ||
var median = (max + min) / 2; | ||
if (c == 0 || median > peaks[i]) { | ||
peaks[i] = median; | ||
if (c == 0 || max > peaks[i]) { | ||
peaks[i] = max; | ||
} | ||
@@ -193,0 +188,0 @@ } |
@@ -5,2 +5,4 @@ 'use strict'; | ||
postInit: function () { | ||
var my = this; | ||
// Dummy media to catch errors | ||
@@ -15,2 +17,9 @@ this.media = { | ||
}; | ||
this.maxCurrentTime = 0; | ||
this.on('audioprocess', function (time) { | ||
if (time > my.maxCurrentTime) { | ||
my.maxCurrentTime = time; | ||
} | ||
}); | ||
}, | ||
@@ -21,2 +30,3 @@ | ||
this.media = media; | ||
this.maxCurrentTime = 0; | ||
this.source = this.ac.createMediaElementSource(this.media); | ||
@@ -39,2 +49,11 @@ this.media.playbackRate = this.playbackRate; | ||
getPlayedPercents: function () { | ||
var duration = this.getDuration(); | ||
var time = this.getCurrentTime(); | ||
if (duration >= Infinity) { // streaming audio | ||
duration = this.maxCurrentTime; | ||
} | ||
return (time / duration) || 0; | ||
}, | ||
/** | ||
@@ -41,0 +60,0 @@ * Set the audio source playback rate. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
3542565
45
12355
186