wavesurfer.js 7.0.0-alpha.17 → 7.0.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,60 +16,21 @@ const el = (tagName, css) => {
16
16
  return element;
17
17
  };
18
18
  class RegionsPlugin extends BasePlugin {
19
+ dragStart = NaN;
20
+ regionsContainer;
21
+ regions = [];
22
+ createdRegion = null;
23
+ modifiedRegion = null;
24
+ isResizingLeft = false;
25
+ isMoving = false;
19
26
  /** Create an instance of RegionsPlugin */
20
27
  constructor(params, options) {
21
28
  super(params, options);
22
- this.dragStart = NaN;
23
- this.regions = [];
24
- this.createdRegion = null;
25
- this.modifiedRegion = null;
26
- this.isResizingLeft = false;
27
- this.isMoving = false;
28
- this.handleMouseDown = (e) => {
29
- if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
30
- this.dragStart = e.clientX - this.wrapper.getBoundingClientRect().left;
31
- }
32
- };
33
- this.handleMouseMove = (e) => {
34
- const dragEnd = e.clientX - this.wrapper.getBoundingClientRect().left;
35
- if (this.options.draggable && this.modifiedRegion && this.isMoving) {
36
- this.moveRegion(this.modifiedRegion, dragEnd - this.dragStart);
37
- this.dragStart = dragEnd;
38
- return;
39
- }
40
- if (this.options.resizable && this.modifiedRegion) {
41
- this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
42
- return;
43
- }
44
- if (this.options.dragSelection && !isNaN(this.dragStart)) {
45
- const dragEnd = e.clientX - this.regionsContainer.getBoundingClientRect().left;
46
- if (dragEnd - this.dragStart >= MIN_WIDTH) {
47
- if (!this.createdRegion) {
48
- this.wrapper.style.pointerEvents = 'none';
49
- this.createdRegion = this.createRegion(this.dragStart, dragEnd);
50
- }
51
- else {
52
- this.updateRegion(this.createdRegion, this.dragStart, dragEnd);
53
- }
54
- }
55
- }
56
- };
57
- this.handleMouseUp = () => {
58
- if (this.createdRegion) {
59
- this.addRegion(this.createdRegion);
60
- this.createdRegion = null;
61
- }
62
- this.modifiedRegion = null;
63
- this.isMoving = false;
64
- this.dragStart = NaN;
65
- this.wrapper.style.pointerEvents = '';
66
- };
67
29
  this.options = Object.assign({}, defaultOptions, options);
68
30
  this.regionsContainer = this.initRegionsContainer();
69
- const unsubscribe = this.wavesurfer.once('decode', () => {
31
+ this.subscriptions.push(this.wavesurfer.once('decode', () => {
70
32
  this.wrapper.appendChild(this.regionsContainer);
71
- });
72
- this.subscriptions.push(unsubscribe);
33
+ }));
73
34
  this.wrapper.addEventListener('mousedown', this.handleMouseDown);
74
35
  document.addEventListener('mousemove', this.handleMouseMove);
75
36
  document.addEventListener('mouseup', this.handleMouseUp);
@@ -93,12 +54,53 @@ class RegionsPlugin extends BasePlugin {
93
54
  pointerEvents: 'none',
94
55
  });
95
56
  }
