@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
499 lines (423 loc) • 14.2 kB
JavaScript
/**
* @file
*
* Defines the {@link TimelineSources} class.
*
* @module timeline-sources
*/
define([
'./source',
'./utils'
], function(Source, Utils) {
'use strict';
/**
* Source parameters.
*
* @typedef {Object} SourceOptions
* @global
* @param {String} id A unique identifier for the source.
* @param {String} title Name of the source.
* @param {String} url Reference to the media element this source is representing.
* @param {Number} startTime Source start time, in seconds.
* @param {Number} endTime Source end time, in seconds.
* @param {String} color Color of the background.
* @param {Boolean} wrapped If <code>true</code> the source representation
* will be reduced to a line, taking less space.
* @param {Number} position Position of the source on the timeline.
* Correspond to the index of the line.
* @param {Boolean} segments If <code>true</code> this source will display segments;
*/
/**
* Handles all functionality related to the adding, removing and manipulation
* of sources.
*
* @class
* @alias TimelineSources
*
* @param {Peaks} peaks The parent Peaks object.
*/
function TimelineSources(peaks) {
this._peaks = peaks;
this._sources = [];
this._sourcesById = {};
this._colorIndex = 0;
this._onSourceCut = this._onSourceCut.bind(this);
this._peaks.on('source.cut', this._onSourceCut);
}
TimelineSources.prototype._onSourceCut = function(sourceToCut, cuttingTime) {
var originalMediaEndTime = sourceToCut.mediaEndTime;
var originalEndTime = sourceToCut.endTime;
// Cut the source
sourceToCut.update({
endTime: sourceToCut.startTime + cuttingTime,
mediaEndTime: sourceToCut.mediaStartTime + cuttingTime
});
// Create the copy (cut)
var newSourceOptions = {
id: Utils.createUuidv4(),
originId: sourceToCut.id,
elementId: sourceToCut.elementId,
title: sourceToCut.title,
titleAlignments: sourceToCut.titleAlignments,
url: sourceToCut.url,
previewUrl: sourceToCut.previewUrl,
binaryUrl: sourceToCut.binaryUrl,
kind: sourceToCut.kind,
subkind: sourceToCut.subkind,
duration: sourceToCut.duration,
startTime: sourceToCut.endTime,
endTime: originalEndTime,
mediaStartTime: sourceToCut.mediaEndTime,
mediaEndTime: originalMediaEndTime,
color: sourceToCut.color,
backgroundColor: sourceToCut.backgroundColor,
borderColor: sourceToCut.borderColor,
selectedColor: sourceToCut.selectedColor,
warningColor: sourceToCut.warningColor,
warningWidth: sourceToCut.warningWidth,
volumeSliderColor: sourceToCut.volumeSliderColor,
volumeSliderWidth: sourceToCut.volumeSliderWidth,
volumeSliderDraggingWidth: sourceToCut.volumeSliderDraggingWidth,
textFont: sourceToCut.textFont,
textFontSize: sourceToCut.textFontSize,
textColor: sourceToCut.textColor,
textBackgroundColor: sourceToCut.textBackgroundColor,
textPosition: sourceToCut.textPosition,
textAutoScroll: sourceToCut.textAutoScroll,
borderWidth: sourceToCut.borderWidth,
borderRadius: sourceToCut.borderRadius,
wrapped: sourceToCut.wrapped,
position: sourceToCut.position,
draggable: sourceToCut.draggable,
orderable: sourceToCut.orderable,
resizable: sourceToCut.resizable,
cuttable: sourceToCut.cuttable,
deletable: sourceToCut.deletable,
wrapping: sourceToCut.wrapping,
previewHeight: sourceToCut.previewHeight,
binaryHeight: sourceToCut.binaryHeight,
markers: sourceToCut.markers,
buttons: sourceToCut.buttons,
markerColor: sourceToCut.markerColor,
markerWidth: sourceToCut.markerWidth,
volume: sourceToCut.volume,
volumeRange: sourceToCut.volumeRange,
loading: sourceToCut.loading
};
for (var key in sourceToCut) {
if (key.startsWith('custom_')) {
newSourceOptions[key] = sourceToCut[key];
}
}
var newSource = this._add([newSourceOptions])[0];
this._peaks.emit('sources.updated', [sourceToCut, newSource]);
};
/**
* Returns a new unique Source id value.
*
* @private
* @returns {String}
*/
TimelineSources.prototype._getNextSourceId = function() {
return 'peaks.source.' + Utils.createUuidv4();
};
/**
* Adds a new Source object.
*
* @private
* @param {Source} Source
*/
TimelineSources.prototype._addSource = function(source) {
this._sources.push(source);
this._sourcesById[source.id] = source;
};
/**
* Creates a new Source object.
*
* @private
* @param {SourceOptions} options
* @return {Source}
*/
TimelineSources.prototype._createSource = function(options) {
// Watch for anyone still trying to use the old
// createSource(startTime, endTime, ...) API
if (!Utils.isObject(options)) {
throw new TypeError('peaks.sources.add(): expected a Source object parameter');
}
var customParams = [];
Object.entries(options).forEach(function([key, value]) {
if (key.startsWith('custom_')) {
customParams.push(key, value);
}
});
var source = new Source(
this._peaks,
options.id || this._getNextSourceId(),
options.originId,
options.elementId,
options.title,
options.titleAlignments,
options.url,
options.previewUrl,
options.binaryUrl,
options.kind,
options.subkind,
options.duration,
options.startTime,
options.endTime,
options.mediaStartTime,
options.mediaEndTime,
options.color,
options.backgroundColor,
options.borderColor,
options.selectedColor,
options.warningColor,
options.warningWidth,
options.volumeSliderColor,
options.volumeSliderWidth,
options.volumeSliderDraggingWidth,
options.textFont,
options.textFontSize,
options.textColor,
options.textBackgroundColor,
options.textPosition,
options.textAutoScroll,
options.borderWidth,
options.borderRadius,
options.wrapped,
options.position,
options.draggable,
options.orderable,
options.resizable,
options.cuttable,
options.deletable,
options.wrapping,
options.previewHeight,
options.binaryHeight,
options.indicators,
options.markers,
options.buttons,
options.markerColor,
options.markerWidth,
options.volume,
options.volumeRange,
options.loading,
...customParams
);
return source;
};
/**
* Returns all sources.
*
* @returns {Array<Source>}
*/
TimelineSources.prototype.getSources = function() {
return this._sources;
};
/**
* Returns the Source with the given id, or <code>null</code> if not found.
*
* @param {String} id
* @returns {Source|null}
*/
TimelineSources.prototype.getSource = function(id) {
return this._sourcesById[id] || null;
};
/**
* Returns all sources that overlap a given point in time.
*
* @param {Number} time
* @returns {Array<Source>}
*/
TimelineSources.prototype.getSourcesAtTime = function(time) {
return this._sources.filter(function(source) {
return time >= source.startTime && time <= source.endTime;
});
};
/**
* Returns all sources that overlap a given time region.
*
* @param {Number} startTime The start of the time region, in seconds.
* @param {Number} endTime The end of the time region, in seconds.
*
* @returns {Array<Source>}
*/
TimelineSources.prototype.find = function(startTime, endTime) {
return this._sources.filter(function(source) {
return source.isVisible(startTime, endTime);
});
};
/**
* Adds one or more sources to the timeline.
*
* @param {SourceOptions|Array<SourceOptions>} sourceOrSources
*/
TimelineSources.prototype.add = function(/* sourceOrSources */) {
var sources = Array.isArray(arguments[0]) ?
arguments[0] :
Array.prototype.slice.call(arguments);
this._add(sources);
};
TimelineSources.prototype._add = function(sources) {
var self = this;
sources = sources.map(function(sourceOptions) {
var source = self._createSource(sourceOptions);
if (Utils.objectHasProperty(self._sourcesById, source.id)) {
var src = Object.values(self._sourcesById)[0];
throw new Error('peaks.sources.add(): duplicate id. \n\n Source I = ' +
JSON.stringify(
{
id: src.id,
startTime: src.startTime,
endTime: src.endTime
}
) + ' Source II = ' +
JSON.stringify(
{
id: source.id,
startTime: source.startTime,
endTime: source.endTime
}
)
);
}
return source;
});
sources.forEach(function(source) {
self._addSource(source);
});
this._peaks.emit('sources.add', sources);
return sources;
};
/**
* Returns the indexes of sources that match the given predicate.
*
* @private
* @param {Function} predicate Predicate function to find matching sources.
* @returns {Array<Number>} An array of indexes into the sources array of
* the matching elements.
*/
TimelineSources.prototype._findSource = function(predicate) {
var indexes = [];
for (var i = 0, length = this._sources.length; i < length; i++) {
if (predicate(this._sources[i])) {
indexes.push(i);
}
}
return indexes;
};
/**
* destroys the sources at the given array indexes.
*
* @private
* @param {Array<Number>} indexes The array indexes to destroy.
* @returns {Array<Source>} The destroyd {@link Source} objects.
*/
TimelineSources.prototype._destroyIndexes = function(indexes) {
var destroyed = [];
for (var i = 0; i < indexes.length; i++) {
var index = indexes[i] - destroyed.length;
var itemDestroyed = this._sources.splice(index, 1)[0];
delete this._sourcesById[itemDestroyed.id];
destroyed.push(itemDestroyed);
}
return destroyed;
};
/**
* destroys all sources that match a given predicate function.
*
* After removing the sources, this function also emits a
* <code>sources.destroy</code> event with the destroyd {@link Source}
* objects.
*
* @private
* @param {Function} predicate A predicate function that identifies which
* sources to destroy.
* @returns {Array<Source>} The destroyd {@link Source} objects.
*/
TimelineSources.prototype._destroySources = function(predicate, shouldBlockEvent) {
var indexes = this._findSource(predicate);
var destroyed = this._destroyIndexes(indexes);
this._peaks.emit('sources.destroy', destroyed, shouldBlockEvent);
return destroyed;
};
/**
* destroys the given source.
*
* @param {Source} Source The source to destroy.
* @returns {Array<Source>} The destroyd source.
*/
TimelineSources.prototype.destroy = function(source, shouldBlockEvent) {
return this._destroySources(function(s) {
return s === source;
}, shouldBlockEvent);
};
/**
* destroys any sources with the given id.
*
* @param {String} id
* @returns {Array<Source>} The destroyed {@link Source} objects.
*/
TimelineSources.prototype.destroyById = function(sourceId, shouldBlockEvent) {
return this._destroySources(function(source) {
return source.id === sourceId;
}, shouldBlockEvent);
};
/**
* destroys any sources with the given start time, and optional end time.
*
* @param {Number} startTime sources with this start time are destroyd.
* @param {Number?} endTime If present, only sources with both the given
* start time and end time are destroyd.
* @returns {Array<Source>} The destroyd {@link Source} objects.
*/
TimelineSources.prototype.destroyByTime = function(startTime, endTime, shouldBlockEvent) {
var fnFilter;
if (endTime > 0) {
fnFilter = function(source) {
return source.startTime === startTime && source.endTime === endTime;
};
}
else {
fnFilter = function(source) {
return source.startTime === startTime;
};
}
return this._destroySources(fnFilter, shouldBlockEvent);
};
/**
* destroys all sources.
*/
TimelineSources.prototype.destroyAll = function(shouldBlockEvent) {
this._destroySources(function() {
return true;
}, shouldBlockEvent);
};
/**
* Set the wrapping of the source with the given ID to false.
* The source will be shown in its complete form.
*
* @param {String} sourceId The id of the source.
*/
TimelineSources.prototype.showById = function(sourceId) {
// var indexes = this._findSource(function(source) {
// return source.id === sourceId;
// })
this._sourcesById[sourceId].wrapped = false;
this._peaks.emit('sources.show', [this._sourcesById[sourceId]]);
};
/**
* Set the wrapping of the source with the given ID to true.
* The source will be shown in its reduced form.
*
* @param {String} sourceId The id of the source.
*/
TimelineSources.prototype.hideById = function(sourceId) {
// var indexes = this._findSource(function(source) {
// return source.id === sourceId;
// })
this._sourcesById[sourceId].wrapped = true;
this._peaks.emit('sources.hide', [this._sourcesById[sourceId]]);
};
return TimelineSources;
});