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.
@@ -1,8 +1,8 @@
1
1
  import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
2
- import type { WaveSurfer, WaveSurferPluginParams } from './index.js';
2
+ import WaveSurfer, { type WaveSurferPluginParams } from './index.js';
3
3
  export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
4
4
  protected wavesurfer: WaveSurfer;
5
- protected container: ShadowRoot | HTMLElement;
5
+ protected container: HTMLElement;
6
6
  protected wrapper: HTMLElement;
7
7
  protected subscriptions: (() => void)[];
8
8
  protected options: Options;
@@ -1,8 +1,12 @@
1
1
  import EventEmitter from './event-emitter.js';
2
2
  export class BasePlugin extends EventEmitter {
3
+ wavesurfer;
4
+ container;
5
+ wrapper;
6
+ subscriptions = [];
7
+ options;
3
8
  constructor(params, options) {
4
9
  super();
5
- this.subscriptions = [];
6
10
  this.wavesurfer = params.wavesurfer;
7
11
  this.container = params.container;
8
12
  this.wrapper = params.wrapper;
package/dist/decoder.js CHANGED
@@ -1,13 +1,5 @@
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
  class Decoder {
2
+ audioCtx = null;
11
3
  initAudioContext(sampleRate) {
12
4
  this.audioCtx = new AudioContext({
13
5
  latencyHint: 'playback',
@@ -15,7 +7,6 @@ class Decoder {
15
7
  });
16
8
  }
17
9
  constructor() {
18
- this.audioCtx = null;
19
10
  // Minimum sample rate supported by Web Audio API
20
11
  const DEFAULT_SAMPLE_RATE = 3000; // Chrome, Safari
21
12
  const FALLBACK_SAMPLE_RATE = 8000; // Firefox
@@ -26,17 +17,14 @@ class Decoder {
26
17
  this.initAudioContext(FALLBACK_SAMPLE_RATE);
27
18
  }
28
19
  }
29
- decode(audioData) {
30
- return __awaiter(this, void 0, void 0, function* () {
31
- if (!this.audioCtx) {
32
- throw new Error('AudioContext is not initialized');
33
- }
34
- return this.audioCtx.decodeAudioData(audioData);
35
- });
20
+ async decode(audioData) {
21
+ if (!this.audioCtx) {
22
+ throw new Error('AudioContext is not initialized');
23
+ }
24
+ return this.audioCtx.decodeAudioData(audioData);
36
25
  }
37
26
  destroy() {
38
- var _a;
39
- (_a = this.audioCtx) === null || _a === void 0 ? void 0 : _a.close();
27
+ this.audioCtx?.close();
40
28
  this.audioCtx = null;
41
29
  }
42
30
  }
@@ -1,4 +1,5 @@
1
1
  class EventEmitter {
2
+ eventTarget;
2
3
  constructor() {
3
4
  this.eventTarget = new EventTarget();
4
5
  }
package/dist/fetcher.js CHANGED
@@ -1,17 +1,6 @@
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
  class Fetcher {
11
- load(url) {
12
- return __awaiter(this, void 0, void 0, function* () {
13
- return fetch(url).then((response) => response.arrayBuffer());
14
- });
2
+ async load(url) {
3
+ return fetch(url).then((response) => response.arrayBuffer());
15
4
  }
16
5
  }
17
6
  export default Fetcher;
package/dist/index.d.ts CHANGED
@@ -35,6 +35,8 @@ export type WaveSurferOptions = {
35
35
  autoplay?: boolean;
36
36
  /** Is the waveform clickable? */
37
37
  interactive?: boolean;
38
+ /** Hide scrollbar **/
39
+ noScrollbar?: boolean;
38
40
  };
39
41
  export type WaveSurferEvents = {
40
42
  decode: {
@@ -61,10 +63,10 @@ export type WaveSurferEvents = {
61
63
  };
62
64
  export type WaveSurferPluginParams = {
63
65
  wavesurfer: WaveSurfer;
64
- container: ShadowRoot;
66
+ container: HTMLElement;
65
67
  wrapper: HTMLElement;
66
68
  };
67
- export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
69
+ declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
68
70
  private options;
69
71
  private fetcher;
70
72
  private decoder;
package/dist/index.js CHANGED
@@ -1,12 +1,3 @@
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 Fetcher from './fetcher.js';
11
2
  import Decoder from './decoder.js';
12
3
  import Renderer from './renderer.js';
@@ -22,18 +13,23 @@ const defaultOptions = {
22
13
  fillParent: true,
23
14
  interactive: true,
24
15
  };
25
- export class WaveSurfer extends EventEmitter {
16
+ class WaveSurfer extends EventEmitter {
17
+ options;
18
+ fetcher;
19
+ decoder;
20
+ renderer;
21
+ player;
22
+ timer;
23
+ plugins = [];
24
+ subscriptions = [];
25
+ decodedData = null;
26
26
  /** Create a new WaveSurfer instance */
27
27
  static create(options) {
28
28
  return new WaveSurfer(options);
29
29
  }
30
30
  /** Create a new WaveSurfer instance */
31
31
  constructor(options) {
32
- var _a;
33
32
  super();
34
- this.plugins = [];
35
- this.subscriptions = [];
36
- this.decodedData = null;
37
33
  this.options = Object.assign({}, defaultOptions, options);
38
34
  this.fetcher = new Fetcher();
39
35
  this.decoder = new Decoder();
@@ -54,12 +50,13 @@ export class WaveSurfer extends EventEmitter {
54
50
  barWidth: this.options.barWidth,
55
51
  barGap: this.options.barGap,
56
52
  barRadius: this.options.barRadius,
53
+ noScrollbar: this.options.noScrollbar,
57
54
  });
58
55
  this.initPlayerEvents();
59
56
  this.initRendererEvents();
60
57
  this.initTimerEvents();
61
58
  this.initReadyEvent();
62
- const url = this.options.url || ((_a = this.options.media) === null || _a === void 0 ? void 0 : _a.src);
59
+ const url = this.options.url || this.options.media?.src;
63
60
  if (url) {
64
61
  this.load(url, this.options.peaks, this.options.duration);
65
62
  }
@@ -119,32 +116,32 @@ export class WaveSurfer extends EventEmitter {
119
116
  }));
120
117
  }
121
118
  /** Load an audio file by URL, with optional pre-decoded audio data */
122
- load(url, channelData, duration) {
123
- return __awaiter(this, void 0, void 0, function* () {
124
- this.player.loadUrl(url);
125
- // Fetch and decode the audio of no pre-computed audio data is provided
126
- if (channelData == null) {
127
- const audio = yield this.fetcher.load(url);
128
- const data = yield this.decoder.decode(audio);
129
- this.decodedData = data;
130
- }
131
- else {
132
- if (!duration) {
133
- duration =
134
- (yield new Promise((resolve) => {
135
- this.player.on('canplay', () => resolve(this.getDuration()), { once: true });
136
- })) || 0;
137
- }
138
- this.decodedData = {
139
- duration,
140
- numberOfChannels: channelData.length,
141
- sampleRate: channelData[0].length / duration,
142
- getChannelData: (i) => channelData[i],
143
- };
119
+ async load(url, channelData, duration) {
120
+ this.player.loadUrl(url);
121
+ // Fetch and decode the audio of no pre-computed audio data is provided
122
+ if (channelData == null) {
123
+ const audio = await this.fetcher.load(url);
124
+ const data = await this.decoder.decode(audio);
125
+ this.decodedData = data;
126
+ }
127
+ else {
128
+ if (!duration) {
129
+ duration =
130
+ (await new Promise((resolve) => {
131
+ this.player.on('canplay', () => resolve(this.getDuration()), {
132
+ once: true,
133
+ });
134
+ })) || 0;
144
135
  }
145
- this.renderer.render(this.decodedData);
146
- this.emit('decode', { duration: this.decodedData.duration });
147
- });
136
+ this.decodedData = {
137
+ duration,
138
+ numberOfChannels: channelData.length,
139
+ sampleRate: channelData[0].length / duration,
140
+ getChannelData: (i) => channelData[i],
141
+ };
142
+ }
143
+ this.renderer.render(this.decodedData);
144
+ this.emit('decode', { duration: this.decodedData.duration });
148
145
  }
149
146
  /** Zoom in or out */
150
147
  zoom(minPxPerSec) {
@@ -171,8 +168,7 @@ export class WaveSurfer extends EventEmitter {
171
168
  }
172
169
  /** Get the duration of the audio in seconds */
173
170
  getDuration() {
174
- var _a;
175
- return this.player.getDuration() || ((_a = this.decodedData) === null || _a === void 0 ? void 0 : _a.duration) || 0;
171
+ return this.player.getDuration() || this.decodedData?.duration || 0;
176
172
  }
177
173
  /** Get the current audio position in seconds */
178
174
  getCurrentTime() {
package/dist/player.d.ts CHANGED
@@ -2,6 +2,7 @@ declare class Player {
2
2
  protected media: HTMLMediaElement;
3
3
  private isExternalMedia;
4
4
  private hasPlayedOnce;
5
+ private subscriptions;
5
6
  constructor({ media, autoplay }: {
6
7
  media?: HTMLMediaElement;
7
8
  autoplay?: boolean;
package/dist/player.js CHANGED
@@ -1,7 +1,9 @@
1
1
  class Player {
2
+ media;
3
+ isExternalMedia = false;
4
+ hasPlayedOnce = false;
5
+ subscriptions = [];
2
6
  constructor({ media, autoplay }) {
3
- this.isExternalMedia = false;
4
- this.hasPlayedOnce = false;
5
7
  if (media) {
6
8
  this.media = media;
7
9
  this.isExternalMedia = true;
@@ -9,11 +11,11 @@ class Player {
9
11
  else {
10
12
  this.media = document.createElement('audio');
11
13
  }
14
+ this.subscriptions.push(
12
15
  // Track the first play() call
13
- const unsubscribe = this.on('play', () => {
16
+ this.on('play', () => {
14
17
  this.hasPlayedOnce = true;
15
- unsubscribe();
16
- });
18
+ }, { once: true }));
17
19
  // Autoplay
18
20
  if (autoplay) {
19
21
  this.media.autoplay = true;
@@ -24,6 +26,9 @@ class Player {
24
26
  return () => this.media.removeEventListener(event, callback);
25
27
  }
26
28
  destroy() {
29
+ this.subscriptions.forEach((unsubscribe) => {
30
+ unsubscribe();
31
+ });
27
32
  this.media.pause();
28
33
  if (!this.isExternalMedia) {
29
34
  this.media.remove();
@@ -45,17 +50,13 @@ class Player {
45
50
  return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
46
51
  }
47
52
  seekTo(time) {
48
- if (this.media) {
49
- // iOS Safari requires a play() call before seeking
50
- const { hasPlayedOnce } = this;
51
- if (!hasPlayedOnce) {
52
- this.media.play();
53
- }
54
- this.media.currentTime = time;
55
- if (!hasPlayedOnce) {
56
- this.media.pause();
57
- }
53
+ // iOS Safari requires a play() call before seeking
54
+ if (!this.hasPlayedOnce && navigator.userAgent.match(/(iPad|iPhone|iPod)/g)) {
55
+ this.media.play()?.then?.(() => {
56
+ setTimeout(() => this.media.pause(), 10);
57
+ });
58
58
  }
59
+ this.media.currentTime = time;
59
60
  }
60
61
  getDuration() {
61
62
  return this.media.duration;
@@ -1,13 +1,16 @@
1
1
  import BasePlugin from '../base-plugin.js';
2
- import { WaveSurfer } from '../index.js';
2
+ import WaveSurfer from '../index.js';
3
3
  const defaultOptions = {
4
4
  height: 50,
5
5
  overlayColor: 'rgba(100, 100, 100, 0.1)',
6
6
  };
7
7
  class TimelinePlugin extends BasePlugin {
8
+ options;
9
+ minimapWrapper;
10
+ miniWavesurfer = null;
11
+ overlay;
8
12
  constructor(params, options) {
9
13
  super(params, options);
10
- this.miniWavesurfer = null;
11
14
  this.options = Object.assign({}, defaultOptions, options);
12
15
  this.minimapWrapper = this.initMinimapWrapper();
13
16
  this.overlay = this.initOverlay();
@@ -18,7 +21,7 @@ class TimelinePlugin extends BasePlugin {
18
21
  initMinimapWrapper() {
19
22
  const div = document.createElement('div');
20
23
  div.style.position = 'relative';
21
- this.container.appendChild(div);
24
+ this.container.insertAdjacentElement('afterend', div);
22
25
  return div;
23
26
  }
24
27
  initOverlay() {
@@ -33,7 +36,16 @@ class TimelinePlugin extends BasePlugin {
33
36
  const media = this.wavesurfer.getMediaElement();
34
37
  if (!data || !media)
35
38
  return;
36
- this.miniWavesurfer = WaveSurfer.create(Object.assign(Object.assign({}, this.options), { container: this.minimapWrapper, minPxPerSec: 1, fillParent: true, media, url: media.src, peaks: [data.getChannelData(0)], duration: data.duration }));
39
+ this.miniWavesurfer = WaveSurfer.create({
40
+ ...this.options,
41
+ container: this.minimapWrapper,
42
+ minPxPerSec: 1,
43
+ fillParent: true,
44
+ media,
45
+ url: media.src,
46
+ peaks: [data.getChannelData(0)],
47
+ duration: data.duration,
48
+ });
37
49
  const overlayWidth = Math.round((this.minimapWrapper.clientWidth / this.wrapper.clientWidth) * 100);
38
50
  this.overlay.style.width = `${overlayWidth}%`;
39
51
  this.wavesurfer.on('timeupdate', ({ currentTime }) => {
@@ -43,8 +55,7 @@ class TimelinePlugin extends BasePlugin {
43
55
  }
44
56
  /** Unmount */
45
57
  destroy() {
46
- var _a;
47
- (_a = this.miniWavesurfer) === null || _a === void 0 ? void 0 : _a.destroy();
58
+ this.miniWavesurfer?.destroy();
48
59
  this.minimapWrapper.remove();
49
60
  super.destroy();
50
61
  }
@@ -8,19 +8,26 @@ type MultitrackTracks = Array<{
8
8
  startCue?: number;
9
9
  endCue?: number;
10
10
  markers?: Array<{
11
- id: string | number;
12
11
  time: number;
13
12
  label?: string;
14
13
  color?: string;
15
14
  }>;
15
+ regions?: Array<{
16
+ startTime: number;
17
+ endTime: number;
18
+ label?: string;
19
+ color?: string;
20
+ }>;
21
+ options?: WaveSurferOptions;
16
22
  }>;
17
23
  type MultitrackOptions = {
18
24
  container: HTMLElement;
19
25
  minPxPerSec?: number;
20
26
  cursorColor?: string;
27
+ cursorWidth?: number;
21
28
  trackBackground?: string;
22
29
  trackBorderColor?: string;
23
- cueColor?: string;
30
+ rightButtonDrag?: boolean;
24
31
  onTrackPositionUpdate?: (id: string | number, startPosition: number) => void;
25
32
  };
26
33
  declare class MultiTrack {
@@ -41,6 +48,7 @@ declare class MultiTrack {
41
48
  private initTimeline;
42
49
  private updatePosition;
43
50
  private onDrag;
51
+ private onMove;
44
52
  private findCurrentTracks;
45
53
  private startSync;
46
54
  play(): void;
@@ -2,18 +2,25 @@ import WaveSurfer from '../index.js';
2
2
  import RegionsPlugin from './regions.js';
3
3
  import TimelinePlugin from './timeline.js';
4
4
  class MultiTrack {
5
+ tracks;
6
+ options;
7
+ audios = [];
8
+ wavesurfers = [];
9
+ durations = [];
10
+ currentTime = 0;
11
+ maxDuration = 0;
12
+ rendering;
13
+ isDragging = false;
14
+ frameRequest = null;
5
15
  static create(tracks, options) {
6
16
  return new MultiTrack(tracks, options);
7
17
  }
8
18
  constructor(tracks, options) {
9
- this.audios = [];
10
- this.wavesurfers = [];
11
- this.durations = [];
12
- this.currentTime = 0;
13
- this.maxDuration = 0;
14
- this.isDragging = false;
15
- this.frameRequest = null;
16
- this.tracks = tracks.map((track) => (Object.assign(Object.assign({}, track), { startPosition: track.startPosition || 0, peaks: track.peaks || (track.url ? undefined : [new Float32Array()]) })));
19
+ this.tracks = tracks.map((track) => ({
20
+ ...track,
21
+ startPosition: track.startPosition || 0,
22
+ peaks: track.peaks || (track.url ? undefined : [new Float32Array()]),
23
+ }));
17
24
  this.options = options;
18
25
  this.rendering = initRendering(this.tracks, this.options);
19
26
  this.initAudios().then((durations) => {
@@ -31,9 +38,9 @@ class MultiTrack {
31
38
  this.initWavesurfers();
32
39
  this.initTimeline();
33
40
  this.rendering.containers.forEach((container, index) => {
34
- const drag = initDragging(container, (delta) => this.onDrag(index, delta));
41
+ const drag = initDragging(container, (delta) => this.onDrag(index, delta), options.rightButtonDrag);
35
42
  this.wavesurfers[index].once('destroy', () => {
36
- drag === null || drag === void 0 ? void 0 : drag.destroy();
43
+ drag?.destroy();
37
44
  });
38
45
  });
39
46
  });
@@ -53,25 +60,46 @@ class MultiTrack {
53
60
  initWavesurfers() {
54
61
  const wavesurfers = this.tracks.map((track, index) => {
55
62
  // Create a wavesurfer instance
56
- const ws = WaveSurfer.create(Object.assign(Object.assign({}, this.options), { container: this.rendering.containers[index], minPxPerSec: 0, media: this.audios[index], peaks: track.peaks, cursorColor: 'transparent', interactive: false }));
63
+ const ws = WaveSurfer.create({
64
+ ...track.options,
65
+ container: this.rendering.containers[index],
66
+ minPxPerSec: 0,
67
+ media: this.audios[index],
68
+ peaks: track.peaks,
69
+ cursorColor: 'transparent',
70
+ cursorWidth: 0,
71
+ interactive: false,
72
+ });
57
73
  const wsRegions = ws.registerPlugin(RegionsPlugin, {
58
74
  draggable: false,
59
- resizable: false,
75
+ resizable: true,
60
76
  dragSelection: false,
61
77
  });
62
78
  ws.once('decode', () => {
63
79
  // Render start and end cues
64
80
  if (track.startCue) {
65
- wsRegions.add(track.startCue, track.startCue, 'start', this.options.cueColor);
81
+ const region = wsRegions.add(0, track.startCue, '', 'rgba(0, 0, 0, 0.7)');
82
+ region.element.firstElementChild?.remove();
66
83
  }
67
84
  if (track.endCue) {
68
- wsRegions.add(track.endCue, track.endCue, 'end', this.options.cueColor);
85
+ const region = wsRegions.add(track.endCue, track.endCue + this.durations[index], '', 'rgba(0, 0, 0, 0.7)');
86
+ region.element.lastChild?.remove();
87
+ }
88
+ // Render regions
89
+ if (track.regions) {
90
+ track.regions.forEach((params) => {
91
+ const region = wsRegions.add(params.startTime || 0, params.endTime, params.label, params.color);
92
+ if (!params.startTime) {
93
+ region.element.firstElementChild?.remove();
94
+ }
95
+ });
69
96
  }
70
97
  // Render markers
71
- ;
72
- (track.markers || []).forEach((marker) => {
73
- wsRegions.add(marker.time, marker.time, marker.label, marker.color);
74
- });
98
+ if (track.markers) {
99
+ track.markers.forEach((marker) => {
100
+ wsRegions.add(marker.time, marker.time, marker.label, marker.color);
101
+ });
102
+ }
75
103
  });
76
104
  return ws;
77
105
  });
@@ -111,23 +139,25 @@ class MultiTrack {
111
139
  });
112
140
  }
113
141
  onDrag(index, delta) {
114
- var _a, _b;
142
+ // Prevent click events when dragging
143
+ this.isDragging = true;
144
+ setTimeout(() => (this.isDragging = false), 600);
145
+ const newTime = this.tracks[index].startPosition + delta * this.maxDuration;
146
+ this.onMove(index, newTime);
147
+ }
148
+ onMove(index, newStartPosition) {
115
149
  const track = this.tracks[index];
116
150
  if (!track.draggable)
117
151
  return;
118
- this.isDragging = true;
119
- setTimeout(() => (this.isDragging = false), 600);
120
- const newTime = track.startPosition + delta * this.maxDuration;
121
- const minStart = this.tracks.reduce((min, track, i) => {
122
- if (i === index || !track.url)
123
- return min;
124
- return Math.min(min, track.startPosition);
125
- }, Infinity);
126
- if (newTime + this.durations[index] >= minStart) {
127
- track.startPosition = newTime;
152
+ const mainIndex = this.tracks.findIndex((item) => item.url && !item.draggable);
153
+ const mainTrack = this.tracks[mainIndex];
154
+ const minStart = (mainTrack ? mainTrack.startPosition : 0) - this.durations[index];
155
+ const maxStart = mainTrack ? mainTrack.startPosition + this.durations[mainIndex] : this.maxDuration;
156
+ if (newStartPosition >= minStart && newStartPosition <= maxStart) {
157
+ track.startPosition = newStartPosition;
128
158
  this.rendering.setContainerOffsets();
129
159
  this.updatePosition(this.currentTime);
130
- (_b = (_a = this.options).onTrackPositionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, track.id, track.startPosition);
160
+ this.options.onTrackPositionUpdate?.(track.id, track.startPosition);
131
161
  }
132
162
  }
133
163
  findCurrentTracks() {
@@ -165,8 +195,7 @@ class MultiTrack {
165
195
  this.startSync();
166
196
  const indexes = this.findCurrentTracks();
167
197
  indexes.forEach((index) => {
168
- var _a;
169
- (_a = this.audios[index]) === null || _a === void 0 ? void 0 : _a.play();
198
+ this.audios[index]?.play();
170
199
  });
171
200
  }
172
201
  pause() {
@@ -212,8 +241,9 @@ function initRendering(tracks, options) {
212
241
  options.container.appendChild(scroll);
213
242
  // Create a common cursor
214
243
  const cursor = document.createElement('div');
215
- cursor.setAttribute('style', 'width: 1px; height: 100%; position: absolute; z-index: 10; top: 0; left: 0');
244
+ cursor.setAttribute('style', 'height: 100%; position: absolute; z-index: 10; top: 0; left: 0');
216
245
  cursor.style.backgroundColor = options.cursorColor || '#000';
246
+ cursor.style.width = `${options.cursorWidth ?? 1}px`;
217
247
  wrapper.appendChild(cursor);
218
248
  const { clientWidth } = wrapper;
219
249
  // Create containers for each track
@@ -237,7 +267,7 @@ function initRendering(tracks, options) {
237
267
  container.style.width = `${durations[i] * pxPerSec}px`;
238
268
  container.style.transform = `translateX(${offset}px)`;
239
269
  if (tracks[i].draggable)
240
- container.style.cursor = 'ew-resize';
270
+ container.style.cursor = 'move';
241
271
  });
242
272
  };
243
273
  return {
@@ -271,14 +301,19 @@ function initRendering(tracks, options) {
271
301
  },
272
302
  };
273
303
  }
274
- function initDragging(container, onDrag) {
304
+ function initDragging(container, onDrag, rightButtonDrag = false) {
275
305
  const wrapper = container.parentElement;
276
306
  if (!wrapper)
277
307
  return;
278
308
  // Dragging tracks to set position
279
309
  let dragStart = null;
310
+ container.addEventListener('contextmenu', (e) => {
311
+ rightButtonDrag && e.preventDefault();
312
+ });
280
313
  // Drag start
281
314
  container.addEventListener('mousedown', (e) => {
315
+ if (rightButtonDrag && e.button !== 2)
316
+ return;
282
317
  const rect = wrapper.getBoundingClientRect();
283
318
  dragStart = e.clientX - rect.left;
284
319
  });