From 474daac25e7c208893c30d4a53ddeede852dd9dd Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Sun, 15 Sep 2024 11:08:42 +0100 Subject: [PATCH] Added zoomview.update event See #548 --- demo/custom-markers/main.js | 12 +++++-- demo/index.html | 5 +++ demo/overlay-segments.html | 4 +++ demo/zoomable-waveform.html | 6 ++++ doc/API.md | 24 +++++++++---- src/scrollbar.js | 8 +++-- src/waveform-overview.js | 10 +++--- src/waveform-zoomview.js | 44 ++++++++++++++--------- src/zoom-controller.js | 4 +-- test/api-zoom-spec.js | 9 +++-- test/waveform-zoomview-spec.js | 65 ++++++++++++++++++++++++++++++++++ 11 files changed, 154 insertions(+), 37 deletions(-) diff --git a/demo/custom-markers/main.js b/demo/custom-markers/main.js index 09d33069..b42986f1 100644 --- a/demo/custom-markers/main.js +++ b/demo/custom-markers/main.js @@ -332,7 +332,7 @@ Peaks.init(options, function(err, peaksInstance) { view.setSegmentDragMode(event.target.value); }); - // Points mouse events + // Point events peaksInstance.on('points.mouseenter', function(event) { console.log('points.mouseenter:', event); @@ -368,7 +368,7 @@ Peaks.init(options, function(err, peaksInstance) { console.log('points.dragend:', event); }); - // Segments mouse events + // Segment events peaksInstance.on('segments.dragstart', function(event) { console.log('segments.dragstart:', event); @@ -404,6 +404,8 @@ Peaks.init(options, function(err, peaksInstance) { console.log('segments.contextmenu:', event); }); + // Zoomview waveform events + peaksInstance.on('zoomview.click', function(event) { console.log('zoomview.click:', event); }); @@ -418,6 +420,12 @@ Peaks.init(options, function(err, peaksInstance) { console.log('zoomview.contextmenu:', event); }); + peaksInstance.on('zoomview.update', function(event) { + console.log('zoomview.update:', event); + }); + + // Overview waveform events + peaksInstance.on('overview.click', function(event) { console.log('overview.click:', event); }); diff --git a/demo/index.html b/demo/index.html index f44670cd..c6cbf56f 100644 --- a/demo/index.html +++ b/demo/index.html @@ -523,6 +523,7 @@

Points

}); // Point events + peaksInstance.on('points.add', function(event) { console.log('points.add:', event); }); @@ -633,6 +634,10 @@

Points

console.log('zoomview.contextmenu:', event); }); + peaksInstance.on('zoomview.update', function(event) { + console.log('zoomview.update:', event); + }); + // Overview waveform events peaksInstance.on('overview.click', function(event) { diff --git a/demo/overlay-segments.html b/demo/overlay-segments.html index f35d1852..24afac5a 100644 --- a/demo/overlay-segments.html +++ b/demo/overlay-segments.html @@ -623,6 +623,10 @@

Points

console.log('zoomview.contextmenu:', event); }); + peaksInstance.on('zoomview.update', function(event) { + console.log('zoomview.update:', event); + }); + // Overview waveform events peaksInstance.on('overview.click', function(event) { diff --git a/demo/zoomable-waveform.html b/demo/zoomable-waveform.html index 7155a161..41073f82 100644 --- a/demo/zoomable-waveform.html +++ b/demo/zoomable-waveform.html @@ -174,6 +174,12 @@

Demo: Single Zoomable Waveform

