@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
1,079 lines (853 loc) • 31.4 kB
JavaScript
/**
* @file
*
* Defines the {@link View} class.
*
* @module view
*/
define([
'./components/mouse-drag-handler',
'./components/playhead-layer',
'./components/sources-layer',
'./components/mode-layer',
'./components/axis',
'./utils',
'konva'
], function(
MouseDragHandler,
PlayheadLayer,
SourcesLayer,
ModeLayer,
Axis,
Utils,
Konva) {
'use strict';
/**
* Creates a zoomable timeline view.
*
* @class
* @alias View
*
* @param {HTMLElement} container
* @param {Peaks} peaks
*/
function View(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('main.overrideInteractions', self.overrideInteractions.bind(self));
self._peaks.on('main.allowInteractions', self.allowInteractions.bind(self));
self._peaks.on('handler.player.update', self._onTimeUpdate);
self._peaks.on('handler.player.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('handler.keyboard.left', self._onKeyboardLeft);
self._peaks.on('handler.keyboard.right', self._onKeyboardRight);
self._peaks.on('handler.keyboard.shiftleft', self._onKeyboardShiftLeft);
self._peaks.on('handler.keyboard.shiftright', self._onKeyboardShiftRight);
self._peaks.on('handler.view.defaultmode', self._onDefaultMode);
self._peaks.on('handler.view.cutmode', 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._focusedByHover = false;
self._focusedByClick = false; // Track if focus was set by click
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 Axis(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.getLineGroups(),
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.getFullHeight() - self._height;
var newFrameOffsetY = 0;
if (self._peaks.options.enableVerticalScrolling && height > 0) {
newFrameOffsetY = Utils.clamp(
this.initialFrameOffsetY + diffY, 0, height
);
}
else {
self.setFrameOffsetY(0);
}
if ((newFrameOffset !== this.initialFrameOffset)
|| (newFrameOffsetY !== this.initialFrameOffsetY)) {
self.updateTimeline(
newFrameOffset,
newFrameOffsetY
);
}
},
onMouseUp: function() {
self.enableAutoScroll(true);
}
});
this._stage.on('mouseover', function() {
self._focusedByHover = true;
});
this._stage.on('mouseout', function() {
self._focusedByHover = false;
});
this._stage.on('click', function(event) {
self._focusedByClick = true;
if (!self._isClickable) {
return;
}
self._peaks.emit('handler.view.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('handler.view.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('handler.view.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.getFullHeight() - self._height;
var newFrameOffsetY = 0;
if (self._peaks.options.enableVerticalScrolling && height > 0) {
newFrameOffsetY = Utils.clamp(
self.getFrameOffsetY() + diffY, 0, height
);
}
else {
self.setFrameOffsetY(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);
// Handle clicks outside the timeline to remove focus
this._onWindowClick = this._onWindowClick.bind(this);
window.addEventListener('click', this._onWindowClick, false);
}
View.prototype._mouseUp = function() {
this.stopAutoScroll();
this._peaks.emit('handler.view.mouseup');
};
View.prototype._onWindowClick = function(event) {
if (!this._container.contains(event.target)) {
this._focusedByClick = false;
}
};
View.prototype.setClickable = function(clickable) {
this._isClickable = clickable;
};
View.prototype.getSelectedElements = function() {
return Object.values(this._modeLayer.getSelectedElements());
};
/**
* Updates the view with auto-scroll behavior during drag operations.
*
* Auto-scroll activates when the pointer is within `autoScrollThreshold` of
* the left/right edges. While scrolling, we call `updateWhileScrolling` on
* every animation frame after updating the timeline offset.
*
* If auto-scroll is not active, we stop any pending auto-scroll loop and call
* `updateWhileNotScrolling` (or fall back to `updateWhileScrolling`).
*
* Uses requestAnimationFrame for smooth, consistent scrolling.
*
* @param {Function} updateWhileScrolling Called after each scroll step.
* @param {Function} [updateWhileNotScrolling] Called when not scrolling.
*/
View.prototype.updateWithAutoScroll = function(updateWhileScrolling,
updateWhileNotScrolling,
enableVerticalAutoScroll) {
var self = this;
var pointer = this.getPointerPosition();
var pointerX = pointer ? pointer.x : null;
var pointerY = pointer ? pointer.y : null;
var viewWidth = this.getWidth();
var viewHeight = this.getHeight();
var thresholdPx = Math.round(this._peaks.options.autoScrollThreshold * viewWidth);
var thresholdPy = Math.round(this._peaks.options.autoScrollThreshold * viewHeight);
var MAX_AUTO_SCROLL_PX_PER_FRAME = 30;
var NOMINAL_FRAME_MS = 16.67; // ~60fps
function getAutoScrollVelocity(pointerValue, threshold, size) {
if (typeof pointerValue !== 'number' || threshold <= 0 || size <= 0) {
return 0;
}
if (pointerValue < threshold) {
return Math.round(
-MAX_AUTO_SCROLL_PX_PER_FRAME * Math.min(1, (threshold - pointerValue) / threshold)
);
}
if (pointerValue > size - threshold) {
return Math.round(
MAX_AUTO_SCROLL_PX_PER_FRAME
* Math.min(1, (pointerValue - (size - threshold)) / threshold)
);
}
return 0;
}
var velocityXPerFrame = getAutoScrollVelocity(pointerX, thresholdPx, viewWidth);
var verticalScrollingEnabled = Boolean(enableVerticalAutoScroll
&& this._peaks.options.enableVerticalScrolling);
var maxFrameOffsetY = 0;
if (verticalScrollingEnabled) {
maxFrameOffsetY = this.getFullHeight() - viewHeight;
if (!Number.isFinite(maxFrameOffsetY) || maxFrameOffsetY < 0) {
maxFrameOffsetY = 0;
}
}
var velocityYPerFrame = verticalScrollingEnabled && maxFrameOffsetY > 0 ?
getAutoScrollVelocity(pointerY, thresholdPy, viewHeight) :
0;
// For left scroll (negative), only scroll if we can actually move left.
// For right scroll (positive), allow scrolling (timeline can extend).
var canScrollX = velocityXPerFrame > 0 || (velocityXPerFrame < 0 && self.getFrameOffset() > 0);
var canScrollY = verticalScrollingEnabled && maxFrameOffsetY > 0 && (
(velocityYPerFrame > 0 && self.getFrameOffsetY() < maxFrameOffsetY)
|| (velocityYPerFrame < 0 && self.getFrameOffsetY() > 0)
);
// Keep the current velocities on the instance for debugging/inspection.
this._autoScrollVelocityX = velocityXPerFrame;
this._autoScrollVelocityY = velocityYPerFrame;
if ((velocityXPerFrame !== 0 && canScrollX)
|| (velocityYPerFrame !== 0 && canScrollY)) {
if (!this._scrollingRafId) {
var lastTime = performance.now();
function scrollFrame(currentTime) {
if (!self._scrollingRafId) {
return;
}
var xVel = self._autoScrollVelocityX || 0;
var yVel = self._autoScrollVelocityY || 0;
var canContinueX = xVel > 0 || (xVel < 0 && self.getFrameOffset() > 0);
var maxY = 0;
var canContinueY = false;
if (verticalScrollingEnabled) {
maxY = self.getFullHeight() - self.getHeight();
if (!Number.isFinite(maxY) || maxY < 0) {
maxY = 0;
}
canContinueY = maxY > 0 && (
(yVel > 0 && self.getFrameOffsetY() < maxY)
|| (yVel < 0 && self.getFrameOffsetY() > 0)
);
}
if ((xVel === 0 || !canContinueX)
&& (yVel === 0 || !canContinueY)) {
self.stopAutoScroll();
updateWhileScrolling();
return;
}
// Calculate time delta for consistent scroll speed regardless of frame rate
var deltaTime = currentTime - lastTime;
var scrollAmountX = Math.round(xVel * deltaTime / NOMINAL_FRAME_MS);
var scrollAmountY = Math.round(yVel * deltaTime / NOMINAL_FRAME_MS);
lastTime = currentTime;
var newOffsetX = self.getFrameOffset() + scrollAmountX;
var newOffsetY = self.getFrameOffsetY() + scrollAmountY;
if (newOffsetX < 0) {
newOffsetX = 0;
}
if (verticalScrollingEnabled) {
if (newOffsetY < 0) {
newOffsetY = 0;
}
else if (newOffsetY > maxY) {
newOffsetY = maxY;
}
}
if (!verticalScrollingEnabled) {
self.updateTimeline(newOffsetX);
}
else {
self.updateTimeline(newOffsetX, newOffsetY);
}
updateWhileScrolling();
self._scrollingRafId = requestAnimationFrame(scrollFrame);
}
self._scrollingRafId = requestAnimationFrame(scrollFrame);
}
}
else {
this.stopAutoScroll();
if (updateWhileNotScrolling) {
updateWhileNotScrolling();
}
else {
updateWhileScrolling();
}
}
};
// Clear/stop any active auto-scroll loop.
View.prototype.stopAutoScroll = function() {
if (this._scrollingRafId) {
cancelAnimationFrame(this._scrollingRafId);
this._scrollingRafId = null;
}
};
View.prototype.getCurrentMode = function() {
return this._modeLayer.getCurrentMode();
};
View.prototype.overrideInteractions = function(areInteractionsAllowed) {
this._playheadLayer.listening(areInteractionsAllowed);
this._sourcesLayer.stopDrag();
this._sourcesLayer.batchDraw();
};
View.prototype.allowInteractions = function() {
this._sourcesLayer.stopDrag();
this._sourcesLayer.batchDraw();
};
View.prototype.getSelectedElements = function() {
return this._modeLayer.getSelectedElements();
};
View.prototype.getSourceGroupById = function(sourceId) {
return this._sourcesLayer.getSourceGroupById(sourceId);
};
/**
* Selects a source by its identifier.
*
* @param {String} sourceId
*/
View.prototype.selectSourceById = function(sourceId) {
const sourceGroup = this._sourcesLayer.getSourceGroupById(sourceId);
if (sourceGroup) {
this._modeLayer.selectElements([sourceGroup.getSource()], true);
}
};
/**
* Selects all sources on a line whose start time is at or after the given time.
*
* @param {String} lineId
* @param {Number} time
*/
View.prototype.selectSourcesOnLineAfter = function(lineId, time) {
const sources = this._sourcesLayer.getSourcesOnLineAfter(lineId, time);
if (sources.length) {
this._modeLayer.selectElements(sources, true);
}
};
/**
* Selects a segment by its identifier.
*
* @param {String} segmentId
*/
View.prototype.selectSegmentById = function(segmentId) {
const segment = this._peaks.segmentHandler.getSegment(segmentId);
if (segment) {
this._modeLayer.selectElements([segment], true);
}
};
/**
* Selects all segments in the given segment group or displayed line whose start time is at or after the given time.
*
* @param {String|Number} lineId
* @param {Number} time
*/
View.prototype.selectSegmentsOnLineAfter = function(lineId, time) {
const segments = this._sourcesLayer.getSegmentsOnLineAfter(lineId, time);
if (segments.length) {
this._modeLayer.selectElements(segments, true);
}
};
View.prototype.deselectAll = function(notify) {
this._modeLayer.deselectDifference([], notify);
};
View.prototype.isListening = function() {
return this._stage.listening();
};
View.prototype.isFocusedByHover = function() {
return this._focusedByHover;
};
View.prototype.isFocusedByClick = function() {
return this._focusedByClick;
};
View.prototype.isFocused = function() {
return this._focusedByHover || this._focusedByClick;
};
View.prototype.batchDrawSourcesLayer = function() {
this._sourcesLayer.batchDraw();
};
View.prototype.refresh = function() {
this._sourcesLayer.refresh();
};
View.prototype.getSegmentsGroup = function() {
return this._sourcesLayer.getSegmentsGroup();
};
View.prototype.getTimeToPixelsScale = function() {
return this._timeToPixelsScale;
};
View.prototype.getTimeToPixelsMaxZoom = function() {
return this._options.zoomRange[1];
};
View.prototype.setTimeToPixelsMaxZoom = function(value) {
this._options.zoomRange[1] = value;
if (value < this._timeToPixelsScale) {
this.setZoom(value);
}
};
View.prototype.getTimeToPixelsMinScale = function() {
return this._timeToPixelsMinScale;
};
View.prototype.getName = function() {
return 'zoomview';
};
View.prototype._onTimeUpdate = function(time) {
if (this._mouseDragHandler.isDragging()) {
return;
}
this._syncPlayhead(time);
};
View.prototype._onSeek = function(time) {
var frameIndex = this.timeToPixels(time);
this.updateTimeline(frameIndex - Math.floor(this._width / 2));
this._playheadLayer.updatePlayheadTime(time);
};
View.prototype._onPlay = function(time) {
this._playheadLayer.updatePlayheadTime(time);
this.enableAutoScroll(true);
};
View.prototype._onPause = function(time) {
this._playheadLayer.stop(time);
this.enableAutoScroll(true);
};
View.prototype._onWindowResize = function() {
var self = this;
var width = self._container.clientWidth;
self._width = width;
self._stage.width(width);
self.updateTimeline(self._frameOffset, self._frameOffsetY);
};
View.prototype._onKeyboardLeft = function() {
if (this.isFocused()) {
this._keyboardScroll(-1, false);
}
};
View.prototype._onKeyboardRight = function() {
if (this.isFocused()) {
this._keyboardScroll(1, false);
}
};
View.prototype._onKeyboardShiftLeft = function() {
if (this.isFocused()) {
this._keyboardScroll(-1, true);
}
};
View.prototype._onKeyboardShiftRight = function() {
if (this.isFocused()) {
this._keyboardScroll(1, true);
}
};
View.prototype._onDefaultMode = function() {
this.preventWrappingChange(false);
this._modeLayer.setMode('default');
};
View.prototype._onCutMode = function() {
this.preventWrappingChange(true);
this._modeLayer.setMode('cut');
};
View.prototype.preventWrappingChange = function(bool) {
this._preventWrappingChange = bool;
};
View.prototype.isPreventingWrappingChange = function() {
return this._preventWrappingChange;
};
View.prototype.getHoveredElement = function() {
return this._hoveredElement;
};
View.prototype.setHoveredElement = function(element) {
this._hoveredElement = element;
};
View.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);
};
View.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);
}
}
};
View.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.
*/
View.prototype._getScale = function(duration) {
return duration * this._data.sample_rate / this._width;
};
View.prototype.setZoom = function(newScale) {
newScale = Math.min(Math.max(this._options.zoomRange[0], newScale), this._options.zoomRange[1]);
if (newScale === this._timeToPixelsScale) {
return false;
}
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);
// Block interactions while updating the view.
this.overrideInteractions(true, true);
this.updateTimeline(this._frameOffset);
this._sourcesLayer.rescale();
// Update the playhead position after zooming.
this._playheadLayer.updatePlayheadTime(currentTime);
this._peaks.emit('handler.view.updatezoom', newScale, prevScale);
// Re-enable interactions after updating the view.
this.overrideInteractions(false, true);
return true;
};
View.prototype.getTimelineLength = function() {
return this._timelineLength;
};
View.prototype.updateTimelineLength = function() {
this._timelineLength = this._sourcesLayer.getLength() + this._peaks.options.horizontalPadding;
};
View.prototype.setTimelineLength = function(length) {
this._timelineLength = length;
};
View.prototype.getPointerPosition = function() {
return this._stage.getPointerPosition();
};
View.prototype.getStartTime = function() {
return this.pixelsToTime(this._frameOffset);
};
View.prototype.getEndTime = function() {
return this.pixelsToTime(this._frameOffset + this._width);
};
View.prototype.getLineByPosition = function(pos) {
return this._sourcesLayer.getLineByPosition(pos);
};
View.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.
*/
View.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.
*/
View.prototype.pixelsToTime = function(pixels) {
return Utils.roundTime(pixels / this._timeToPixelsScale);
};
/**
* @returns {Number} The start position of the waveform shown in the view,
* in pixels.
*/
View.prototype.getFrameOffset = function() {
return this._frameOffset;
};
View.prototype.setFrameOffset = function(newFrameOffset) {
newFrameOffset = Math.max(0, newFrameOffset);
this._frameOffset = Math.round(newFrameOffset);
this._timeOffset = this.pixelsToTime(this._frameOffset);
};
View.prototype.getTimeOffset = function() {
return this._timeOffset;
};
View.prototype.setTimeOffset = function(newTimeOffset) {
this._timeOffset = Utils.roundTime(newTimeOffset);
this._frameOffset = this.timeToPixels(this._timeOffset);
};
/**
* @returns {Number} The offset on Y,
* in pixels.
*/
View.prototype.getFrameOffsetY = function() {
return this._frameOffsetY;
};
/**
* @returns {Number} The width of the stage, in pixels.
*/
View.prototype.getWidth = function() {
return this._width;
};
/**
* @returns {Number} The width of the whole view, in pixels.
*/
View.prototype.getOriginalWidth = function() {
return this._originalWidth;
};
/**
* @returns {Number} The height of the view, in pixels.
*/
View.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
*/
View.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;
};
View.prototype.getAmplitudeScale = function() {
return this._amplitudeScale;
};
/**
* Updates the region of waveform shown in the view.
*
* @param {Number} frameOffset The new frame offset, in pixels.
*/
View.prototype.updateTimeline = function(frameOffset, frameOffsetY) {
if (!Utils.isNullOrUndefined(frameOffset)) {
this.setFrameOffset(frameOffset);
const playheadPixel = this._playheadLayer.getPlayheadPixel();
this._playheadLayer.updatePlayheadTime(this.pixelsToTime(playheadPixel));
this._axis.batchDraw();
}
const frameStartTime = this.pixelsToTime(this._frameOffset);
const frameEndTime = this.pixelsToTime(this._frameOffset + this._width);
if (!Utils.isNullOrUndefined(frameOffsetY)) {
this.setFrameOffsetY(frameOffsetY);
}
this._sourcesLayer.updateSources(frameStartTime, frameEndTime);
};
View.prototype.setFrameOffsetY = function(frameOffsetY) {
if (frameOffsetY === this._frameOffsetY) {
return;
}
this._frameOffsetY = frameOffsetY;
this._peaks.emit('handler.view.setFrameOffsetY', frameOffsetY);
};
View.prototype.toggleMainCursor = function(on, type) {
this._isMainCursorToggled = on;
this._stage.container().style.cursor = on ?
type :
this._nativeCursor;
};
View.prototype.setCursor = function(type) {
this._nativeCursor = type;
if (!this._isMainCursorToggled) {
this._stage.container().style.cursor = type;
}
};
View.prototype.getCursor = function() {
return this._stage.container().style.cursor;
};
View.prototype.showPlayheadTime = function(show) {
this._playheadLayer.showPlayheadTime(show);
};
View.prototype.enableAutoScroll = function(enable) {
this._enableAutoScroll = enable;
};
View.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.batchDraw();
}
};
View.prototype.getFullHeight = function() {
return this._sourcesLayer.getFullHeight();
};
View.prototype.destroy = function() {
// 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('handler.keyboard.left', this._onKeyboardLeft);
this._peaks.off('handler.keyboard.right', this._onKeyboardRight);
this._peaks.off('handler.keyboard.shiftleft', this._onKeyboardShiftLeft);
this._peaks.off('handler.keyboard.shiftright', this._onKeyboardShiftRight);
this._peaks.off('handler.view.defaultmode', this._onDefaultMode);
this._peaks.off('handler.view.cutmode', this._onCutMode);
// Remove window click listener
window.removeEventListener('click', this._onWindowClick, false);
if (this._stage) {
this._stage.destroy();
this._stage = null;
}
};
return View;
});