@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
520 lines (441 loc) • 14.3 kB
JavaScript
/**
* @file
*
* Defines the {@link SourceHandler} class.
*
* @module source-handler
*/
define([
'./models/source',
'./source-keys',
'./utils'
], function(Source, SourceKeys, Utils) {
'use strict';
var SOURCE_OPTION_KEYS = SourceKeys;
/**
* 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 SourceHandler
*
* @param {Peaks} peaks The parent Peaks object.
*/
function SourceHandler(peaks) {
this._peaks = peaks;
this._sources = [];
this._sourcesById = {};
this._colorIndex = 0;
}
SourceHandler.prototype.cutSource = 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(),
lineId: sourceToCut.lineId,
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,
hoverBackgroundColor: sourceToCut.hoverBackgroundColor,
selectedBackgroundColor: sourceToCut.selectedBackgroundColor,
borderColor: sourceToCut.borderColor,
selectedBorderColor: sourceToCut.selectedBorderColor,
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,
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,
targetSpeed: sourceToCut.targetSpeed,
waveformData: sourceToCut.waveformData
};
// Preserve user-defined metadata fields on the new (cut) source.
Object.keys(sourceToCut).forEach(function(key) {
if (key && !key.startsWith('_') && !SOURCE_OPTION_KEYS.has(key)) {
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}
*/
SourceHandler.prototype._getNextSourceId = function() {
return 'peaks.source.' + Utils.createUuidv4();
};
/**
* Adds a new Source object.
*
* @private
* @param {Source} Source
*/
SourceHandler.prototype._addSource = function(source) {
this._sources.push(source);
this._sourcesById[source.id] = source;
};
/**
* Creates a new Source object.
*
* @private
* @param {SourceOptions} options
* @return {Source}
*/
SourceHandler.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 extraParams = [];
Object.entries(options).forEach(function([key, value]) {
if (key && typeof key === 'string' && !key.startsWith('_') && !SOURCE_OPTION_KEYS.has(key)) {
extraParams.push(key, value);
}
});
var source = new Source(
this._peaks,
options.id || this._getNextSourceId(),
options.lineId,
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.hoverBackgroundColor,
options.selectedBackgroundColor,
options.borderColor,
options.selectedBorderColor,
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.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,
options.targetSpeed,
options.waveformData,
...extraParams
);
return source;
};
/**
* Returns all sources.
*
* @returns {Array<Source>}
*/
SourceHandler.prototype.getSources = function() {
return this._sources;
};
/**
* Returns all sources, serialized to a plain object.
*
* @returns {Array<Object>}
*/
SourceHandler.prototype.getSourcesSerialized = function() {
return this._sources.map(function(source) {
return source.toSerializable();
});
};
/**
* Returns the Source with the given id, or <code>null</code> if not found.
*
* @param {String} id
* @returns {Source|null}
*/
SourceHandler.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>}
*/
SourceHandler.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>}
*/
SourceHandler.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
*/
SourceHandler.prototype.add = function(/* sourceOrSources */) {
var sources = Array.isArray(arguments[0]) ?
arguments[0] :
Array.prototype.slice.call(arguments);
this._add(sources);
};
SourceHandler.prototype._add = function(sources) {
var self = this;
sources = sources.map(function(sourceOptions) {
if (
!Utils.isString(sourceOptions.lineId) ||
Utils.isNullOrUndefined(self._peaks.lineHandler.getLine(sourceOptions.lineId))
) {
throw new Error('peaks.sources.add(): line with id ' + sourceOptions.lineId +
' does not exist');
}
const source = self._createSource(sourceOptions);
if (Utils.objectHasProperty(self._sourcesById, source.id)) {
const src = Object.values(self._sourcesById)[0];
throw new Error('peaks.sources.add(): duplicate id. \n\n Source I = ' +
JSON.stringify(
src.toSerializable()
) + ' Source II = ' +
JSON.stringify(
source.toSerializable()
)
);
}
return source;
});
sources.forEach(function(source) {
self._addSource(source);
});
this._peaks.emit('handler.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.
*/
SourceHandler.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.
*/
SourceHandler.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 destroyed {@link Source}
* objects.
*
* @private
* @param {Function} predicate A predicate function that identifies which
* sources to destroy.
* @returns {Array<Source>} The destroyed {@link Source} objects.
*/
SourceHandler.prototype._destroySources = function(predicate) {
var indexes = this._findSource(predicate);
var destroyed = this._destroyIndexes(indexes);
this._peaks.emit('handler.sources.destroy', destroyed);
return destroyed;
};
/**
* destroys the given source.
*
* @param {Source} Source The source to destroy.
* @returns {Array<Source>} The destroyd source.
*/
SourceHandler.prototype.destroy = function(source) {
return this._destroySources(function(s) {
return s === source;
});
};
/**
* destroys any sources with the given id.
*
* @param {String} id
* @returns {Array<Source>} The destroyed {@link Source} objects.
*/
SourceHandler.prototype.destroyById = function(sourceId) {
return this._destroySources(function(source) {
return source.id === sourceId;
});
};
/**
* 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.
*/
SourceHandler.prototype.destroyByTime = function(startTime, endTime) {
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);
};
/**
* destroys all sources.
*/
SourceHandler.prototype.destroyAll = function() {
this._destroySources(function() {
return true;
});
};
/**
* 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.
*/
SourceHandler.prototype.showById = function(sourceId) {
if (this._sourcesById[sourceId].wrapped === false) {
return;
}
this._sourcesById[sourceId].wrapped = false;
this._peaks.emit('handler.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.
*/
SourceHandler.prototype.hideById = function(sourceId) {
if (this._sourcesById[sourceId].wrapped === true) {
return;
}
this._sourcesById[sourceId].wrapped = true;
this._peaks.emit('handler.sources.hide', [this._sourcesById[sourceId]]);
};
return SourceHandler;
});