@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
907 lines (710 loc) • 26.3 kB
JavaScript
/**
* @file
*
* Defines the {@link TimelineZoomView} class.
*
* @module timeline-zoomview
*/
define([
'./mouse-drag-handler',
'./playhead-layer',
'./sources-layer',
'./mode-layer',
'./timeline-axis',
'./utils',
'konva'
], function(
MouseDragHandler,
PlayheadLayer,
SourcesLayer,
ModeLayer,
TimelineAxis,
Utils,
Konva) {
'use strict';
/**
* Creates a zoomable timeline view.
*
* @class
* @alias TimelineZoomView
*
* @param {HTMLElement} container
* @param {Peaks} peaks
*/
function TimelineZoomView(container, peaks) {
var self = this;
self._container = container;
self._peaks = peaks;
// Bind event handlers
self._onTimeUpdate = self._onTimeUpdate.bind(self);
self._onSeek = self._onSeek.bind(self);
self._onPlay = self._onPlay.bind(self);
self._onPause = self._onPause.bind(self);
self._onWindowResize = self._onWindowResize.bind(self);
self._onKeyboardLeft = self._onKeyboardLeft.bind(self);
self._onKeyboardRight = self._onKeyboardRight.bind(self);
self._onKeyboardShiftLeft = self._onKeyboardShiftLeft.bind(self);
self._onKeyboardShiftRight = self._onKeyboardShiftRight.bind(self);
self._onDefaultMode = self._onDefaultMode.bind(self);
self._onCutMode = self._onCutMode.bind(self);
// Register event handlers
self._peaks.on('timeline.update', self._onTimeUpdate);
self._peaks.on('timeline.seek', self._onTimeUpdate);
self._peaks.on('playhead.drag', self._onTimeUpdate);
self._peaks.on('playhead.dragend', self._onTimeUpdate);
self._peaks.on('user_seek', self._onSeek);
self._peaks.on('timeline.play', self._onPlay);
self._peaks.on('timeline.pause', self._onPause);
self._peaks.on('window_resize', self._onWindowResize);
self._peaks.on('keyboard.left', self._onKeyboardLeft);
self._peaks.on('keyboard.right', self._onKeyboardRight);
self._peaks.on('keyboard.shift_left', self._onKeyboardShiftLeft);
self._peaks.on('keyboard.shift_right', self._onKeyboardShiftRight);
self._peaks.on('default_mode', self._onDefaultMode);
self._peaks.on('cut_mode', self._onCutMode);
self._enableAutoScroll = true;
self._amplitudeScale = 1.0;
self._options = peaks.options;
self._sources = peaks.sources;
self._timelineLength = 0;
self._timeToPixelsScale = self._options.initialZoomLevel;
self._timeToPixelsMinScale = self._options.minScale;
self._resizeTimeoutId = null;
self._isFocused = false;
self._isClickable = true;
self._width = container.clientWidth;
self._height = container.clientHeight || self._options.height;
self._originalWidth = self._width;
// The pixel offset of the current frame being displayed
self.setFrameOffset(0);
self._frameOffsetY = 0;
self._preventWrappingChange = false;
container.oncontextmenu = function() {
return false;
};
container.style.whiteSpace = 'nowrap';
var lineIndicatorContainer = document.createElement('div');
var stageContainer = document.createElement('div');
lineIndicatorContainer.id = 'line-indicator-container';
stageContainer.id = 'stage-container';
stageContainer.style.display = 'inline-block';
stageContainer.style.zIndex = 0;
lineIndicatorContainer.style.display = 'inline-block';
lineIndicatorContainer.style.zIndex = 1;
container.appendChild(lineIndicatorContainer);
container.appendChild(stageContainer);
self._stage = new Konva.Stage({
container: stageContainer,
width: self._width - self._peaks.options.lineIndicatorWidth,
height: self._height
});
self._width -= self._peaks.options.lineIndicatorWidth;
self._axis = new TimelineAxis(self._peaks, self, {
axisGridlineColor: this._options.axisGridlineColor,
axisLabelColor: this._options.axisLabelColor
});
self._axis.addBackToStage(self._stage);
self._sourcesLayer = new SourcesLayer(peaks, self, true);
self._sourcesLayer.addToStage(self._stage);
self._axis.addFrontToStage(self._stage);
self._playheadLayer = new PlayheadLayer(
peaks,
self,
self._sourcesLayer,
self._options.showPlayheadTime
);
self._playheadLayer.addToStage(self._stage);
var time = self._peaks.player.getCurrentTime();
self._syncPlayhead(time);
self._hoveredElement = null;
self._modeLayer = new ModeLayer(peaks, self, self._playheadLayer, self._stage, 'default');
self._modeLayer.addToStage(self._stage);
self._mouseDragHandler = new MouseDragHandler(self._stage, {
onMouseDown: function(mousePosX, mousePosY) {
this.initialFrameOffset = self._frameOffset;
this.initialFrameOffsetY = self._frameOffsetY;
this.mouseDownX = mousePosX;
this.mouseDownY = mousePosY;
self.enableAutoScroll(false);
},
onMouseMove: function(mousePosX, mousePosY) {
// Moving the mouse to the left increases the time position of the
// left-hand edge of the visible waveform.
var diff = this.mouseDownX - mousePosX;
var diffY = this.mouseDownY - mousePosY;
var newFrameOffset = null;
if (self.isListening()) {
newFrameOffset = Math.max(
this.initialFrameOffset + diff, 0
);
}
var height = self._sourcesLayer._lines.height() - self._height;
var newFrameOffsetY = 0;
if (self._peaks.options.enableVerticalScrolling && height > 0) {
newFrameOffsetY = Utils.clamp(
this.initialFrameOffsetY + diffY, 0, height
);
}
else {
self._frameOffsetY = 0;
}
if ((newFrameOffset !== this.initialFrameOffset)
|| (newFrameOffsetY !== this.initialFrameOffsetY)) {
self.updateTimeline(
newFrameOffset,
newFrameOffsetY
);
}
},
onMouseUp: function() {
self.enableAutoScroll(true);
}
});
this._stage.on('mouseover', function() {
self._isFocused = true;
});
this._stage.on('mouseout', function() {
self._isFocused = false;
});
this._stage.on('click', function(event) {
self._isFocused = true;
if (!self._isClickable) {
return;
}
self._peaks.emit('zoomview.click', event);
// Set playhead position only on click release, when not dragging.
if (self._modeLayer.getCurrentMode() === 'default' && !self._mouseDragHandler.isDragging()) {
var mouseDownX = Math.floor(self._stage.getPointerPosition().x);
var pixelIndex = self._frameOffset + mouseDownX;
var time = self.pixelsToTime(pixelIndex);
if (!(self._options.blockUpdatingOnMouseClickWithMetaKey && event.metaKey)
&& !(self._options.blockUpdatingOnMouseClickWithCtrlKey && event.ctrlKey)) {
self.updateTimeline(pixelIndex - mouseDownX);
self._peaks.player.seek(time);
}
self._peaks.emit('zoomview.updateTime', event, time);
}
});
this._stage.on('dblclick', function(event) {
var mousePosX = event.evt.layerX;
var pixelIndex = self._frameOffset + mousePosX;
var time = self.pixelsToTime(pixelIndex);
self._peaks.emit('zoomview.dblclick', time);
});
this._stage.on('wheel', function(e) {
// prevent parent scrolling
e.evt.preventDefault();
if (self._mouseDragHandler.isDragging()) {
return;
}
if (self._peaks.keyboardHandler.isCtrlCmdPressed()) {
if (e.evt.deltaY > 0) {
self.setZoom(
self.getTimeToPixelsScale() + Math.floor(self.getTimeToPixelsScale() / 10) + 1
);
}
else if ((e.evt.deltaY < 0)) {
self.setZoom(
self.getTimeToPixelsScale() - Math.floor(self.getTimeToPixelsScale() / 10) + 1
);
}
self.updateTimelineLength();
}
else {
var diff = e.evt.deltaX * 2;
var diffY = e.evt.deltaY / 2;
var newFrameOffset = null;
if (self.isListening()) {
newFrameOffset = Utils.clamp(
self.getFrameOffset() + diff, 0, self._timelineLength - self._width
);
}
var height = self._sourcesLayer._lines.height() - self._height;
var newFrameOffsetY = 0;
if (self._peaks.options.enableVerticalScrolling && height > 0) {
newFrameOffsetY = Utils.clamp(
self.getFrameOffsetY() + diffY, 0, height
);
}
else {
self._frameOffsetY = 0;
}
if ((newFrameOffset !== self.getFrameOffset())
|| (newFrameOffsetY !== self.getFrameOffsetY())) {
self.updateTimeline(
newFrameOffset,
newFrameOffsetY
);
}
}
});
window.addEventListener('mouseup', this._mouseUp.bind(this), false);
window.addEventListener('touchend', this._mouseUp.bind(this), false);
window.addEventListener('blur', this._mouseUp.bind(this), false);
}
TimelineZoomView.prototype._mouseUp = function() {
this.clearScrollingInterval();
};
TimelineZoomView.prototype.setClickable = function(clickable) {
this._isClickable = clickable;
};
TimelineZoomView.prototype.getSelectedElements = function() {
return Object.values(this._modeLayer.getSelectedElements());
};
TimelineZoomView.prototype.updateWithAutoScroll = function(updateInInterval,
updateOutInterval) {
var self = this;
var posX = this.getPointerPosition().x;
var threshold = Math.round(this._peaks.options.autoScrollThreshold * this.getWidth());
this._limited = 0;
if (posX < threshold) {
this._limited = Math.round(-30 * Math.min(1, (threshold - posX) / threshold));
}
else if (posX > this.getWidth() - threshold) {
this._limited = Math.round(
30 * Math.min(1, (posX - (this.getWidth() - threshold)) / threshold)
);
}
if (this._limited && self.getFrameOffset() > 0 || this._limited > 0) {
if (!this._scrollingInterval) {
this._scrollingInterval = setInterval(
function() {
var newOffset = self.getFrameOffset() + self._limited;
if (newOffset < 0) {
self.updateTimeline(0, null, false);
clearInterval(self._scrollingInterval);
self._scrollingInterval = null;
}
else {
self.updateTimeline(self.getFrameOffset() + self._limited, null, false);
}
updateInInterval();
},
10
);
}
}
else {
this.clearScrollingInterval();
if (updateOutInterval) {
updateOutInterval();
}
else {
updateInInterval();
}
}
};
TimelineZoomView.prototype.clearScrollingInterval = function() {
if (this._scrollingInterval) {
clearInterval(this._scrollingInterval);
this._scrollingInterval = null;
}
};
TimelineZoomView.prototype.getCurrentMode = function() {
return this._modeLayer.getCurrentMode();
};
TimelineZoomView.prototype.overrideInteractions = function(bool, areInteractionsAllowed) {
this._sourcesLayer._lines.overrideInteractions(bool, areInteractionsAllowed);
this._playheadLayer.listening(areInteractionsAllowed);
this._sourcesLayer.stopDrag();
this._sourcesLayer.draw();
};
TimelineZoomView.prototype.allowInteractions = function(forSources, forSegments) {
this._sourcesLayer._lines.allowInteractions(forSources, forSegments);
this._sourcesLayer.stopDrag();
this._sourcesLayer.draw();
};
TimelineZoomView.prototype.getSelectedElements = function() {
return this._modeLayer.getSelectedElements();
};
TimelineZoomView.prototype.getSourceGroupById = function(sourceId) {
return this._sourcesLayer.getSourceGroupById(sourceId);
};
TimelineZoomView.prototype.selectSourceById = function(sourceId) {
const sourceGroup = this._sourcesLayer.getSourceGroupById(sourceId);
if (sourceGroup) {
this._modeLayer.selectElements([sourceGroup.getSource()], false);
}
};
TimelineZoomView.prototype.selectSourcesOnLineAfter = function(lineId, time) {
const sources = this._sourcesLayer.getSourcesOnLineAfter(lineId, time);
if (sources) {
this._modeLayer.selectElements(sources, false);
}
};
TimelineZoomView.prototype.deselectAll = function(notify) {
this._modeLayer.deselectDifference([], notify);
};
TimelineZoomView.prototype.isListening = function() {
return this._stage.listening();
};
TimelineZoomView.prototype.isFocused = function() {
return this._isFocused;
};
TimelineZoomView.prototype.drawSourcesLayer = function() {
this._sourcesLayer.draw();
};
TimelineZoomView.prototype.getSegmentsGroup = function() {
return this._sourcesLayer.getSegmentsGroup();
};
TimelineZoomView.prototype.getTimeToPixelsScale = function() {
return this._timeToPixelsScale;
};
TimelineZoomView.prototype.getTimeToPixelsMaxZoom = function() {
return this._options.zoomRange[1];
};
TimelineZoomView.prototype.setTimeToPixelsMaxZoom = function(value) {
this._options.zoomRange[1] = value;
if (value < this._timeToPixelsScale) {
this.setZoom(value);
}
};
TimelineZoomView.prototype.getTimeToPixelsMinScale = function() {
return this._timeToPixelsMinScale;
};
TimelineZoomView.prototype.getName = function() {
return 'zoomview';
};
TimelineZoomView.prototype._onTimeUpdate = function(time) {
if (this._mouseDragHandler.isDragging()) {
return;
}
this._syncPlayhead(time);
};
TimelineZoomView.prototype._onSeek = function(time) {
var frameIndex = this.timeToPixels(time);
this.updateTimeline(frameIndex - Math.floor(this._width / 2));
this._playheadLayer.updatePlayheadTime(time);
};
TimelineZoomView.prototype._onPlay = function(time) {
this._playheadLayer.updatePlayheadTime(time);
this.enableAutoScroll(true);
};
TimelineZoomView.prototype._onPause = function(time) {
this._playheadLayer.stop(time);
this.enableAutoScroll(true);
};
TimelineZoomView.prototype._onWindowResize = function() {
var self = this;
var width = self._container.clientWidth;
self._width = width;
self._stage.width(width);
self.updateTimeline(self._frameOffset, self._frameOffsetY);
};
TimelineZoomView.prototype._onKeyboardLeft = function() {
if (this.isFocused()) {
this._keyboardScroll(-1, false);
}
};
TimelineZoomView.prototype._onKeyboardRight = function() {
if (this.isFocused()) {
this._keyboardScroll(1, false);
}
};
TimelineZoomView.prototype._onKeyboardShiftLeft = function() {
if (this.isFocused()) {
this._keyboardScroll(-1, true);
}
};
TimelineZoomView.prototype._onKeyboardShiftRight = function() {
if (this.isFocused()) {
this._keyboardScroll(1, true);
}
};
TimelineZoomView.prototype._onDefaultMode = function() {
this.preventWrappingChange(false);
this._modeLayer.setMode('default');
};
TimelineZoomView.prototype._onCutMode = function() {
this.preventWrappingChange(true);
this._modeLayer.setMode('cut');
};
TimelineZoomView.prototype.preventWrappingChange = function(bool) {
this._preventWrappingChange = bool;
};
TimelineZoomView.prototype.isPreventingWrappingChange = function() {
return this._preventWrappingChange;
};
TimelineZoomView.prototype.getHoveredElement = function() {
return this._hoveredElement;
};
TimelineZoomView.prototype.setHoveredElement = function(element) {
this._hoveredElement = element;
};
TimelineZoomView.prototype._keyboardScroll = function(direction, large) {
var increment;
if (large) {
increment = direction * this._width;
}
else {
increment = direction * this.timeToPixels(this._options.nudgeIncrement);
}
this.updateTimeline(this._frameOffset + increment);
};
TimelineZoomView.prototype._syncPlayhead = function(time) {
this._playheadLayer.updatePlayheadTime(time);
if (this._enableAutoScroll) {
// Check for the playhead reaching the right-hand side of the window.
var pixelIndex = this.timeToPixels(time);
var threshold = Math.round(0.1 * this._width);
var endThreshold = this._frameOffset + this._width - threshold;
if (pixelIndex >= endThreshold || pixelIndex < this._frameOffset) {
// Put the playhead at 100 pixels from the left edge
this.setFrameOffset(pixelIndex - threshold);
if (this._frameOffset < 0) {
this.setFrameOffset(0);
}
this.updateTimeline(this._frameOffset);
}
}
};
TimelineZoomView.prototype.showPlayhead = function() {
var newFrameOffset = this._playheadLayer.getPlayheadPixel() - Math.round(0.1 * this._width);
this.setFrameOffset(newFrameOffset);
this.updateTimeline(this._frameOffset);
this.enableAutoScroll(true);
};
/**
* Changes the zoom level.
*
* @param {Number} scale The new zoom level, in samples per pixel.
*/
TimelineZoomView.prototype._getScale = function(duration) {
return duration * this._data.sample_rate / this._width;
};
TimelineZoomView.prototype.setZoom = function(newScale) {
newScale = Math.min(Math.max(this._options.zoomRange[0], newScale), this._options.zoomRange[1]);
var currentTime = this._peaks.player.getCurrentTime();
var apexTime;
var playheadOffsetPixels = this._playheadLayer.getPlayheadOffset();
if (playheadOffsetPixels >= 0 && playheadOffsetPixels < this._width) {
// Playhead is visible. Change the zoom level while keeping the
// playhead at the same position in the window.
apexTime = currentTime;
}
else {
// Playhead is not visible. Change the zoom level while keeping the
// centre of the window at the same position in the waveform.
playheadOffsetPixels = this._width / 2;
apexTime = this.pixelsToTime(this._frameOffset + playheadOffsetPixels);
}
var prevScale = this._timeToPixelsScale;
this._timeToPixelsScale = newScale;
var apexPixel = this.timeToPixels(apexTime);
this.setFrameOffset(apexPixel - playheadOffsetPixels);
this.updateTimeline(this._frameOffset, undefined, undefined, true);
this._sourcesLayer.rescale(true);
// Update the playhead position after zooming.
this._playheadLayer.updatePlayheadTime(currentTime);
this._peaks.emit('zoom.update', newScale, prevScale);
return true;
};
TimelineZoomView.prototype.getTimelineLength = function() {
return this._timelineLength;
};
TimelineZoomView.prototype.updateTimelineLength = function() {
this._timelineLength = this._sourcesLayer.getLength() + this._peaks.options.horizontalPadding;
};
TimelineZoomView.prototype.setTimelineLength = function(length) {
this._timelineLength = length;
};
TimelineZoomView.prototype.getPointerPosition = function() {
return this._stage.getPointerPosition();
};
TimelineZoomView.prototype.getStartTime = function() {
return this.pixelsToTime(this._frameOffset);
};
TimelineZoomView.prototype.getEndTime = function() {
return this.pixelsToTime(this._frameOffset + this._width);
};
TimelineZoomView.prototype.getLineByPosition = function(pos) {
return this._sourcesLayer.getLineByPosition(pos);
};
TimelineZoomView.prototype.setStartTime = function(time) {
if (time < 0) {
time = 0;
}
this.updateTimeline(this.timeToPixels(time));
};
/**
* Returns the pixel index for a given time, for the current zoom level.
*
* @param {Number} time Time, in seconds.
* @returns {Number} Pixel index.
*/
TimelineZoomView.prototype.timeToPixels = function(time) {
return Math.round(time * this._timeToPixelsScale);
};
/**
* Returns the time for a given pixel index, for the current zoom level.
*
* @param {Number} pixels Pixel index.
* @returns {Number} Time, in seconds.
*/
TimelineZoomView.prototype.pixelsToTime = function(pixels) {
return Utils.roundTime(pixels / this._timeToPixelsScale);
};
/**
* @returns {Number} The start position of the waveform shown in the view,
* in pixels.
*/
TimelineZoomView.prototype.getFrameOffset = function() {
return this._frameOffset;
};
TimelineZoomView.prototype.setFrameOffset = function(newFrameOffset) {
newFrameOffset = Math.max(0, newFrameOffset);
this._frameOffset = newFrameOffset;
this._timeOffset = this.pixelsToTime(this._frameOffset);
};
TimelineZoomView.prototype.getTimeOffset = function() {
return this._timeOffset;
};
TimelineZoomView.prototype.setTimeOffset = function(newTimeOffset) {
this._timeOffset = newTimeOffset;
this._frameOffset = this.timeToPixels(this._timeOffset);
};
/**
* @returns {Number} The offset on Y,
* in pixels.
*/
TimelineZoomView.prototype.getFrameOffsetY = function() {
return this._frameOffsetY;
};
/**
* @returns {Number} The width of the stage, in pixels.
*/
TimelineZoomView.prototype.getWidth = function() {
return this._width;
};
/**
* @returns {Number} The width of the whole view, in pixels.
*/
TimelineZoomView.prototype.getOriginalWidth = function() {
return this._originalWidth;
};
/**
* @returns {Number} The height of the view, in pixels.
*/
TimelineZoomView.prototype.getHeight = function() {
return this._height;
};
/**
* Adjusts the amplitude scale of waveform shown in the view, which allows
* users to zoom the waveform vertically.
*
* @param {Number} scale The new amplitude scale factor
*/
TimelineZoomView.prototype.setAmplitudeScale = function(scale) {
if (!Utils.isNumber(scale) || !Number.isFinite(scale)) {
throw new Error('view.setAmplitudeScale(): Scale must be a valid number');
}
this._amplitudeScale = scale;
};
TimelineZoomView.prototype.getAmplitudeScale = function() {
return this._amplitudeScale;
};
/**
* Updates the region of waveform shown in the view.
*
* @param {Number} frameOffset The new frame offset, in pixels.
*/
TimelineZoomView.prototype.updateTimeline = function(frameOffset, frameOffsetY, fixPlayhead,
ignoreRescale) {
var frameStartTime = null;
var frameEndTime = null;
if (frameOffset !== undefined && frameOffset !== null) {
this.setFrameOffset(frameOffset);
frameStartTime = this.pixelsToTime(this._frameOffset);
frameEndTime = this.pixelsToTime(this._frameOffset + this._width);
if (!fixPlayhead) {
// Display playhead if it is within the zoom frame width.
var playheadPixel = this._playheadLayer.getPlayheadPixel();
this._playheadLayer.updatePlayheadTime(this.pixelsToTime(playheadPixel));
}
this._axis.draw();
this._peaks.emit('zoomview.displaying', frameStartTime, frameEndTime);
}
if (frameOffsetY !== undefined && frameOffsetY !== null) {
this._frameOffsetY = frameOffsetY;
}
if (frameStartTime === null) {
frameStartTime = this.pixelsToTime(this._frameOffset);
}
if (frameEndTime === null) {
frameEndTime = this.pixelsToTime(this._frameOffset + this._width);
}
if (!ignoreRescale) {
this._sourcesLayer.rescale();
}
this._sourcesLayer.updateSources(frameStartTime, frameEndTime);
};
TimelineZoomView.prototype.toggleMainCursor = function(on, type) {
this._isMainCursorToggled = on;
this._stage.container().style.cursor = on ?
type :
this._nativeCursor;
};
TimelineZoomView.prototype.setCursor = function(type) {
this._nativeCursor = type;
if (!this._isMainCursorToggled) {
this._stage.container().style.cursor = type;
}
};
TimelineZoomView.prototype.getCursor = function() {
return this._stage.container().style.cursor;
};
TimelineZoomView.prototype.showPlayheadTime = function(show) {
this._playheadLayer.showPlayheadTime(show);
};
TimelineZoomView.prototype.enableAutoScroll = function(enable) {
this._enableAutoScroll = enable;
};
TimelineZoomView.prototype.fitToContainer = function() {
if (this._container.clientWidth === 0 && this._container.clientHeight === 0) {
return;
}
var shouldUpdate = false;
if (this._container.clientWidth - this._peaks.options.lineIndicatorWidth !== this._width) {
this._width = this._container.clientWidth - this._peaks.options.lineIndicatorWidth;
this._stage.width(this._width);
shouldUpdate = true;
}
if (this._container.clientHeight !== this._height) {
this._height = this._container.clientHeight;
this._stage.height(this._height);
shouldUpdate = true;
}
if (shouldUpdate) {
this._sourcesLayer.fitToView();
this._playheadLayer.fitToView();
this.updateTimeline(this._frameOffset);
this._stage.draw();
}
};
TimelineZoomView.prototype.getFullHeight = function() {
return this._sourcesLayer.getFullHeight();
};
TimelineZoomView.prototype.destroy = function() {
if (this._resizeTimeoutId) {
clearTimeout(this._resizeTimeoutId);
this._resizeTimeoutId = null;
}
// Unregister event handlers
this._peaks.off('player_time_update', this._onTimeUpdate);
this._peaks.off('user_seek', this._onSeek);
this._peaks.off('player_play', this._onPlay);
this._peaks.off('player_pause', this._onPause);
this._peaks.off('window_resize', this._onWindowResize);
this._peaks.off('keyboard.left', this._onKeyboardLeft);
this._peaks.off('keyboard.right', this._onKeyboardRight);
this._peaks.off('keyboard.shift_left', this._onKeyboardShiftLeft);
this._peaks.off('keyboard.shift_right', this._onKeyboardShiftRight);
this._peaks.off('default_mode', this._onDefaultMode);
this._peaks.off('cut_mode', this._onCutMode);
if (this._stage) {
this._stage.destroy();
this._stage = null;
}
};
return TimelineZoomView;
});