view.setStartTime(seconds); } }); + + // Zoomview waveform events + + peaksInstance.on('zoomview.update', function(event) { + console.log('zoomview.update:', event); + }); }); })(peaks); diff --git a/doc/API.md b/doc/API.md index 1b8ffabc..ac3940aa 100644 --- a/doc/API.md +++ b/doc/API.md @@ -92,6 +92,7 @@ This document describes the Peaks.js API, including configuration options, funct - [zoomview.click](#zoomviewclick) - [zoomview.dblclick](#zoomviewdblclick) - [zoomview.contextmenu](#zoomviewcontextmenu) + - [zoomview.update](#zoomviewupdate) - [zoom.update](#zoomupdate) - [Point Events](#point-events) - [points.add](#pointsadd) @@ -1700,21 +1701,32 @@ instance.on('zoomview.contextmenu', function(event) { }); ``` -### `zoom.update` +### `zoomview.update` -This event is emitted when the zoom level in the zoomable waveform view changes. +This event is emitted when the time range visible in the zoomable waveform view changes. The `event` parameter contains: -* `currentZoom`: The current zoom level, in samples per pixel -* `previousZoom`: The previous zoom level, in samples per pixel +* `startTime`: The time at the left edge of the waveform view. +* `endTime`: The time at the right edge of the waveform view. + +Note that `startTime` may not be exactly the same value you set when calling [`view.setStartTime()`](#viewsetstarttimetime). This is because the time is rounded to a number of pixels at the view's zoom level. ```js -instance.on('zoom.update', function(event) { - console.log(`Zoom changed from ${event.previousZoom} to ${event.currentZoom}`); +instance.on('zoomview.update', function(event) { + console.log(`Start time: ${event.startTime}, end time: ${event.endTime}`); }); ``` +### `zoom.update` + +This event is emitted when the zoom level in the zoomable waveform view changes. + +The `event` parameter contains: + +* `currentZoom`: The current zoom level, in samples per pixel +* `previousZoom`: The previous zoom level, in samples per pixel + ## Point Events ### `points.add` diff --git a/src/scrollbar.js b/src/scrollbar.js index 8e7dcf10..a88ca232 100644 --- a/src/scrollbar.js +++ b/src/scrollbar.js @@ -33,10 +33,10 @@ function Scrollbar(waveformData, container, peaks) { this._onScrollboxDragStart = this._onScrollboxDragStart.bind(this); this._onScrollboxDragMove = this._onScrollboxDragMove.bind(this); this._onScrollboxDragEnd = this._onScrollboxDragEnd.bind(this); - this._onZoomviewDisplaying = this._onZoomviewDisplaying.bind(this); + this._onZoomviewUpdate = this._onZoomviewUpdate.bind(this); this._onScrollbarClick = this._onScrollbarClick.bind(this); - peaks.on('zoomview.displaying', this._onZoomviewDisplaying); + this._peaks.on('zoomview.update', this._onZoomviewUpdate); this._width = container.clientWidth; this._height = container.clientHeight; @@ -150,7 +150,7 @@ Scrollbar.prototype._onScrollboxDragMove = function() { } }; -Scrollbar.prototype._onZoomviewDisplaying = function(/* startTime , endTime */) { +Scrollbar.prototype._onZoomviewUpdate = function(/* event */) { if (!this._dragging) { this._updateScrollbarWidthAndPosition(); } @@ -218,6 +218,8 @@ Scrollbar.prototype.fitToContainer = function() { }; Scrollbar.prototype.destroy = function() { + this._peaks.off('zoomview.update', this._onZoomviewUpdate); + this._layer.destroy(); this._stage.destroy(); diff --git a/src/waveform-overview.js b/src/waveform-overview.js index e49b4bee..0cb0794a 100644 --- a/src/waveform-overview.js +++ b/src/waveform-overview.js @@ -30,13 +30,13 @@ function WaveformOverview(waveformData, container, peaks) { self._onTimeUpdate = self._onTimeUpdate.bind(self); self._onPlaying = self._onPlaying.bind(self); self._onPause = self._onPause.bind(self); - self._onZoomviewDisplaying = self._onZoomviewDisplaying.bind(self); + self._onZoomviewUpdate = self._onZoomviewUpdate.bind(self); // Register event handlers peaks.on('player.timeupdate', self._onTimeUpdate); peaks.on('player.playing', self._onPlaying); peaks.on('player.pause', self._onPause); - peaks.on('zoomview.displaying', self._onZoomviewDisplaying); + peaks.on('zoomview.update', self._onZoomviewUpdate); const time = self._peaks.player.getCurrentTime(); @@ -82,8 +82,8 @@ WaveformOverview.prototype._onPause = function(time) { this._playheadLayer.stop(time); }; -WaveformOverview.prototype._onZoomviewDisplaying = function(startTime, endTime) { - this.showHighlight(startTime, endTime); +WaveformOverview.prototype._onZoomviewUpdate = function(event) { + this.showHighlight(event.startTime, event.endTime); }; WaveformOverview.prototype.showHighlight = function(startTime, endTime) { @@ -156,7 +156,7 @@ WaveformOverview.prototype.destroy = function() { this._peaks.off('player.playing', this._onPlaying); this._peaks.off('player.pause', this._onPause); this._peaks.off('player.timeupdate', this._onTimeUpdate); - this._peaks.off('zoomview.displaying', this._onZoomviewDisplaying); + this._peaks.off('zoomview.update', this._onZoomviewUpdate); this._mouseDragHandler.destroy(); diff --git a/src/waveform-zoomview.js b/src/waveform-zoomview.js index c1cf7bad..6fa9c0a3 100644 --- a/src/waveform-zoomview.js +++ b/src/waveform-zoomview.js @@ -68,7 +68,10 @@ function WaveformZoomView(waveformData, container, peaks) { self._onWheelCaptureVerticalScroll = self._onWheelCaptureVerticalScroll.bind(self); self.setWheelMode(self._viewOptions.wheelMode); - self._peaks.emit('zoomview.displaying', 0, self.getEndTime()); + self._peaks.emit('zoomview.update', { + startTime: 0, + endTime: self.getEndTime() + }); } WaveformZoomView.prototype = Object.create(WaveformView.prototype); @@ -150,11 +153,11 @@ WaveformZoomView.prototype._onWheel = function(event) { wheelEvent.preventDefault(); - const newFrameOffset = clamp( + const frameOffset = clamp( this._frameOffset + Math.floor(delta), 0, this._pixelLength - this._width ); - this.updateWaveform(newFrameOffset); + this.updateWaveform(frameOffset, false); }; WaveformZoomView.prototype._onWheelCaptureVerticalScroll = function(event) { @@ -165,11 +168,11 @@ WaveformZoomView.prototype._onWheelCaptureVerticalScroll = function(event) { wheelEvent.preventDefault(); - const newFrameOffset = clamp( + const frameOffset = clamp( this._frameOffset + Math.floor(delta), 0, this._pixelLength - this._width ); - this.updateWaveform(newFrameOffset); + this.updateWaveform(frameOffset, false); }; WaveformZoomView.prototype.setWaveformDragMode = function(mode) { @@ -291,15 +294,17 @@ WaveformZoomView.prototype._syncPlayhead = function(time) { // the keyboard) const endThreshold = this._frameOffset + this._width - this._autoScrollOffset; - if (pixelIndex >= endThreshold || pixelIndex < this._frameOffset) { + let frameOffset = this._frameOffset; + + if (pixelIndex >= endThreshold || pixelIndex < frameOffset) { // Put the playhead at 100 pixels from the left edge - this._frameOffset = pixelIndex - this._autoScrollOffset; + frameOffset = pixelIndex - this._autoScrollOffset; - if (this._frameOffset < 0) { - this._frameOffset = 0; + if (frameOffset < 0) { + frameOffset = 0; } - this.updateWaveform(this._frameOffset); + this.updateWaveform(frameOffset, false); } } }; @@ -387,9 +392,9 @@ WaveformZoomView.prototype.setZoom = function(options) { const apexPixel = this.timeToPixels(apexTime); - this._frameOffset = apexPixel - playheadOffsetPixels; + const frameOffset = apexPixel - playheadOffsetPixels; - this.updateWaveform(this._frameOffset); + this.updateWaveform(frameOffset, true); this._playheadLayer.zoomLevelChanged(); @@ -453,7 +458,7 @@ WaveformZoomView.prototype.setStartTime = function(time) { time = 0; } - this.updateWaveform(this.timeToPixels(time)); + this.updateWaveform(this.timeToPixels(time), false); }; /** @@ -483,7 +488,7 @@ WaveformZoomView.prototype.scrollWaveform = function(options) { throw new TypeError('view.scrollWaveform(): Missing umber of pixels or seconds'); } - this.updateWaveform(this._frameOffset + scrollAmount); + this.updateWaveform(this._frameOffset + scrollAmount, false); }; /** @@ -492,7 +497,7 @@ WaveformZoomView.prototype.scrollWaveform = function(options) { * @param {Number} frameOffset The new frame offset, in pixels. */ -WaveformZoomView.prototype.updateWaveform = function(frameOffset) { +WaveformZoomView.prototype.updateWaveform = function(frameOffset, forceUpdate) { let upperLimit; if (this._pixelLength < this._width) { @@ -507,6 +512,10 @@ WaveformZoomView.prototype.updateWaveform = function(frameOffset) { frameOffset = clamp(frameOffset, 0, upperLimit); + if (!forceUpdate && frameOffset === this._frameOffset) { + return; + } + this._frameOffset = frameOffset; // Display playhead if it is within the zoom frame width. @@ -528,7 +537,10 @@ WaveformZoomView.prototype.updateWaveform = function(frameOffset) { this._segmentsLayer.updateSegments(frameStartTime, frameEndTime); } - this._peaks.emit('zoomview.displaying', frameStartTime, frameEndTime); + this._peaks.emit('zoomview.update', { + startTime: frameStartTime, + endTime: frameEndTime + }); }; WaveformZoomView.prototype.enableAutoScroll = function(enable, options) { diff --git a/src/zoom-controller.js b/src/zoom-controller.js index 1d5600eb..9a3b8afd 100644 --- a/src/zoom-controller.js +++ b/src/zoom-controller.js @@ -32,7 +32,7 @@ ZoomController.prototype.setZoomLevels = function(zoomLevels) { */ ZoomController.prototype.zoomIn = function() { - this.setZoom(this._zoomLevelIndex - 1); + this.setZoom(this._zoomLevelIndex - 1, false); }; /** @@ -40,7 +40,7 @@ ZoomController.prototype.zoomIn = function() { */ ZoomController.prototype.zoomOut = function() { - this.setZoom(this._zoomLevelIndex + 1); + this.setZoom(this._zoomLevelIndex + 1, false); }; /** diff --git a/test/api-zoom-spec.js b/test/api-zoom-spec.js index 6862c2ec..55c1f02e 100644 --- a/test/api-zoom-spec.js +++ b/test/api-zoom-spec.js @@ -45,13 +45,14 @@ describe('Peaks.zoom', function() { expect(p.zoom.getZoom()).to.equal(1); }); - it('should emit a zoom.update event with the new zoom level index', function() { + it('should emit a zoom.update event with the new zoom level', function() { const spy = sinon.spy(); p.on('zoom.update', spy); p.zoom.setZoom(1); - expect(spy).to.have.been.calledWith({ currentZoom: 1024, previousZoom: 512 }); + expect(spy.callCount).to.equal(1); + expect(spy).calledWith({ currentZoom: 1024, previousZoom: 512 }); }); it('should limit the zoom level index value to the minimum valid index', function() { @@ -80,6 +81,7 @@ describe('Peaks.zoom', function() { p.on('zoom.update', spy); p.zoom.zoomOut(); + expect(spy.callCount).to.equal(1); expect(spy).to.have.been.calledWith({ currentZoom: 1024, previousZoom: 512 }); }); }); @@ -93,7 +95,8 @@ describe('Peaks.zoom', function() { p.on('zoom.update', spy); p.zoom.zoomIn(); - expect(spy).to.have.been.calledWith({ currentZoom: 512, previousZoom: 1024 }); + expect(spy.callCount).to.equal(1); + expect(spy).calledWith({ currentZoom: 512, previousZoom: 1024 }); }); }); }); diff --git a/test/waveform-zoomview-spec.js b/test/waveform-zoomview-spec.js index 10b45f7f..1d96bc6c 100644 --- a/test/waveform-zoomview-spec.js +++ b/test/waveform-zoomview-spec.js @@ -64,6 +64,27 @@ describe('WaveformZoomView', function() { expect(zoomview.getStartTime()).to.equal(0.0); }); + + it('should emit a zoomview.update event if the start time has changed', function() { + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + + zoomview.setStartTime(5.0); + + expect(spy.callCount).to.equal(1); + expect(spy.getCall(0).args[0].startTime).to.equal(zoomview.pixelsToTime(zoomview.timeToPixels(5.0))); + }); + + it('should not emit a zoomview.update event if the start time has not changed', function() { + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + + zoomview.setStartTime(-1.0); + + expect(spy.callCount).to.equal(0); + }); }); context('with auto zoom level', function() { @@ -76,6 +97,16 @@ describe('WaveformZoomView', function() { expect(zoomview.getStartTime()).to.equal(0.0); }); + + it('should not emit a zoomview.update event', function() { + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + + zoomview.setStartTime(5.0); + + expect(spy.callCount).to.equal(0); + }); }); }); @@ -211,6 +242,10 @@ describe('WaveformZoomView', function() { it('should scroll the waveform to the left', function() { zoomview.updateWaveform(500); + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + const distance = 100; inputController.mouseDown({ x: 100, y: 50 }); @@ -221,9 +256,16 @@ describe('WaveformZoomView', function() { expect(zoomview.getFrameOffset()).to.equal(400); expect(zoomview.getStartTime()).to.equal(view.pixelsToTime(400)); + + expect(spy.callCount).to.equal(1); + expect(spy.getCall(0).args[0].startTime).to.equal(view.pixelsToTime(400)); }); it('should not scroll beyond the start of the waveform', function() { + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + const distance = 200; inputController.mouseDown({ x: 50, y: 50 }); @@ -232,6 +274,8 @@ describe('WaveformZoomView', function() { expect(zoomview.getFrameOffset()).to.equal(0); expect(zoomview.getStartTime()).to.equal(0); + + expect(spy.callCount).to.equal(0); }); it('should not scroll beyond the end of the waveform', function() { @@ -258,6 +302,10 @@ describe('WaveformZoomView', function() { context('when dragging the waveform view', function() { it('should scroll the waveform to the right', function() { + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + const distance = 100; inputController.mouseDown({ x: 100, y: 50 }); @@ -268,11 +316,18 @@ describe('WaveformZoomView', function() { expect(zoomview.getFrameOffset()).to.equal(100); expect(zoomview.getStartTime()).to.equal(view.pixelsToTime(distance)); + + expect(spy.callCount).to.equal(1); + expect(spy.getCall(0).args[0].startTime).to.equal(view.pixelsToTime(distance)); }); it('should scroll the waveform to the left', function() { zoomview.updateWaveform(500); + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + const distance = 100; inputController.mouseDown({ x: 100, y: 50 }); @@ -283,11 +338,18 @@ describe('WaveformZoomView', function() { expect(zoomview.getFrameOffset()).to.equal(400); expect(zoomview.getStartTime()).to.equal(view.pixelsToTime(400)); + + expect(spy.callCount).to.equal(1); + expect(spy.getCall(0).args[0].startTime).to.equal(view.pixelsToTime(400)); }); it('should prevent the start time from becoming less than zero', function() { zoomview.updateWaveform(100); + const spy = sinon.spy(); + + p.on('zoomview.update', spy); + const distance = 150; inputController.mouseDown({ x: 100, y: 50 }); @@ -296,6 +358,9 @@ describe('WaveformZoomView', function() { expect(zoomview.getFrameOffset()).to.equal(0); expect(zoomview.getStartTime()).to.equal(0); + + expect(spy.callCount).to.equal(1); + expect(spy.getCall(0).args[0].startTime).to.equal(zoomview.pixelsToTime(0)); }); }); });