wavesurfer.js 5.0.0 → 6.0.0

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.
Files changed (66) hide show
  1. package/CHANGES.md +48 -0
  2. package/README.md +10 -0
  3. package/dist/plugin/wavesurfer.cursor.js +53 -51
  4. package/dist/plugin/wavesurfer.cursor.js.map +1 -1
  5. package/dist/plugin/wavesurfer.cursor.min.js +2 -2
  6. package/dist/plugin/wavesurfer.cursor.min.js.map +1 -1
  7. package/dist/plugin/wavesurfer.elan.js +10 -7
  8. package/dist/plugin/wavesurfer.elan.js.map +1 -1
  9. package/dist/plugin/wavesurfer.elan.min.js +2 -2
  10. package/dist/plugin/wavesurfer.elan.min.js.map +1 -1
  11. package/dist/plugin/wavesurfer.markers.js +151 -25
  12. package/dist/plugin/wavesurfer.markers.js.map +1 -1
  13. package/dist/plugin/wavesurfer.markers.min.js +2 -2
  14. package/dist/plugin/wavesurfer.markers.min.js.map +1 -1
  15. package/dist/plugin/wavesurfer.mediasession.js +6 -6
  16. package/dist/plugin/wavesurfer.mediasession.js.map +1 -1
  17. package/dist/plugin/wavesurfer.mediasession.min.js +2 -2
  18. package/dist/plugin/wavesurfer.mediasession.min.js.map +1 -1
  19. package/dist/plugin/wavesurfer.microphone.js +5 -5
  20. package/dist/plugin/wavesurfer.microphone.js.map +1 -1
  21. package/dist/plugin/wavesurfer.microphone.min.js +2 -2
  22. package/dist/plugin/wavesurfer.microphone.min.js.map +1 -1
  23. package/dist/plugin/wavesurfer.minimap.js +26 -23
  24. package/dist/plugin/wavesurfer.minimap.js.map +1 -1
  25. package/dist/plugin/wavesurfer.minimap.min.js +2 -2
  26. package/dist/plugin/wavesurfer.minimap.min.js.map +1 -1
  27. package/dist/plugin/wavesurfer.playhead.js +5 -5
  28. package/dist/plugin/wavesurfer.playhead.js.map +1 -1
  29. package/dist/plugin/wavesurfer.playhead.min.js +2 -2
  30. package/dist/plugin/wavesurfer.playhead.min.js.map +1 -1
  31. package/dist/plugin/wavesurfer.regions.js +92 -23
  32. package/dist/plugin/wavesurfer.regions.js.map +1 -1
  33. package/dist/plugin/wavesurfer.regions.min.js +2 -2
  34. package/dist/plugin/wavesurfer.regions.min.js.map +1 -1
  35. package/dist/plugin/wavesurfer.spectrogram.js +104 -85
  36. package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
  37. package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
  38. package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
  39. package/dist/plugin/wavesurfer.timeline.js +67 -57
  40. package/dist/plugin/wavesurfer.timeline.js.map +1 -1
  41. package/dist/plugin/wavesurfer.timeline.min.js +2 -2
  42. package/dist/plugin/wavesurfer.timeline.min.js.map +1 -1
  43. package/dist/wavesurfer-html-init.js +6 -6
  44. package/dist/wavesurfer-html-init.js.map +1 -1
  45. package/dist/wavesurfer-html-init.min.js +2 -2
  46. package/dist/wavesurfer-html-init.min.js.map +1 -1
  47. package/dist/wavesurfer.js +300 -134
  48. package/dist/wavesurfer.js.map +1 -1
  49. package/dist/wavesurfer.min.js +2 -2
  50. package/dist/wavesurfer.min.js.map +1 -1
  51. package/package.json +24 -24
  52. package/src/drawer.canvasentry.js +30 -6
  53. package/src/drawer.js +2 -1
  54. package/src/drawer.multicanvas.js +19 -8
  55. package/src/mediaelement.js +1 -1
  56. package/src/plugin/cursor/index.js +68 -70
  57. package/src/plugin/markers/index.js +120 -23
  58. package/src/plugin/minimap/index.js +24 -21
  59. package/src/plugin/regions/index.js +30 -3
  60. package/src/plugin/regions/region.js +47 -10
  61. package/src/plugin/spectrogram/index.js +101 -82
  62. package/src/plugin/timeline/index.js +30 -19
  63. package/src/util/index.js +1 -0
  64. package/src/util/silence-mode.js +35 -0
  65. package/src/wavesurfer.js +54 -23
  66. package/src/webaudio.js +10 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesurfer.js",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Interactive navigable audio visualization using Web Audio and Canvas",
