UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

1,079 lines (853 loc) 31.4 kB
/** * @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; });