515 lines
16 KiB
JavaScript
515 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
/* Regions manager */
|
|
WaveSurfer.Regions = {
|
|
init: function (wavesurfer) {
|
|
this.wavesurfer = wavesurfer;
|
|
this.wrapper = this.wavesurfer.drawer.wrapper;
|
|
|
|
/* Id-based hash of regions. */
|
|
this.list = {};
|
|
},
|
|
|
|
/* Add a region. */
|
|
add: function (params) {
|
|
var region = Object.create(WaveSurfer.Region);
|
|
region.init(params, this.wavesurfer);
|
|
|
|
this.list[region.id] = region;
|
|
|
|
region.on('remove', (function () {
|
|
delete this.list[region.id];
|
|
}).bind(this));
|
|
|
|
return region;
|
|
},
|
|
|
|
/* Remove all regions. */
|
|
clear: function () {
|
|
Object.keys(this.list).forEach(function (id) {
|
|
this.list[id].remove();
|
|
}, this);
|
|
},
|
|
|
|
enableDragSelection: function (params) {
|
|
var my = this;
|
|
var drag;
|
|
var start;
|
|
var region;
|
|
var touchId;
|
|
var slop = params.slop || 2;
|
|
var pxMove = 0;
|
|
|
|
var eventDown = function (e) {
|
|
if (e.touches && e.touches.length > 1) { return; }
|
|
|
|
// Check whether the click/tap is on the bottom-most DOM element
|
|
// Effectively prevent clicks on the scrollbar from registering as
|
|
// region creation.
|
|
if (e.target.childElementCount > 0) { return; }
|
|
|
|
touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
|
|
|
|
drag = true;
|
|
start = my.wavesurfer.drawer.handleEvent(e, true);
|
|
region = null;
|
|
};
|
|
this.wrapper.addEventListener('mousedown', eventDown);
|
|
this.wrapper.addEventListener('touchstart', eventDown);
|
|
this.on('disable-drag-selection', function() {
|
|
my.wrapper.removeEventListener('touchstart', eventDown);
|
|
my.wrapper.removeEventListener('mousedown', eventDown);
|
|
});
|
|
|
|
var eventUp = function (e) {
|
|
if (e.touches && e.touches.length > 1) { return; }
|
|
|
|
drag = false;
|
|
pxMove = 0;
|
|
|
|
if (region) {
|
|
region.fireEvent('update-end', e);
|
|
my.wavesurfer.fireEvent('region-update-end', region, e);
|
|
}
|
|
|
|
region = null;
|
|
};
|
|
this.wrapper.addEventListener('mouseup', eventUp);
|
|
this.wrapper.addEventListener('touchend', eventUp);
|
|
this.on('disable-drag-selection', function() {
|
|
my.wrapper.removeEventListener('touchend', eventUp);
|
|
my.wrapper.removeEventListener('mouseup', eventUp);
|
|
});
|
|
|
|
var eventMove = function (e) {
|
|
if (!drag) { return; }
|
|
if (++pxMove <= slop) { return; }
|
|
|
|
if (e.touches && e.touches.length > 1) { return; }
|
|
if (e.targetTouches && e.targetTouches[0].identifier != touchId) { return; }
|
|
|
|
if (!region) {
|
|
region = my.add(params || {});
|
|
}
|
|
|
|
var duration = my.wavesurfer.getDuration();
|
|
var end = my.wavesurfer.drawer.handleEvent(e);
|
|
region.update({
|
|
start: Math.min(end * duration, start * duration),
|
|
end: Math.max(end * duration, start * duration)
|
|
});
|
|
};
|
|
this.wrapper.addEventListener('mousemove', eventMove);
|
|
this.wrapper.addEventListener('touchmove', eventMove);
|
|
this.on('disable-drag-selection', function() {
|
|
my.wrapper.removeEventListener('touchmove', eventMove);
|
|
my.wrapper.removeEventListener('mousemove', eventMove);
|
|
});
|
|
},
|
|
|
|
disableDragSelection: function () {
|
|
this.fireEvent('disable-drag-selection');
|
|
}
|
|
};
|
|
|
|
WaveSurfer.util.extend(WaveSurfer.Regions, WaveSurfer.Observer);
|
|
|
|
WaveSurfer.Region = {
|
|
/* Helper function to assign CSS styles. */
|
|
style: WaveSurfer.Drawer.style,
|
|
|
|
init: function (params, wavesurfer) {
|
|
this.wavesurfer = wavesurfer;
|
|
this.wrapper = wavesurfer.drawer.wrapper;
|
|
|
|
this.id = params.id == null ? WaveSurfer.util.getId() : params.id;
|
|
this.start = Number(params.start) || 0;
|
|
this.end = params.end == null ?
|
|
// small marker-like region
|
|
this.start + (4 / this.wrapper.scrollWidth) * this.wavesurfer.getDuration() :
|
|
Number(params.end);
|
|
this.resize = params.resize === undefined ? true : Boolean(params.resize);
|
|
this.drag = params.drag === undefined ? true : Boolean(params.drag);
|
|
this.loop = Boolean(params.loop);
|
|
this.color = params.color || 'rgba(0, 0, 0, 0.1)';
|
|
this.data = params.data || {};
|
|
this.attributes = params.attributes || {};
|
|
|
|
this.maxLength = params.maxLength;
|
|
this.minLength = params.minLength;
|
|
|
|
this.bindInOut();
|
|
this.render();
|
|
|
|
this.onZoom = this.updateRender.bind(this);
|
|
this.wavesurfer.on('zoom', this.onZoom);
|
|
|
|
this.wavesurfer.fireEvent('region-created', this);
|
|
|
|
},
|
|
|
|
/* Update region params. */
|
|
update: function (params) {
|
|
if (null != params.start) {
|
|
this.start = Number(params.start);
|
|
}
|
|
if (null != params.end) {
|
|
this.end = Number(params.end);
|
|
}
|
|
if (null != params.loop) {
|
|
this.loop = Boolean(params.loop);
|
|
}
|
|
if (null != params.color) {
|
|
this.color = params.color;
|
|
}
|
|
if (null != params.data) {
|
|
this.data = params.data;
|
|
}
|
|
if (null != params.resize) {
|
|
this.resize = Boolean(params.resize);
|
|
}
|
|
if (null != params.drag) {
|
|
this.drag = Boolean(params.drag);
|
|
}
|
|
if (null != params.maxLength) {
|
|
this.maxLength = Number(params.maxLength);
|
|
}
|
|
if (null != params.minLength) {
|
|
this.minLength = Number(params.minLength);
|
|
}
|
|
if (null != params.attributes) {
|
|
this.attributes = params.attributes;
|
|
}
|
|
|
|
this.updateRender();
|
|
this.fireEvent('update');
|
|
this.wavesurfer.fireEvent('region-updated', this);
|
|
},
|
|
|
|
/* Remove a single region. */
|
|
remove: function () {
|
|
if (this.element) {
|
|
this.wrapper.removeChild(this.element);
|
|
this.element = null;
|
|
this.wavesurfer.un('zoom', this.onZoom);
|
|
this.fireEvent('remove');
|
|
this.wavesurfer.fireEvent('region-removed', this);
|
|
}
|
|
},
|
|
|
|
/* Play the audio region. */
|
|
play: function () {
|
|
this.wavesurfer.play(this.start, this.end);
|
|
this.fireEvent('play');
|
|
this.wavesurfer.fireEvent('region-play', this);
|
|
},
|
|
|
|
/* Play the region in loop. */
|
|
playLoop: function () {
|
|
this.play();
|
|
this.once('out', this.playLoop.bind(this));
|
|
},
|
|
|
|
/* Render a region as a DOM element. */
|
|
render: function () {
|
|
var regionEl = document.createElement('region');
|
|
regionEl.className = 'wavesurfer-region';
|
|
regionEl.title = this.formatTime(this.start, this.end);
|
|
regionEl.setAttribute('data-id', this.id);
|
|
|
|
for (var attrname in this.attributes) {
|
|
regionEl.setAttribute('data-region-' + attrname, this.attributes[attrname]);
|
|
}
|
|
|
|
var width = this.wrapper.scrollWidth;
|
|
this.style(regionEl, {
|
|
position: 'absolute',
|
|
zIndex: 2,
|
|
height: '100%',
|
|
top: '0px'
|
|
});
|
|
|
|
/* Resize handles */
|
|
if (this.resize) {
|
|
var handleLeft = regionEl.appendChild(document.createElement('handle'));
|
|
var handleRight = regionEl.appendChild(document.createElement('handle'));
|
|
handleLeft.className = 'wavesurfer-handle wavesurfer-handle-start';
|
|
handleRight.className = 'wavesurfer-handle wavesurfer-handle-end';
|
|
var css = {
|
|
cursor: 'col-resize',
|
|
position: 'absolute',
|
|
left: '0px',
|
|
top: '0px',
|
|
width: '1%',
|
|
maxWidth: '4px',
|
|
height: '100%'
|
|
};
|
|
this.style(handleLeft, css);
|
|
this.style(handleRight, css);
|
|
this.style(handleRight, {
|
|
left: '100%'
|
|
});
|
|
}
|
|
|
|
this.element = this.wrapper.appendChild(regionEl);
|
|
this.updateRender();
|
|
this.bindEvents(regionEl);
|
|
},
|
|
|
|
formatTime: function (start, end) {
|
|
return (start == end ? [ start ] : [ start, end ]).map(function (time) {
|
|
return [
|
|
Math.floor((time % 3600) / 60), // minutes
|
|
('00' + Math.floor(time % 60)).slice(-2) // seconds
|
|
].join(':');
|
|
}).join('-');
|
|
},
|
|
|
|
getWidth: function () {
|
|
return this.wavesurfer.drawer.width / this.wavesurfer.params.pixelRatio;
|
|
},
|
|
|
|
/* Update element's position, width, color. */
|
|
updateRender: function () {
|
|
var dur = this.wavesurfer.getDuration();
|
|
var width = this.getWidth();
|
|
|
|
if (this.start < 0) {
|
|
this.start = 0;
|
|
this.end = this.end - this.start;
|
|
}
|
|
if (this.end > dur) {
|
|
this.end = dur;
|
|
this.start = dur - (this.end - this.start);
|
|
}
|
|
|
|
if (this.minLength != null) {
|
|
this.end = Math.max(this.start + this.minLength, this.end);
|
|
}
|
|
|
|
if (this.maxLength != null) {
|
|
this.end = Math.min(this.start + this.maxLength, this.end);
|
|
}
|
|
|
|
if (this.element != null) {
|
|
// Calculate the left and width values of the region such that
|
|
// no gaps appear between regions.
|
|
var left = Math.round(this.start / dur * width);
|
|
var regionWidth =
|
|
Math.round(this.end / dur * width) - left;
|
|
|
|
this.style(this.element, {
|
|
left: left + 'px',
|
|
width: regionWidth + 'px',
|
|
backgroundColor: this.color,
|
|
cursor: this.drag ? 'move' : 'default'
|
|
});
|
|
|
|
for (var attrname in this.attributes) {
|
|
this.element.setAttribute('data-region-' + attrname, this.attributes[attrname]);
|
|
}
|
|
|
|
this.element.title = this.formatTime(this.start, this.end);
|
|
}
|
|
},
|
|
|
|
/* Bind audio events. */
|
|
bindInOut: function () {
|
|
var my = this;
|
|
|
|
my.firedIn = false;
|
|
my.firedOut = false;
|
|
|
|
var onProcess = function (time) {
|
|
if (!my.firedOut && my.firedIn && (my.start >= Math.round(time * 100) / 100 || my.end <= Math.round(time * 100) / 100)) {
|
|
my.firedOut = true;
|
|
my.firedIn = false;
|
|
my.fireEvent('out');
|
|
my.wavesurfer.fireEvent('region-out', my);
|
|
}
|
|
if (!my.firedIn && my.start <= time && my.end > time) {
|
|
my.firedIn = true;
|
|
my.firedOut = false;
|
|
my.fireEvent('in');
|
|
my.wavesurfer.fireEvent('region-in', my);
|
|
}
|
|
};
|
|
|
|
this.wavesurfer.backend.on('audioprocess', onProcess);
|
|
|
|
this.on('remove', function () {
|
|
my.wavesurfer.backend.un('audioprocess', onProcess);
|
|
});
|
|
|
|
/* Loop playback. */
|
|
this.on('out', function () {
|
|
if (my.loop) {
|
|
my.wavesurfer.play(my.start);
|
|
}
|
|
});
|
|
},
|
|
|
|
/* Bind DOM events. */
|
|
bindEvents: function () {
|
|
var my = this;
|
|
|
|
this.element.addEventListener('mouseenter', function (e) {
|
|
my.fireEvent('mouseenter', e);
|
|
my.wavesurfer.fireEvent('region-mouseenter', my, e);
|
|
});
|
|
|
|
this.element.addEventListener('mouseleave', function (e) {
|
|
my.fireEvent('mouseleave', e);
|
|
my.wavesurfer.fireEvent('region-mouseleave', my, e);
|
|
});
|
|
|
|
this.element.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
my.fireEvent('click', e);
|
|
my.wavesurfer.fireEvent('region-click', my, e);
|
|
});
|
|
|
|
this.element.addEventListener('dblclick', function (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
my.fireEvent('dblclick', e);
|
|
my.wavesurfer.fireEvent('region-dblclick', my, e);
|
|
});
|
|
|
|
/* Drag or resize on mousemove. */
|
|
(this.drag || this.resize) && (function () {
|
|
var duration = my.wavesurfer.getDuration();
|
|
var drag;
|
|
var resize;
|
|
var startTime;
|
|
var touchId;
|
|
|
|
var onDown = function (e) {
|
|
if (e.touches && e.touches.length > 1) { return; }
|
|
touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
|
|
|
|
e.stopPropagation();
|
|
startTime = my.wavesurfer.drawer.handleEvent(e, true) * duration;
|
|
|
|
if (e.target.tagName.toLowerCase() == 'handle') {
|
|
if (e.target.classList.contains('wavesurfer-handle-start')) {
|
|
resize = 'start';
|
|
} else {
|
|
resize = 'end';
|
|
}
|
|
} else {
|
|
drag = true;
|
|
resize = false;
|
|
}
|
|
};
|
|
var onUp = function (e) {
|
|
if (e.touches && e.touches.length > 1) { return; }
|
|
|
|
if (drag || resize) {
|
|
drag = false;
|
|
resize = false;
|
|
|
|
my.fireEvent('update-end', e);
|
|
my.wavesurfer.fireEvent('region-update-end', my, e);
|
|
}
|
|
};
|
|
var onMove = function (e) {
|
|
if (e.touches && e.touches.length > 1) { return; }
|
|
if (e.targetTouches && e.targetTouches[0].identifier != touchId) { return; }
|
|
|
|
if (drag || resize) {
|
|
var time = my.wavesurfer.drawer.handleEvent(e) * duration;
|
|
var delta = time - startTime;
|
|
startTime = time;
|
|
|
|
// Drag
|
|
if (my.drag && drag) {
|
|
my.onDrag(delta);
|
|
}
|
|
|
|
// Resize
|
|
if (my.resize && resize) {
|
|
my.onResize(delta, resize);
|
|
}
|
|
}
|
|
};
|
|
|
|
my.element.addEventListener('mousedown', onDown);
|
|
my.element.addEventListener('touchstart', onDown);
|
|
|
|
my.wrapper.addEventListener('mousemove', onMove);
|
|
my.wrapper.addEventListener('touchmove', onMove);
|
|
|
|
document.body.addEventListener('mouseup', onUp);
|
|
document.body.addEventListener('touchend', onUp);
|
|
|
|
my.on('remove', function () {
|
|
document.body.removeEventListener('mouseup', onUp);
|
|
document.body.removeEventListener('touchend', onUp);
|
|
my.wrapper.removeEventListener('mousemove', onMove);
|
|
my.wrapper.removeEventListener('touchmove', onMove);
|
|
});
|
|
|
|
my.wavesurfer.on('destroy', function () {
|
|
document.body.removeEventListener('mouseup', onUp);
|
|
document.body.removeEventListener('touchend', onUp);
|
|
});
|
|
}());
|
|
},
|
|
|
|
onDrag: function (delta) {
|
|
var maxEnd = this.wavesurfer.getDuration();
|
|
if ((this.end + delta) > maxEnd || (this.start + delta) < 0) {
|
|
return;
|
|
}
|
|
|
|
this.update({
|
|
start: this.start + delta,
|
|
end: this.end + delta
|
|
});
|
|
},
|
|
|
|
onResize: function (delta, direction) {
|
|
if (direction == 'start') {
|
|
this.update({
|
|
start: Math.min(this.start + delta, this.end),
|
|
end: Math.max(this.start + delta, this.end)
|
|
});
|
|
} else {
|
|
this.update({
|
|
start: Math.min(this.end + delta, this.start),
|
|
end: Math.max(this.end + delta, this.start)
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
WaveSurfer.util.extend(WaveSurfer.Region, WaveSurfer.Observer);
|
|
|
|
|
|
/* Augment WaveSurfer with region methods. */
|
|
WaveSurfer.initRegions = function () {
|
|
if (!this.regions) {
|
|
this.regions = Object.create(WaveSurfer.Regions);
|
|
this.regions.init(this);
|
|
}
|
|
};
|
|
|
|
WaveSurfer.addRegion = function (options) {
|
|
this.initRegions();
|
|
return this.regions.add(options);
|
|
};
|
|
|
|
WaveSurfer.clearRegions = function () {
|
|
this.regions && this.regions.clear();
|
|
};
|
|
|
|
WaveSurfer.enableDragSelection = function (options) {
|
|
this.initRegions();
|
|
this.regions.enableDragSelection(options);
|
|
};
|
|
|
|
WaveSurfer.disableDragSelection = function () {
|
|
this.regions.disableDragSelection();
|
|
};
|