5
5
  "main": "dist/wavesurfer.js",
6
6
  "directories": {
@@ -55,17 +55,17 @@
55
55
  "visualization"
56
56
  ],
57
57
  "devDependencies": {
58
- "@babel/core": "^7.13.16",
59
- "@babel/preset-env": "^7.13.15",
60
- "@babel/register": "^7.13.16",
61
- "@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
62
- "babel-eslint": "^10.1.0",
63
- "babel-loader": "^8.2.2",
58
+ "@babel/core": "^7.17.0",
59
+ "@babel/eslint-parser": "^7.17.0",
60
+ "@babel/plugin-proposal-class-properties": "^7.16.7",
61
+ "@babel/preset-env": "^7.16.11",
62
+ "@babel/register": "^7.17.0",
63
+ "@chiragrupani/karma-chromium-edge-launcher": "^2.2.1",
64
+ "babel-loader": "^8.2.3",
64
65
  "babel-plugin-add-module-exports": "^1.0.4",
65
- "babel-plugin-istanbul": "^6.0.0",
66
+ "babel-plugin-istanbul": "^6.1.1",
66
67
  "babel-plugin-proxy": "^1.1.0",
67
- "babel-plugin-transform-class-properties": "^6.24.1",
68
- "date-fns": "^2.21.1",
68
+ "date-fns": "^2.28.0",
69
69
  "debounce": "^1.2.1",
70
70
  "esdoc": "^1.1.0",
71
71
  "esdoc-accessor-plugin": "^1.0.0",
@@ -74,25 +74,25 @@
74
74
  "esdoc-ecmascript-proposal-plugin": "^1.0.0",
75
75
  "esdoc-integrate-test-plugin": "^1.0.0",
76
76
  "esdoc-standard-plugin": "^1.0.0",
77
- "eslint": "^7.24.0",
78
- "htmlhint": "^0.14.2",
77
+ "eslint": "^8.8.0",
78
+ "htmlhint": "^1.1.2",
79
79
  "in-publish": "^2.0.1",
80
- "jasmine-core": "^3.7.1",
81
- "karma": "^5.2.3",
80
+ "jasmine-core": "^4.0.0",
81
+ "karma": "^6.3.15",
82
82
  "karma-chrome-launcher": "3.1.0",
83
- "karma-coverage": "^2.0.3",
84
- "karma-firefox-launcher": "^2.1.0",
83
+ "karma-coverage": "^2.1.1",
84
+ "karma-firefox-launcher": "^2.1.2",
85
85
  "karma-jasmine": "4.0.1",
86
- "karma-jasmine-matchers": "4.0.2",
87
- "karma-verbose-reporter": "0.0.6",
86
+ "karma-jasmine-matchers": "5.0.0",
87
+ "karma-verbose-reporter": "0.0.8",
88
88
  "karma-webpack": "^5.0.0",
89
- "lint-staged": "^10.5.4",
90
- "load-script": "^1.0.0",
89
+ "lint-staged": "^12.3.3",
90
+ "load-script": "^2.0.0",
91
91
  "pre-commit": "^1.2.2",
92
- "webpack": "^5.35.0",
93
- "webpack-cli": "^4.6.0",
94
- "webpack-dev-server": "^4.0.0-beta.2",
95
- "webpack-merge": "^5.7.3"
92
+ "webpack": "^5.68.0",
93
+ "webpack-cli": "^4.9.2",
94
+ "webpack-dev-server": "^4.7.4",
95
+ "webpack-merge": "^5.8.0"
96
96
  },
97
97
  "homepage": "https://wavesurfer-js.org",
98
98
  "pre-commit": "lint-js-html",
