UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

702 lines (578 loc) 19.1 kB
/** * @file * * Defines the {@link line} class. * * @module line */ define([ 'konva', './utils' ], function(Konva, Utils) { 'use strict'; function Line(peaks, view, y, id, position) { this._peaks = peaks; this._view = view; this._id = id; this._position = position; this._firstSourceId = null; this._sources = {}; this._sourcesGroup = {}; this._wrapped = false; this._y = y; this._group = new Konva.Group({ x: 0, y: y - this._view.getFrameOffsetY(), draggable: true, dragBoundFunc: function() { return { x: this.absolutePosition().x, y: this.absolutePosition().y }; } }); this._sourceHeights = {}; this._height = this._peaks.options.emptyLineHeight; this._unwrappedCount = 0; } Line.prototype.getPosition = function() { return this._position; }; Line.prototype.getId = function() { return this._id; }; Line.prototype.isEmpty = function() { return this.isSegmentsLine() ? this._segmentsGroup.isEmpty() : Object.keys(this._sources).length === 0; }; Line.prototype.isSegmentsLine = function() { return Boolean(this._segmentsGroup); }; Line.prototype.updateSegments = function(frameStartTime, frameEndTime) { if (this.isSegmentsLine()) { this._segmentsGroup.updateSegments(frameStartTime, frameEndTime); } }; Line.prototype.lineLength = function() { var length = 0; if (this.isSegmentsLine()) { return this._segmentsGroup.getSegmentsGroupLength(); } for (var sourceId in this._sources) { if (Utils.objectHasProperty(this._sources, sourceId)) { var sourceGroupLength = this._view.timeToPixels( this._sources[sourceId].source.endTime ); if (sourceGroupLength > length) { length = sourceGroupLength; } } } return length; }; Line.prototype.lineHeight = function() { return this._height; }; Line.prototype.changeHeight = function(from, to) { if (this._sourceHeights[from]) { var oldHeight = this._height; if (this._sourceHeights[to]) { this._sourceHeights[to] += this._sourceHeights[from]; } else { this._sourceHeights[to] = this._sourceHeights[from]; } if (to > this._height) { this._height = to; } else if (from === this._height) { this._height = 0; for (var height in this._sourceHeights) { if (Utils.objectHasProperty(this._sourceHeights, height)) { var parsedHeight = parseInt(height, 10); if (parsedHeight !== from) { if (parsedHeight > this._height) { this._height = parsedHeight; } } } } } if (this._height !== oldHeight) { this._peaks.emit('line.heightChanged', this._position); } delete this._sourceHeights[from]; } }; Line.prototype.updateLineHeight = function(sourceGroup, action) { var oldHeight = this._height; var sourceGroupHeight; switch (action) { case 'add': sourceGroupHeight = sourceGroup.getCurrentHeight(); if (this._sourceHeights[sourceGroupHeight]) { this._sourceHeights[sourceGroupHeight]++; } else { this._sourceHeights[sourceGroupHeight] = 1; if (sourceGroupHeight > this._height) { this._height = sourceGroupHeight; } } break; case 'remove': if (Object.keys(this._sources).length === 0) { this._height = this._peaks.options.emptyLineHeight; this._sourceHeights = {}; } else { sourceGroupHeight = sourceGroup.getCurrentHeight(); this._sourceHeights[sourceGroupHeight]--; if (this._sourceHeights[sourceGroupHeight] === 0 && sourceGroupHeight === this._height) { delete this._sourceHeights[sourceGroupHeight]; this._height = 0; for (var height in this._sourceHeights) { if (Utils.objectHasProperty(this._sourceHeights, height)) { var parsedHeight = parseInt(height, 10); if (parsedHeight > this._height) { this._height = parsedHeight; } } } } } break; } if (this._height !== oldHeight) { this._peaks.emit('line.heightChanged', this._position); } }; Line.prototype.isVisible = function() { return this._y < this._view.getFrameOffsetY() + this._view.getHeight() && this._view.getFrameOffsetY() < this._y + this._height; }; Line.prototype.addToLayer = function(layer) { layer.add(this._group); }; Line.prototype.addSourceGroup = function(sourceGroup) { var source = sourceGroup.getSource(); this._sourcesGroup[source.id] = sourceGroup; if (!this._sources[source.id]) { var newSource = { source: source, prevSourceId: null, nextSourceId: null }; if (this._firstSourceId) { var currentSource = null; do { if (!currentSource) { currentSource = this._sources[this._firstSourceId]; } else { currentSource = this._sources[currentSource.nextSourceId]; } if (source.endTime <= currentSource.source.startTime) { var startLimit = currentSource.prevSourceId ? this._sources[currentSource.prevSourceId].source.endTime : 0; var newTimes = this._canBePlacedBetween( source.startTime, source.endTime, startLimit, currentSource.source.startTime ); if (newTimes) { source.updateTimes(newTimes.startTime, newTimes.endTime); if (currentSource.prevSourceId) { this._sources[currentSource.prevSourceId].nextSourceId = source.id; newSource.prevSourceId = currentSource.prevSourceId; } else { this._firstSourceId = source.id; } currentSource.prevSourceId = source.id; newSource.nextSourceId = currentSource.source.id; this._sources[source.id] = newSource; break; } } } while (currentSource.nextSourceId); if (!newSource.prevSourceId && !newSource.nextSourceId) { if (source.startTime < currentSource.source.endTime) { // Overlapping with last source var timeWidth = source.endTime - source.startTime; source.updateTimes( currentSource.source.endTime, currentSource.source.endTime + timeWidth ); } currentSource.nextSourceId = source.id; newSource.prevSourceId = currentSource.source.id; this._sources[source.id] = newSource; } } else { this._firstSourceId = source.id; this._sources[source.id] = newSource; } this.updateLineHeight(sourceGroup, 'add'); } if (!sourceGroup.getParent() || !sourceGroup.isDescendantOf(this._group)) { sourceGroup.addToGroup(this._group); } }; Line.prototype.addSegments = function(segmentsGroup) { this._segmentsGroup = segmentsGroup; this._height = this._segmentsGroup.getCurrentHeight(); segmentsGroup.addToGroup(this._group); }; Line.prototype.refreshSegmentsHeight = function() { if (this.isSegmentsLine) { var oldHeight = this._height; this._height = this._segmentsGroup.getCurrentHeight(); if (this._height !== oldHeight) { this._peaks.emit('line.heightChanged', this._position); } } }; Line.prototype._canBePlacedBetween = function(startTime, endTime, startLimit, endLimit) { var timeWidth = endTime - startTime; var newTimes = null; if ((!endLimit && startTime > startLimit) || (startTime > startLimit && endTime < endLimit)) { // Can be placed at its wanted position with wanted start/end time newTimes = { startTime: startTime, endTime: endTime }; } else if (endLimit - startLimit >= timeWidth) { // Can be placed at its wanted position but not with its wanted start/end time if (startTime > startLimit) { newTimes = { startTime: endLimit - timeWidth, endTime: endLimit }; } else { newTimes = { startTime: startLimit, endTime: startLimit + timeWidth }; } } return newTimes; }; Line.prototype.removeSourceGroup = function(source, isPermanent) { var sourceGroup = this._sourcesGroup[source.id]; delete this._sourcesGroup[source.id]; if (isPermanent) { if (this._sources[source.id].prevSourceId) { this._sources[this._sources[source.id].prevSourceId].nextSourceId = this._sources[source.id].nextSourceId; } if (this._sources[source.id].nextSourceId) { this._sources[this._sources[source.id].nextSourceId].prevSourceId = this._sources[source.id].prevSourceId; } if (this._firstSourceId === source.id) { this._firstSourceId = this._sources[source.id].nextSourceId; } delete this._sources[source.id]; this.updateLineHeight(sourceGroup, 'remove'); } return sourceGroup; }; Line.prototype.getKonvaGroup = function() { return this._group; }; Line.prototype.getY = function() { return this._group.y(); }; Line.prototype.getInitialY = function() { return this._y; }; Line.prototype.y = function(value, changeInitialY) { this._group.y(value); if (changeInitialY) { this._y = value; } }; Line.prototype.moveOnY = function(dy) { this._y += dy; this._group.y(this._group.y() + dy); }; Line.prototype.manageSourceOrder = function(source, newStartX, newEndX) { var cursorX = this._view.getPointerPosition().x; var tmpXs; var newXs = { startX: newStartX, endX: newEndX }; var sourceWidth = this._view.timeToPixels(source.endTime - source.startTime); if (newStartX !== null && newEndX !== null) { if (this._sources[source.id].prevSourceId) { // there is another source to the left var previousStartX = this._view.timeToPixels( this._sources[this._sources[source.id].prevSourceId].source.startTime ); if (cursorX + this._view.getFrameOffset() < previousStartX) { // we want to change order tmpXs = this._changeSourcePosition( source, sourceWidth, cursorX + this._view.getFrameOffset() ); if (tmpXs) { newXs.startX = tmpXs.startTime; newXs.endX = tmpXs.endTime; } } } if (this._sources[source.id].nextSourceId) { // there is another source to the right var nextEndX = this._view.timeToPixels( this._sources[this._sources[source.id].nextSourceId].source.endTime ); if (cursorX + this._view.getFrameOffset() > nextEndX) { // we want to change order tmpXs = this._changeSourcePosition( source, sourceWidth, cursorX + this._view.getFrameOffset() ); if (tmpXs) { newXs.startX = tmpXs.startTime; newXs.endX = tmpXs.endTime; } } } } return newXs; }; Line.prototype._changeSourcePosition = function(source, sourceWidth, x) { if (source.orderable && this._firstSourceId) { var currentRange = { start: null, end: null }; var startLimit = null; var endLimit = null; do { if (!currentRange.end) { currentRange.end = this._sources[this._firstSourceId]; } else { currentRange.start = currentRange.end; currentRange.end = this._sources[currentRange.start.nextSourceId]; } if (currentRange.start) { startLimit = this._view.timeToPixels( currentRange.start.source.endTime ); } else { startLimit = 0; } if (currentRange.end) { endLimit = this._view.timeToPixels( currentRange.end.source.startTime ); } else { endLimit = null; } if (x > startLimit && (endLimit === null || x < endLimit)) { var newTimes = this._canBePlacedBetween( x, x + sourceWidth, startLimit, endLimit ); if (newTimes) { source.updateTimes( this._view.pixelsToTime(newTimes.startTime), this._view.pixelsToTime(newTimes.endTime) ); var prevSourceId = currentRange.start ? currentRange.start.source.id : null; this._moveSource(this._sources[source.id].source, prevSourceId); return newTimes; } } } while (currentRange.end); } return null; }; Line.prototype._moveSource = function(source, prevSourceId) { // Remove source from the list var sourceObj = this._sources[source.id]; var prevSource = this._sources[sourceObj.prevSourceId]; var nextSource = this._sources[sourceObj.nextSourceId]; if (prevSource) { this._sources[sourceObj.prevSourceId].nextSourceId = sourceObj.nextSourceId; } else { this._firstSourceId = sourceObj.nextSourceId; } if (nextSource) { this._sources[sourceObj.nextSourceId].prevSourceId = sourceObj.prevSourceId; } delete this._sources[source.id]; // Add source back to the list sourceObj.prevSourceId = prevSourceId; if (prevSourceId) { sourceObj.nextSourceId = this._sources[prevSourceId].nextSourceId; this._sources[prevSourceId].nextSourceId = source.id; } else { sourceObj.nextSourceId = this._firstSourceId; this._firstSourceId = source.id; } if (sourceObj.nextSourceId) { this._sources[sourceObj.nextSourceId].prevSourceId = source.id; } this._sources[source.id] = sourceObj; }; Line.prototype.manageCollision = function(source, newStartX, newEndX) { var originalStartTime = null; var originalEndTime = null; var newStartTime = null; var newEndTime = null; var startLimited = false; var endLimited = false; var newXs = { startX: newStartX, endX: newEndX, updateTimelineLength: false }; if (newStartX !== null) { // startMarker changed originalStartTime = this._view.pixelsToTime(newStartX); newStartTime = originalStartTime; if (source.startTime > newStartTime) { // startMarker moved to the left if (this._sources[source.id].prevSourceId) { // there is another source to the left var previousSource = this._sources[this._sources[source.id].prevSourceId] .source; if (newStartTime < previousSource.endTime) { // there is collision newStartTime = previousSource.endTime; startLimited = true; } } else { if (newStartTime < 0) { newStartTime = 0; startLimited = true; } } } } if (newEndX !== null) { // endMarker changed originalEndTime = this._view.pixelsToTime(newEndX); newEndTime = originalEndTime; if (source.endTime < newEndTime) { // endMarker moved to the right if (this._sources[source.id].nextSourceId) { // there is another source to the right var nextSource = this._sources[this._sources[source.id].nextSourceId] .source; if (newEndTime > nextSource.startTime) { // there is collision newEndTime = nextSource.startTime; endLimited = true; } } } } // Update the other edge if dragging and collision if (newStartTime !== null && newEndTime !== null) { var timeWidth = Utils.roundTime(originalEndTime - originalStartTime); if (startLimited) { newEndTime = newStartTime + timeWidth; } if (endLimited) { newStartTime = newEndTime - timeWidth; } } // Check for minimal size of source if (newStartTime !== null && newEndTime === null) { if (source.endTime - newStartTime < source.minSize) { newStartTime = source.endTime - source.minSize; } } else if (newEndTime !== null && newStartTime === null) { if (newEndTime - source.startTime < source.minSize) { newEndTime = source.startTime + source.minSize; } } else { if (newEndTime - newStartTime < source.minSize) { if (source.endTime !== newEndTime) { newEndTime = newStartTime + source.minSize; } if (source.startTime !== newStartTime) { newStartTime = newEndTime - source.minSize; } } } if (newStartTime !== null && newStartTime !== originalStartTime) { newXs.startX = this._view.timeToPixels(newStartTime); } if (newEndTime !== null && newEndTime !== originalEndTime) { newXs.endX = this._view.timeToPixels(newEndTime); if (this._sources[source.id].nextSourceId === null) { newXs.updateTimelineLength = true; } } return newXs; }; // Line.prototype.rescale = function() { // for (var sourceId in this._sourcesGroup) { // if (Utils.objectHasProperty(this._sourcesGroup, sourceId)) { // this._sourcesGroup[sourceId].rescale(); // } // } // }; Line.prototype.getSourcesAfter = function(time) { const sources = []; var currentId = this._firstSourceId; while (currentId) { var lineSource = this._sources[currentId]; if (lineSource.source.startTime >= time) { while (currentId) { lineSource = this._sources[currentId]; sources.push(lineSource.source); currentId = lineSource.nextSourceId; } break; } currentId = lineSource.nextSourceId; } return sources; }; Line.prototype.updatePosition = function(pos) { for (var sourceId in this._sources) { if (Utils.objectHasProperty(this._sources, sourceId)) { this._sources[sourceId].source.position = pos; } } this._position = pos; }; Line.prototype.destroy = function() { this._firstSourceId = null; this._sources = {}; this._sourcesGroup = {}; this._group.destroy(); }; Line.prototype.allowInteractions = function(bool) { this._group.listening(bool); }; return Line; });