57
+ handleMouseDown = (e) => {
58
+ if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
59
+ this.dragStart = e.clientX - this.wrapper.getBoundingClientRect().left;
60
+ }
61
+ };
62
+ handleMouseMove = (e) => {
63
+ const box = this.wrapper.getBoundingClientRect();
64
+ const { width } = box;
65
+ const dragEnd = e.clientX - box.left;
66
+ if (this.options.draggable && this.modifiedRegion && this.isMoving) {
67
+ this.moveRegion(this.modifiedRegion, (dragEnd - this.dragStart) / width);
68
+ this.dragStart = dragEnd;
69
+ return;
70
+ }
71
+ if (this.options.resizable && this.modifiedRegion) {
72
+ this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd / width : undefined, this.isResizingLeft ? undefined : dragEnd / width);
73
+ return;
74
+ }
75
+ if (this.options.dragSelection && !isNaN(this.dragStart)) {
76
+ const dragEnd = e.clientX - this.regionsContainer.getBoundingClientRect().left;
77
+ if (dragEnd - this.dragStart >= MIN_WIDTH) {
78
+ if (!this.createdRegion) {
79
+ this.wrapper.style.pointerEvents = 'none';
80
+ this.createdRegion = this.createRegion(this.dragStart / width, dragEnd / width);
81
+ }
82
+ else {
83
+ this.updateRegion(this.createdRegion, this.dragStart / width, dragEnd / width);
84
+ }
85
+ }
86
+ }
87
+ };
88
+ handleMouseUp = () => {
89
+ if (this.createdRegion) {
90
+ this.addRegion(this.createdRegion);
91
+ this.createdRegion = null;
92
+ }
93
+ this.modifiedRegion = null;
94
+ this.isMoving = false;
95
+ this.dragStart = NaN;
96
+ this.wrapper.style.pointerEvents = '';
97
+ };
96
98
  createRegionElement(start, end, title = '') {
97
99
  const noWidth = start === end;
98
100
  const div = el('div', {
99
101
  position: 'absolute',
100
- left: `${start}px`,
101
- width: `${end - start}px`,
102
+ left: `${start * 100}%`,
103
+ width: `${(end - start) * 100}%`,
102
104
  height: '100%',
103
105
  backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
104
106
  borderRadius: '2px',
@@ -165,15 +167,12 @@ class RegionsPlugin extends BasePlugin {
165
167
  }
166
168
  createRegion(start, end, title = '') {
167
169
  const duration = this.wavesurfer.getDuration();
168
- const width = this.regionsContainer.clientWidth;
169
- start = Math.max(0, Math.min(start, width - 1));
170
- end = Math.max(0, Math.min(end, width - 1));
171
170
  return {
172
171
  element: this.createRegionElement(start, end, title),
173
172
  start,
174
173
  end,
175
- startTime: (start / width) * duration,
176
- endTime: (end / width) * duration,
174
+ startTime: start * duration,
175
+ endTime: end * duration,
177
176
  title,
178
177
  };
179
178
  }
@@ -182,19 +181,16 @@ class RegionsPlugin extends BasePlugin {
182
181
  this.emit('region-created', { region });
183
182
  }
184
183
  updateRegion(region, start, end) {
185
- const width = this.regionsContainer.clientWidth;
186
184
  if (start != null) {
187
- start = Math.max(0, Math.min(start, width));
188
185
  region.start = start;
189
- region.element.style.left = `${region.start}px`;
190
- region.element.style.width = `${region.end - region.start}px`;
191
- region.startTime = (start / this.regionsContainer.clientWidth) * this.wavesurfer.getDuration();
186
+ region.element.style.left = `${region.start * 100}%`;
187
+ region.element.style.width = `${(region.end - region.start) * 100}%`;
188
+ region.startTime = start * this.wavesurfer.getDuration();
192
189
  }
193
190
  if (end != null) {
194
- end = Math.max(0, Math.min(end, width));
195
191
  region.end = end;
196
- region.element.style.width = `${region.end - region.start}px`;
197
- region.endTime = (end / this.regionsContainer.clientWidth) * this.wavesurfer.getDuration();
192
+ region.element.style.width = `${(region.end - region.start) * 100}%`;
193
+ region.endTime = end * this.wavesurfer.getDuration();
198
194
  }
199
195
  this.emit('region-updated', { region });
200
196
  }
@@ -204,9 +200,8 @@ class RegionsPlugin extends BasePlugin {
204
200
  /** Create a region at a given start and end time, with an optional title */
205
201
  add(startTime, endTime, title = '', color = '') {
206
202
  const duration = this.wavesurfer.getDuration();
207
- const width = this.regionsContainer.clientWidth;
208
- const start = (startTime / duration) * width;
209
- const end = (endTime / duration) * width;
203
+ const start = startTime / duration;
204
+ const end = endTime / duration;
210
205
  const region = this.createRegion(start, end, title);
211
206
  this.addRegion(region);
212
207
  if (color)
@@ -3,11 +3,12 @@ const defaultOptions = {
3
3
  height: 20,
4
4
  };
5
5
  class TimelinePlugin extends BasePlugin {
6
+ timelineWrapper;
7
+ options;
6
8
  constructor(params, options) {
7
- var _a;
8
9
  super(params, options);
9
10
  this.options = Object.assign({}, defaultOptions, options);
10
- this.container = (_a = this.options.container) !== null && _a !== void 0 ? _a : this.wrapper;
11
+ this.container = this.options.container ?? this.wrapper;
11
12
  this.timelineWrapper = this.initTimelineWrapper();
12
13
  this.container.appendChild(this.timelineWrapper);
13
14
  if (this.options.duration) {
@@ -78,12 +79,11 @@ class TimelinePlugin extends BasePlugin {
78
79
  return 2;
79
80
  }
80
81
  initTimeline(duration) {
81
- var _a, _b, _c;
82
82
  const width = Math.round(this.timelineWrapper.scrollWidth * devicePixelRatio);
83
83
  const pxPerSec = width / duration;
84
- const timeInterval = (_a = this.options.timeInterval) !== null && _a !== void 0 ? _a : this.defaultTimeInterval(pxPerSec);
85
- const primaryLabelInterval = (_b = this.options.primaryLabelInterval) !== null && _b !== void 0 ? _b : this.defaultPrimaryLabelInterval(pxPerSec);
86
- const secondaryLabelInterval = (_c = this.options.secondaryLabelInterval) !== null && _c !== void 0 ? _c : this.defaultSecondaryLabelInterval(pxPerSec);
84
+ const timeInterval = this.options.timeInterval ?? this.defaultTimeInterval(pxPerSec);
85
+ const primaryLabelInterval = this.options.primaryLabelInterval ?? this.defaultPrimaryLabelInterval(pxPerSec);
86
+ const secondaryLabelInterval = this.options.secondaryLabelInterval ?? this.defaultSecondaryLabelInterval(pxPerSec);
87
87
  const timeline = document.createElement('div');
88
88
  timeline.setAttribute('style', `
89
89
  height: ${this.options.height}px;
@@ -11,6 +11,7 @@ type RendererOptions = {
11
11
  barWidth?: number;
12
12
  barGap?: number;
13
13
  barRadius?: number;
14
+ noScrollbar?: boolean;
14
15
  };
15
16
  type RendererEvents = {
16
17
  click: {
@@ -22,18 +23,15 @@ declare class Renderer extends EventEmitter<RendererEvents> {
22
23
  private container;
23
24
  private scrollContainer;
24
25
  private wrapper;
25
- private mainCanvas;
26
- private progressCanvas;
27
- private cursor;
28
- private ctx;
26
+ private canvasWrapper;
27
+ private progressWrapper;
29
28
  private timeout;
30
29
  constructor(options: RendererOptions);
31
- getContainer(): ShadowRoot;
30
+ getContainer(): HTMLElement;
32
31
  getWrapper(): HTMLElement;
33
32
  destroy(): void;
34
33
  private delay;
35
34
  private renderPeaks;
36
- private createProgressMask;
37
35
  render(audioData: AudioBuffer): void;
38
36
  zoom(audioData: AudioBuffer, minPxPerSec: number): void;
39
37
  renderProgress(progress: number, autoCenter?: boolean): void;
package/dist/renderer.js CHANGED
@@ -1,17 +1,14 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import EventEmitter from './event-emitter.js';
11
2
  class Renderer extends EventEmitter {
3
+ options;
4
+ container;
5
+ scrollContainer;
6
+ wrapper;
7
+ canvasWrapper;
8
+ progressWrapper;
9
+ timeout = null;
12
10
  constructor(options) {
13
11
  super();
14
- this.timeout = null;
15
12
  this.options = options;
16
13
  let container = null;
17
14
  if (typeof this.options.container === 'string') {
@@ -35,64 +32,65 @@ class Renderer extends EventEmitter {
35
32
  overflow-y: hidden;
36
33
  width: 100%;
37
34
  position: relative;
35
+ ${this.options.noScrollbar ? 'scrollbar-color: transparent;' : ''}
36
+ }
37
+ :host ::-webkit-scrollbar {
38
+ display: ${this.options.noScrollbar ? 'none' : 'auto'};
38
39
  }
39
40
  :host .wrapper {
40
41
  position: relative;
41
- width: fit-content;
42
42
  min-width: 100%;
43
43
  z-index: 2;
44
44
  }
45
+ :host .canvases {
46
+ position: relative;
47
+ height: ${this.options.height}px;
48
+ }
45
49
  :host canvas {
46
50
  display: block;
51
+ position: absolute;
52
+ top: 0;
47
53
  height: ${this.options.height}px;
48
- min-width: 100%;
49
54
  image-rendering: pixelated;
50
55
  }
51
56
  :host .progress {
52
- position: absolute;
53
- z-index: 2;
54
- top: 0;
55
- left: 0;
56
- width: 100%;
57
57
  pointer-events: none;
58
- clip-path: inset(0px 100% 0px 0px);
59
- }
60
- :host .cursor {
61
58
  position: absolute;
62
- z-index: 3;
59
+ z-index: 2;
63
60
  top: 0;
64
61
  left: 0;
65
- height: 100%;
66
- width: ${this.options.cursorWidth}px;
67
- background: ${this.options.cursorColor || this.options.progressColor};
62
+ width: 0;
63
+ height: ${this.options.height}px;
64
+ overflow: hidden;
65
+ box-sizing: border-box;
66
+ border-right-style: solid;
67
+ border-right-width: ${this.options.cursorWidth}px;
68
+ border-right-color: ${this.options.cursorColor || this.options.progressColor};
68
69
  }
69
70
  </style>
70
71
 
71
72
  <div class="scroll">
72
73
  <div class="wrapper">
73
- <canvas></canvas>
74
- <canvas class="progress"></canvas>
75
- <div class="cursor"></div>
74
+ <div class="canvases"></div>
75
+ <div class="progress"></div>
76
76
  </div>
77
77
  </div>
78
78
  `;
79
79
  this.container = div;
80
80
  this.scrollContainer = shadow.querySelector('.scroll');
81
81
  this.wrapper = shadow.querySelector('.wrapper');
82
- this.mainCanvas = shadow.querySelector('canvas');
83
- this.ctx = this.mainCanvas.getContext('2d', { desynchronized: true });
84
- this.progressCanvas = shadow.querySelector('.progress');
85
- this.cursor = shadow.querySelector('.cursor');
82
+ this.canvasWrapper = shadow.querySelector('.canvases');
83
+ this.progressWrapper = shadow.querySelector('.progress');
86
84
  container.appendChild(div);
87
- this.mainCanvas.addEventListener('click', (e) => {
88
- const rect = this.mainCanvas.getBoundingClientRect();
85
+ this.wrapper.addEventListener('click', (e) => {
86
+ const rect = this.wrapper.getBoundingClientRect();
89
87
  const x = e.clientX - rect.left;
90
88
  const relativeX = x / rect.width;
91
89
  this.emit('click', { relativeX });
92
90
  });
93
91
  }
94
92
  getContainer() {
95
- return this.container.shadowRoot;
93
+ return this.scrollContainer;
96
94
  }
97
95
  getWrapper() {
98
96
  return this.wrapper;
@@ -100,7 +98,7 @@ class Renderer extends EventEmitter {
100
98
  destroy() {
101
99
  this.container.remove();
102
100
  }
103
- delay(fn, delayMs = 100) {
101
+ delay(fn, delayMs = 10) {
104
102
  if (this.timeout) {
105
103
  clearTimeout(this.timeout);
106
104
  }
@@ -110,139 +108,128 @@ class Renderer extends EventEmitter {
110
108
  }, delayMs);
111
109
  });
112
110
  }
113
- renderPeaks(channelData, width, height) {
114
- var _a;
115
- return __awaiter(this, void 0, void 0, function* () {
116
- const { devicePixelRatio } = window;
117
- const { ctx } = this;
118
- const barWidth = this.options.barWidth != null ? this.options.barWidth * devicePixelRatio : 1;
119
- const barGap = this.options.barGap != null ? this.options.barGap * devicePixelRatio : this.options.barWidth ? barWidth / 2 : 0;
120
- const barRadius = (_a = this.options.barRadius) !== null && _a !== void 0 ? _a : 0;
121
- const leftChannel = channelData[0];
122
- const len = leftChannel.length;
123
- const barCount = Math.floor(width / (barWidth + barGap));
124
- const barIndexScale = barCount / len;
125
- const halfHeight = height / 2;
126
- const isMono = channelData.length === 1;
127
- const rightChannel = isMono ? leftChannel : channelData[1];
128
- const useNegative = isMono && rightChannel.some((v) => v < 0);
129
- const draw = (start, end) => {
130
- let prevX = 0;
131
- let prevLeft = 0;
132
- let prevRight = 0;
133
- ctx.beginPath();
134
- ctx.fillStyle = this.options.waveColor;
135
- // Firefox shim until 2023.04.11
136
- if (!ctx.roundRect)
137
- ctx.roundRect = ctx.fillRect;
138
- for (let i = start; i < end; i++) {
139
- const barIndex = Math.round(i * barIndexScale);
140
- if (barIndex > prevX) {
141
- const leftBarHeight = Math.round(prevLeft * halfHeight);
142
- const rightBarHeight = Math.round(prevRight * halfHeight);
143
- ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + rightBarHeight || 1, barRadius);
144
- prevX = barIndex;
145
- prevLeft = 0;
146
- prevRight = 0;
147
- }
148
- const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
149
- const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
150
- if (leftValue > prevLeft) {
151
- prevLeft = leftValue;
152
- }
153
- // If stereo, both channels are drawn as max values
154
- // If mono with negative values, the bottom channel will be the min negative values
155
- if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
156
- prevRight = rightValue < 0 ? -rightValue : rightValue;
157
- }
111
+ async renderPeaks(channelData, width, height, pixelRatio) {
112
+ const barWidth = this.options.barWidth != null ? this.options.barWidth * pixelRatio : 1;
113
+ const barGap = this.options.barGap != null ? this.options.barGap * pixelRatio : this.options.barWidth ? barWidth / 2 : 0;
114
+ const barRadius = this.options.barRadius ?? 0;
115
+ const leftChannel = channelData[0];
116
+ const len = leftChannel.length;
117
+ const barCount = Math.floor(width / (barWidth + barGap));
118
+ const barIndexScale = barCount / len;
119
+ const halfHeight = height / 2;
120
+ const isMono = channelData.length === 1;
121
+ const rightChannel = isMono ? leftChannel : channelData[1];
122
+ const useNegative = isMono && rightChannel.some((v) => v < 0);
123
+ const draw = (start, end) => {
124
+ let prevX = 0;
125
+ let prevLeft = 0;
126
+ let prevRight = 0;
127
+ const canvas = document.createElement('canvas');
128
+ canvas.width = Math.round((width * (end - start)) / len);
129
+ canvas.height = this.options.height;
130
+ canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
131
+ canvas.style.left = `${Math.floor((start * width) / pixelRatio / len)}px`;
132
+ this.canvasWrapper.appendChild(canvas);
133
+ const ctx = canvas.getContext('2d', {
134
+ desynchronized: true,
135
+ });
136
+ ctx.beginPath();
137
+ ctx.fillStyle = this.options.waveColor;
138
+ // Firefox shim until 2023.04.11
139
+ if (!ctx.roundRect)
140
+ ctx.roundRect = ctx.fillRect;
141
+ for (let i = start; i < end; i++) {
142
+ const barIndex = Math.round((i - start) * barIndexScale);
143
+ if (barIndex > prevX) {
144
+ const leftBarHeight = Math.round(prevLeft * halfHeight);
145
+ const rightBarHeight = Math.round(prevRight * halfHeight);
146
+ ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + rightBarHeight || 1, barRadius);
147
+ prevX = barIndex;
148
+ prevLeft = 0;
149
+ prevRight = 0;
150
+ }
151
+ const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
152
+ const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
153
+ if (leftValue > prevLeft) {
154
+ prevLeft = leftValue;
155
+ }
156
+ // If stereo, both channels are drawn as max values
157
+ // If mono with negative values, the bottom channel will be the min negative values
158
+ if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
159
+ prevRight = rightValue < 0 ? -rightValue : rightValue;
158
160
  }
159
- ctx.fill();
160
- ctx.closePath();
161
- };
162
- // Clear the canvas
163
- ctx.clearRect(0, 0, width, height);
164
- // Draw the currently visible part of the waveform
165
- const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
166
- const scale = len / scrollWidth;
167
- const start = Math.floor(scrollLeft * scale);
168
- const end = Math.ceil((scrollLeft + clientWidth) * scale);
169
- draw(start, end);
170
- // Draw the progress mask
171
- this.createProgressMask();
172
- // Draw the rest of the waveform with a timeout for better performance
173
- if (start > 0) {
174
- yield this.delay(() => {
175
- draw(0, start);
176
- });
177
- }
178
- if (end < len) {
179
- yield this.delay(() => {
180
- draw(end, len);
181
- });
182
161
  }
183
- // Redraw the progress mask
184
- this.delay(() => {
185
- this.createProgressMask();
162
+ ctx.fill();
163
+ ctx.closePath();
164
+ // Draw a progress canvas
165
+ const progressCanvas = canvas.cloneNode();
166
+ this.progressWrapper.appendChild(progressCanvas);
167
+ const progressCtx = progressCanvas.getContext('2d', {
168
+ desynchronized: true,
186
169
  });
187
- });
188
- }
189
- createProgressMask() {
190
- const progressCtx = this.progressCanvas.getContext('2d', { desynchronized: true });
191
- // Set the canvas to the same size as the main canvas
192
- this.progressCanvas.width = this.mainCanvas.width;
193
- this.progressCanvas.height = this.mainCanvas.height;
194
- // Copy the waveform image to the progress canvas
195
- // The main canvas itself is used as the source image
196
- progressCtx.drawImage(this.mainCanvas, 0, 0);
197
- // Set the composition method to draw only where the waveform is drawn
198
- progressCtx.globalCompositeOperation = 'source-in';
199
- progressCtx.fillStyle = this.options.progressColor;
200
- // This rectangle acts as a mask thanks to the composition method
201
- progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
170
+ progressCtx.drawImage(canvas, 0, 0);
171
+ // Set the composition method to draw only where the waveform is drawn
172
+ progressCtx.globalCompositeOperation = 'source-in';
173
+ progressCtx.fillStyle = this.options.progressColor;
174
+ // This rectangle acts as a mask thanks to the composition method
175
+ progressCtx.fillRect(0, 0, canvas.width, canvas.height);
176
+ };
177
+ // Clear the canvas
178
+ this.canvasWrapper.innerHTML = '';
179
+ this.progressWrapper.innerHTML = '';
180
+ // Draw the currently visible part of the waveform
181
+ const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
182
+ const scale = len / scrollWidth;
183
+ const start = Math.floor(scrollLeft * scale);
184
+ const end = Math.ceil(Math.min(scrollWidth, scrollLeft + clientWidth) * scale);
185
+ draw(start, end);
186
+ // Draw the rest of the waveform with a timeout for better performance
187
+ const step = end - start;
188
+ for (let i = end; i < len; i += step) {
189
+ await this.delay(() => {
190
+ draw(i, Math.min(len, i + step));
191
+ });
192
+ }
193
+ for (let i = start - 1; i >= 0; i -= step) {
194
+ await this.delay(() => {
195
+ draw(Math.max(0, i - step), i);
196
+ });
197
+ }
202
198
  }
203
199
  render(audioData) {
204
200
  // Determine the width of the canvas
205
- const { devicePixelRatio } = window;
206
- const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth * devicePixelRatio : 0;
201
+ const pixelRatio = window.devicePixelRatio || 1;
202
+ const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth * pixelRatio : 0;
207
203
  const scrollWidth = audioData.duration * this.options.minPxPerSec;
208
204
  const isScrolling = scrollWidth > parentWidth;
209
205
  const width = Math.max(1, isScrolling ? scrollWidth : parentWidth);
210
206
  const { height } = this.options;
211
- this.mainCanvas.width = width;
212
- this.mainCanvas.height = height;
213
- this.mainCanvas.style.width = Math.floor(scrollWidth / devicePixelRatio) + 'px';
207
+ // Remember the current cursor position
208
+ const oldCursorPosition = this.progressWrapper.clientWidth;
209
+ this.scrollContainer.style.overflowX = isScrolling ? 'auto' : 'hidden';
210
+ this.wrapper.style.width = `${Math.floor(width / pixelRatio)}px`;
211
+ // Adjust the scroll position so that the cursor stays in the same place
212
+ const newCursortPosition = this.progressWrapper.clientWidth;
213
+ this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
214
214
  // First two channels are used
215
215
  const channelData = [audioData.getChannelData(0)];
216
216
  if (audioData.numberOfChannels > 1) {
217
217
  channelData.push(audioData.getChannelData(1));
218
218
  }
219
- this.renderPeaks(channelData, width, height);
219
+ this.renderPeaks(channelData, width, height, pixelRatio);
220
220
  }
221
221
  zoom(audioData, minPxPerSec) {
222
- // Remember the current cursor position
223
- const oldCursorPosition = this.cursor.getBoundingClientRect().left;
224
222
  this.options.minPxPerSec = minPxPerSec;
225
223
  this.render(audioData);
226
- // Adjust the scroll position so that the cursor stays in the same place
227
- const newCursortPosition = this.cursor.getBoundingClientRect().left;
228
- this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
229
224
  }
230
225
  renderProgress(progress, autoCenter = false) {
231
- this.progressCanvas.style.clipPath = `inset(0 ${100 - progress * 100}% 0 0)`;
232
- this.cursor.style.left = `${progress * 100}%`;
233
- this.cursor.style.marginLeft =
234
- progress > 0
235
- ? Math.round(progress * 100) === 100
236
- ? `-${this.cursor.offsetWidth}px`
237
- : `-${this.cursor.offsetWidth / 2}px`
238
- : '';
239
- this.cursor.scrollIntoView();
240
- if (autoCenter) {
241
- const center = this.container.clientWidth / 2;
242
- const fullWidth = this.mainCanvas.clientWidth;
243
- if (fullWidth * progress >= center) {
244
- this.scrollContainer.scrollLeft = fullWidth * progress - center;
245
- }
226
+ this.progressWrapper.style.width = `${progress * 100}%`;
227
+ const containerWidth = this.scrollContainer.clientWidth;
228
+ const center = containerWidth / 2;
229
+ const progressWidth = this.progressWrapper.clientWidth;
230
+ const minScroll = autoCenter ? center : containerWidth;
231
+ if (progressWidth > this.scrollContainer.scrollLeft + minScroll) {
232
+ this.scrollContainer.scrollLeft = progressWidth - center;
246
233
  }
247
234
  }
248
235
  }
package/dist/timer.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import EventEmitter from './event-emitter.js';
2
2
  class Timer extends EventEmitter {
3
- constructor() {
4
- super(...arguments);
5
- this.unsubscribe = () => undefined;
6
- }
3
+ unsubscribe = () => undefined;
7
4
  start() {
8
5
  this.unsubscribe = this.on('tick', () => {
9
6
  requestAnimationFrame(() => {