@@ -145,18 +145,42 @@ export default class CanvasEntry {
145
145
 
146
146
  /**
147
147
  * Set the fill styles for wave and progress
148
- *
149
- * @param {string} waveColor Fill color for the wave canvas
150
- * @param {?string} progressColor Fill color for the progress canvas
148
+ * @param {string|string[]} waveColor Fill color for the wave canvas,
149
+ * or an array of colors to apply as a gradient
150
+ * @param {?string|string[]} progressColor Fill color for the progress canvas,
151
+ * or an array of colors to apply as a gradient
151
152
  */
152
153
  setFillStyles(waveColor, progressColor) {
153
- this.waveCtx.fillStyle = waveColor;
154
+ this.waveCtx.fillStyle = this.getFillStyle(this.waveCtx, waveColor);
154
155
 
155
156
  if (this.hasProgressCanvas) {
156
- this.progressCtx.fillStyle = progressColor;
157
+ this.progressCtx.fillStyle = this.getFillStyle(this.progressCtx, progressColor);
157
158
  }
158
159
  }
159
160
 
161
+ /**
162
+ * Utility function to handle wave color arguments
163
+ *
164
+ * When the color argument type is a string, it will be returned as is.
165
+ * Otherwise, it will be treated as an array, and a canvas gradient will
166
+ * be returned
167
+ *
168
+ * @since 5.3.0
169
+ * @param {CanvasRenderingContext2D} ctx Rendering context of target canvas
170
+ * @param {string|string[]} color Fill color for the wave canvas, or an array of colors to apply as a gradient
171
+ * @returns {string|CanvasGradient} Returns a string fillstyle value, or a canvas gradient
172
+ */
173
+ getFillStyle(ctx, color) {
174
+ if (typeof color == 'string') {
175
+ return color;
176
+ }
177
+
178
+ const waveGradient = ctx.createLinearGradient(0, 0, 0, ctx.canvas.height);
179
+ color.forEach((value, index) => waveGradient.addColorStop((index / color.length), value));
180
+
181
+ return waveGradient;
182
+ }
183
+
160
184
  /**
161
185
  * Set the canvas transforms for wave and progress
162
186
  *
@@ -357,7 +381,7 @@ export default class CanvasEntry {
357
381
  ctx.lineTo(
358
382
  (canvasStart - first) * scale,
359
383
  halfOffset -
360
- Math.round((peaks[2 * canvasStart + 1] || 0) / absmaxHalf)
384
+ Math.round((peaks[2 * canvasStart + 1] || 0) / absmaxHalf)
361
385
  );
362
386
 
363
387
  ctx.closePath();
package/src/drawer.js CHANGED
@@ -69,6 +69,7 @@ export default class Drawer extends util.Observer {
69
69
  if (this.params.fillParent || this.params.scrollParent) {
70
70
  this.style(this.wrapper, {
71
71
  width: '100%',
72
+ cursor: this.params.hideCursor ? 'none' : 'auto',
72
73
  overflowX: this.params.hideScrollbar ? 'hidden' : 'auto',
73
74
  overflowY: 'hidden'
74
75
  });
@@ -342,7 +343,7 @@ export default class Drawer extends util.Observer {
342
343
  destroy() {
343
344
  this.unAll();
344
345
  if (this.wrapper) {
345
- if (this.wrapper.parentNode == this.container) {
346
+ if (this.wrapper.parentNode == this.container.domElement) {
346
347
  this.container.removeChild(this.wrapper.domElement);
347
348
  }
348
349
  this.wrapper = null;
@@ -305,21 +305,32 @@ export default class MultiCanvas extends Drawer {
305
305
  const scale = length / this.width;
306
306
  const first = start;
307
307
  const last = end;
308
- let i = first;
309
-
310
- for (i; i < last; i += step) {
311
- const peak =
312
- peaks[Math.floor(i * scale * peakIndexScale)] || 0;
308
+ let peakIndex = first;
309
+ for (peakIndex; peakIndex < last; peakIndex += step) {
310
+
311
+ // search for the highest peak in the range this bar falls into
312
+ let peak = 0;
313
+ let peakIndexRange = Math.floor(peakIndex * scale) * peakIndexScale; // start index
314
+ const peakIndexEnd = Math.floor((peakIndex + step) * scale) * peakIndexScale;
315
+ do { // do..while makes sure at least one peak is always evaluated
316
+ const newPeak = peaks[peakIndexRange];
317
+ if (newPeak > peak) {
318
+ peak = newPeak; // higher
319
+ }
320
+ peakIndexRange += peakIndexScale; // skip every other value for negatives
321
+ } while (peakIndexRange < peakIndexEnd);
322
+
323
+ // calculate the height of this bar according to the highest peak found
313
324
  let h = Math.round((peak / absmax) * halfH);
314
325
 
315
- /* in case of silences, allow the user to specify that we
316
- * always draw *something* (normally a 1px high bar) */
326
+ // in case of silences, allow the user to specify that we
327
+ // always draw *something* (normally a 1px high bar)
317
328
  if (h == 0 && this.params.barMinHeight) {
318
329
  h = this.params.barMinHeight;
319
330
  }
320
331
 
321
332
  this.fillRect(
322
- i + this.halfPixel,
333
+ peakIndex + this.halfPixel,
323
334
  halfH - h + offsetY,
324
335
  bar + this.halfPixel,
325
336
  h * 2,
@@ -275,7 +275,7 @@ export default class MediaElement extends WebAudio {
275
275
  * @param {number} start Position to start at in seconds
276
276
  */
277
277
  seekTo(start) {
278
- if (start != null) {
278
+ if (start != null && !isNaN(start)) {
279
279
  this.media.currentTime = start;
280
280
  }
281
281
  this.clearPlayEnd();
@@ -86,14 +86,15 @@ export default class CursorPlugin {
86
86
  * @param {object} e Mouse move event
87
87
  */
88
88
  _onMousemove = e => {
89
- const bbox = this.wavesurfer.container.getBoundingClientRect();
89
+ const event = this.util.withOrientation(e, this.wavesurfer.params.vertical);
90
+ const bbox = this.wrapper.getBoundingClientRect();
90
91
  let y = 0;
91
- let x = e.clientX - bbox.left;
92
- let flip = bbox.right < e.clientX + this.outerWidth(this.displayTime);
92
+ let x = this.wrapper.scrollLeft + event.clientX - bbox.left;
93
+ let flip = bbox.right < event.clientX + this.displayTime.getBoundingClientRect().width;
93
94
 
94
95
  if (this.params.showTime && this.params.followCursorY) {
95
96
  // follow y-position of the mouse
96
- y = e.clientY - (bbox.top + bbox.height / 2);
97
+ y = event.clientY - (bbox.top + bbox.height / 2);
97
98
  }
98
99
 
99
100
  this.updateCursorPosition(x, y, flip);
@@ -119,6 +120,7 @@ export default class CursorPlugin {
119
120
  constructor(params, ws) {
120
121
  this.wavesurfer = ws;
121
122
  this.style = ws.util.style;
123
+ this.util = ws.util;
122
124
  /**
123
125
  * The cursor HTML element
124
126
  *
@@ -141,14 +143,38 @@ export default class CursorPlugin {
141
143
  this.params = Object.assign({}, this.defaultParams, params);
142
144
  }
143
145
 
144
- /**
145
- * Initialise the plugin (used by the Plugin API)
146
- */
147
- init() {
148
- this.wrapper = this.wavesurfer.container;
149
- this.cursor = this.wrapper.appendChild(
146
+ _onReady() {
147
+ this.wrapper = this.wavesurfer.drawer.wrapper;
148
+ this.cursor = this.util.withOrientation(this.wrapper.appendChild(
149
+ document.createElement('cursor'),
150
+ ), this.wavesurfer.params.vertical);
151
+
152
+ this.style(this.cursor,
153
+ Object.assign(
154
+ {
155
+ position: 'absolute',
156
+ zIndex: this.params.zIndex,
157
+ left: 0,
158
+ top: 0,
159
+ bottom: 0,
160
+ width: '0',
161
+ display: 'flex',
162
+ borderRightStyle: this.params.style,
163
+ borderRightWidth: this.params.width,
164
+ borderRightColor: this.params.color,
165
+ opacity: this.params.opacity,
166
+ pointerEvents: 'none'
167
+ },
168
+ this.params.customStyle
169
+ )
170
+ );
171
+
172
+ if (this.params.showTime) {
173
+ this.showTime = this.util.withOrientation(this.wrapper.appendChild(
174
+ document.createElement('showTitle')
175
+ ), this.wavesurfer.params.vertical);
150
176
  this.style(
151
- document.createElement('cursor'),
177
+ this.showTime,
152
178
  Object.assign(
153
179
  {
154
180
  position: 'absolute',
@@ -156,53 +182,32 @@ export default class CursorPlugin {
156
182
  left: 0,
157
183
  top: 0,
158
184
  bottom: 0,
159
- width: '0',
185
+ width: 'auto',
160
186
  display: 'flex',
161
- borderRightStyle: this.params.style,
162
- borderRightWidth: this.params.width,
163
- borderRightColor: this.params.color,
164
187
  opacity: this.params.opacity,
165
- pointerEvents: 'none'
188
+ pointerEvents: 'none',
189
+ height: '100%'
166
190
  },
167
191
  this.params.customStyle
168
192
  )
169
- )
170
- );
171
- if (this.params.showTime) {
172
- this.showTime = this.wrapper.appendChild(
173
- this.style(
174
- document.createElement('showTitle'),
175
- Object.assign(
176
- {
177
- position: 'absolute',
178
- zIndex: this.params.zIndex,
179
- left: 0,
180
- top: 0,
181
- bottom: 0,
182
- width: 'auto',
183
- display: 'flex',
184
- opacity: this.params.opacity,
185
- pointerEvents: 'none',
186
- height: '100%'
187
- },
188
- this.params.customStyle
189
- )
190
- )
191
193
  );
192
- this.displayTime = this.showTime.appendChild(
193
- this.style(
194
- document.createElement('div'),
195
- Object.assign(
196
- {
197
- display: 'inline',
198
- pointerEvents: 'none',
199
- margin: 'auto',
200
- visibility: 'hidden' // initial value will be hidden just for measuring purpose
201
- },
202
- this.params.customShowTimeStyle
203
- )
194
+
195
+ this.displayTime = this.util.withOrientation(this.showTime.appendChild(
196
+ document.createElement('div'),
197
+ ), this.wavesurfer.params.vertical);
198
+
199
+ this.style(this.displayTime,
200
+ Object.assign(
201
+ {
202
+ display: 'inline',
203
+ pointerEvents: 'none',
204
+ margin: 'auto',
205
+ visibility: 'hidden' // initial value will be hidden just for measuring purpose
206
+ },
207
+ this.params.customShowTimeStyle
204
208
  )
205
209
  );
210
+
206
211
  // initial value to measure display width
207
212
  this.displayTime.innerHTML = this.formatTime(0);
208
213
  }
@@ -216,6 +221,17 @@ export default class CursorPlugin {
216
221
  }
217
222
  }
218
223
 
224
+ /**
225
+ * Initialise the plugin (used by the Plugin API)
226
+ */
227
+ init() {
228
+ if (this.wavesurfer.isReady) {
229
+ this._onReady();
230
+ } else {
231
+ this.wavesurfer.once('ready', () => this._onReady());
232
+ }
233
+ }
234
+
219
235
  /**
220
236
  * Destroy the plugin (used by the Plugin API)
221
237
  */
@@ -253,10 +269,10 @@ export default class CursorPlugin {
253
269
  (duration / this.wavesurfer.drawer.width) * scrollWidth;
254
270
 
255
271
  const timeValue =
256
- Math.max(0, (xpos / elementWidth) * duration) + scrollTime;
272
+ Math.max(0, ((xpos - this.wrapper.scrollLeft) / elementWidth) * duration) + scrollTime;
257
273
  const formatValue = this.formatTime(timeValue);
258
274
  if (flip) {
259
- const textOffset = this.outerWidth(this.displayTime);
275
+ const textOffset = this.displayTime.getBoundingClientRect().width;
260
276
  xpos -= textOffset;
261
277
  }
262
278
  this.style(this.showTime, {
@@ -318,22 +334,4 @@ export default class CursorPlugin {
318
334
  ].join(':')
319
335
  );
320
336
  }
321
-
322
- /**
323
- * Get outer width of given element.
324
- *
325
- * @param {DOM} element DOM Element
326
- * @returns {number} outer width
327
- */
328
- outerWidth(element) {
329
- if (!element) {
330
- return 0;
331
- }
332
-
333
- let width = element.offsetWidth;
334
- let style = getComputedStyle(element);
335
-
336
- width += parseInt(style.marginLeft + style.marginRight);
337
- return width;
338
- }
339
337
  }
@@ -1,12 +1,13 @@
1
-
2
1
  /**
3
2
  * @typedef {Object} MarkerParams
4
3
  * @desc The parameters used to describe a marker.
5
4
  * @example wavesurfer.addMarker(regionParams);
6
5
  * @property {number} time The time to set the marker at
7
- * @property {?label} string an optional marker label
8
- * @property {?color} string background color for marker
6
+ * @property {?label} string An optional marker label
7
+ * @property {?color} string Background color for marker
9
8
  * @property {?position} string "top" or "bottom", defaults to "bottom"
9
+ * @property {?markerElement} element An HTML element to display instead of the default marker image
10
+ * @property {?draggable} boolean Set marker as draggable, defaults to false
10
11
  */
11
12
 
12
13
 
@@ -39,6 +40,9 @@ export default class MarkersPlugin {
39
40
  /**
40
41
  * @typedef {Object} MarkersPluginParams
41
42
  * @property {?MarkerParams[]} markers Initial set of markers
43
+ * @fires MarkersPlugin#marker-click
44
+ * @fires MarkersPlugin#marker-drag
45
+ * @fires MarkersPlugin#marker-drop
42
46
  */
43
47
 
44
48
  /**
@@ -76,8 +80,10 @@ export default class MarkersPlugin {
76
80
  this.wavesurfer = ws;
77
81
  this.util = ws.util;
78
82
  this.style = this.util.style;
83
+ this.markerLineWidth = 1;
79
84
  this.markerWidth = 11;
80
85
  this.markerHeight = 22;
86
+ this.dragging = false;
81
87
 
82
88
 
83
89
  this._onResize = () => {
@@ -92,6 +98,16 @@ export default class MarkersPlugin {
92
98
  window.addEventListener('resize', this._onResize, true);
93
99
  window.addEventListener('orientationchange', this._onResize, true);
94
100
  this.wavesurfer.on('zoom', this._onResize);
101
+
102
+ if (!this.markers.find(marker => marker.draggable)){
103
+ return;
104
+ }
105
+
106
+ this.onMouseMove = (e) => this._onMouseMove(e);
107
+ window.addEventListener('mousemove', this.onMouseMove);
108
+
109
+ this.onMouseUp = (e) => this._onMouseUp(e);
110
+ window.addEventListener("mouseup", this.onMouseUp);
95
111
  };
96
112
 
97
113
  this.markers = [];
@@ -121,6 +137,13 @@ export default class MarkersPlugin {
121
137
  window.removeEventListener('resize', this._onResize, true);
122
138
  window.removeEventListener('orientationchange', this._onResize, true);
123
139
 
140
+ if (this.onMouseMove) {
141
+ window.removeEventListener('mousemove', this.onMouseMove);
142
+ }
143
+ if (this.onMouseUp) {
144
+ window.removeEventListener("mouseup", this.onMouseUp);
145
+ }
146
+
124
147
  this.clear();
125
148
  }
126
149
 
@@ -135,10 +158,11 @@ export default class MarkersPlugin {
135
158
  time: params.time,
136
159
  label: params.label,
137
160
  color: params.color || DEFAULT_FILL_COLOR,
138
- position: params.position || DEFAULT_POSITION
161
+ position: params.position || DEFAULT_POSITION,
162
+ draggable: !!params.draggable
139
163
  };
140
164
 
141
- marker.el = this._createMarkerElement(marker);
165
+ marker.el = this._createMarkerElement(marker, params.markerElement);
142
166
 
143
167
  this.wrapper.appendChild(marker.el);
144
168
  this.markers.push(marker);
@@ -190,9 +214,8 @@ export default class MarkersPlugin {
190
214
  return el;
191
215
  }
192
216
 
193
- _createMarkerElement(marker) {
217
+ _createMarkerElement(marker, markerElement) {
194
218
  let label = marker.label;
195
- let time = marker.time;
196
219
 
197
220
  const el = document.createElement('marker');
198
221
  el.className = "wavesurfer-marker";
@@ -206,17 +229,22 @@ export default class MarkersPlugin {
206
229
  });
207
230
 
208
231
  const line = document.createElement('div');
232
+ const width = markerElement ? markerElement.width : this.markerWidth;
233
+ marker.offset = (width - this.markerLineWidth) / 2;
209
234
  this.style(line, {
210
235
  "flex-grow": 1,
211
- "margin-left": (this.markerWidth / 2 - 0.5) + "px",
236
+ "margin-left": marker.offset + "px",
212
237
  background: "black",
213
- width: "1px",
238
+ width: this.markerLineWidth + "px",
214
239
  opacity: 0.1
215
240
  });
216
241
  el.appendChild(line);
217
242
 
218
243
  const labelDiv = document.createElement('div');
219
- const point = this._createPointerSVG(marker.color, marker.position);
244
+ const point = markerElement || this._createPointerSVG(marker.color, marker.position);
245
+ if (marker.draggable){
246
+ point.draggable = false;
247
+ }
220
248
  labelDiv.appendChild(point);
221
249
 
222
250
  if ( label ) {
@@ -239,30 +267,99 @@ export default class MarkersPlugin {
239
267
 
240
268
  labelDiv.addEventListener("click", e => {
241
269
  e.stopPropagation();
242
- this.wavesurfer.setCurrentTime(time);
270
+ // Click event is caught when the marker-drop event was dispatched.
271
+ // Drop event was dispatched at this moment, but this.dragging
272
+ // is waiting for the next tick to set as false
273
+ if (this.dragging){
274
+ return;
275
+ }
276
+ this.wavesurfer.setCurrentTime(marker.time);
277
+ this.wavesurfer.fireEvent("marker-click", marker, e);
243
278
  });
244
279
 
280
+ if (marker.draggable) {
281
+ labelDiv.addEventListener("mousedown", e => {
282
+ this.selectedMarker = marker;
283
+ });
284
+ }
245
285
  return el;
246
286
  }
247
287
 
248
288
  _updateMarkerPositions() {
249
- const duration = this.wavesurfer.getDuration();
250
-
251
289
  for ( let i = 0 ; i < this.markers.length; i++ ) {
252
290
  let marker = this.markers[i];
253
- const elementWidth =
254
- this.wavesurfer.drawer.width /
255
- this.wavesurfer.params.pixelRatio;
256
-
257
- const positionPct = Math.min(marker.time / duration, 1);
258
- const leftPx = ((elementWidth * positionPct) - (this.markerWidth / 2));
259
- this.style(marker.el, {
260
- "left": leftPx + "px",
261
- "max-width": (elementWidth - leftPx) + "px"
262
- });
291
+ this._updateMarkerPosition(marker);
263
292
  }
264
293
  }
265
294
 
295
+ /**
296
+ * Update a marker position based on its time property.
297
+ *
298
+ * @private
299
+ * @param {MarkerParams} params The marker to update.
300
+ * @returns {void}
301
+ */
302
+ _updateMarkerPosition(params) {
303
+ const duration = this.wavesurfer.getDuration();
304
+ const elementWidth =
305
+ this.wavesurfer.drawer.width /
306
+ this.wavesurfer.params.pixelRatio;
307
+
308
+ const positionPct = Math.min(params.time / duration, 1);
309
+ const leftPx = ((elementWidth * positionPct) - params.offset);
310
+ this.style(params.el, {
311
+ "left": leftPx + "px",
312
+ "max-width": (elementWidth - leftPx) + "px"
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Fires `marker-drag` event, update the `time` property for the
318
+ * selected marker based on the mouse position, and calls to update
319
+ * its position.
320
+ *
321
+ * @private
322
+ * @param {MouseEvent} event The mouse event.
323
+ * @returns {void}
324
+ */
325
+ _onMouseMove(event) {
326
+ if (!this.selectedMarker){
327
+ return;
328
+ }
329
+ if (!this.dragging){
330
+ this.dragging = true;
331
+ this.wavesurfer.fireEvent("marker-drag", this.selectedMarker, event);
332
+ }
333
+ this.selectedMarker.time = this.wavesurfer.drawer.handleEvent(event) * this.wavesurfer.getDuration();
334
+ this._updateMarkerPositions();
335
+ }
336
+
337
+ /**
338
+ * Fires `marker-drop` event and unselect the dragged marker.
339
+ *
340
+ * @private
341
+ * @param {MouseEvent} event The mouse event.
342
+ * @returns {void}
343
+ */
344
+ _onMouseUp(event) {
345
+ if (this.selectedMarker) {
346
+ setTimeout(() => {
347
+ this.selectedMarker = false;
348
+ this.dragging = false;
349
+ }, 0);
350
+ }
351
+
352
+ if (!this.dragging) {
353
+ return;
354
+ }
355
+
356
+ event.stopPropagation();
357
+ const duration = this.wavesurfer.getDuration();
358
+ this.selectedMarker.time = this.wavesurfer.drawer.handleEvent(event) * duration;
359
+ this._updateMarkerPositions();
360
+ this.wavesurfer.fireEvent("marker-drop", this.selectedMarker, event);
361
+ }
362
+
266
363
  /**
267
364
  * Remove all markers
268
365
  */