UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

520 lines (441 loc) 14.3 kB
/** * @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; });