wavesurfer.js 7.0.0-alpha.0 → 7.0.0-alpha.2

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.
package/dist/renderer.js DELETED
@@ -1,193 +0,0 @@
1
- import EventEmitter from './event-emitter.js';
2
- class Renderer extends EventEmitter {
3
- constructor(options) {
4
- super();
5
- this.options = options;
6
- let container = null;
7
- if (typeof this.options.container === 'string') {
8
- container = document.querySelector(this.options.container);
9
- }
10
- else if (this.options.container instanceof HTMLElement) {
11
- container = this.options.container;
12
- }
13
- if (!container) {
14
- throw new Error('Container not found');
15
- }
16
- const div = document.createElement('div');
17
- const shadow = div.attachShadow({ mode: 'open' });
18
- shadow.innerHTML = `
19
- <style>
20
- :host .scroll {
21
- overflow-x: auto;
22
- overflow-y: visible;
23
- user-select: none;
24
- width: 100%;
25
- height: ${this.options.height}px;
26
- position: relative;
27
- }
28
- :host .wrapper {
29
- position: relative;
30
- width: fit-content;
31
- min-width: 100%;
32
- height: 100%;
33
- z-index: 2;
34
- }
35
- :host canvas {
36
- display: block;
37
- height: 100%;
38
- min-width: 100%;
39
- image-rendering: pixelated;
40
- }
41
- :host .progress {
42
- position: absolute;
43
- z-index: 2;
44
- top: 0;
45
- left: 0;
46
- height: 100%;
47
- pointer-events: none;
48
- clip-path: inset(100%);
49
- }
50
- :host .cursor {
51
- position: absolute;
52
- z-index: 3;
53
- top: 0;
54
- left: 0;
55
- height: 100%;
56
- border-right: 1px solid ${this.options.progressColor};
57
- }
58
- </style>
59
-
60
- <div class="scroll">
61
- <div class="wrapper">
62
- <canvas></canvas>
63
- <canvas class="progress"></canvas>
64
- <div class="cursor"></div>
65
- </div>
66
- </div>
67
- `;
68
- this.container = div;
69
- this.shadowRoot = shadow;
70
- this.mainCanvas = shadow.querySelector('canvas');
71
- this.ctx = this.mainCanvas.getContext('2d', { desynchronized: true });
72
- this.progressCanvas = shadow.querySelector('.progress');
73
- this.cursor = shadow.querySelector('.cursor');
74
- container.appendChild(div);
75
- this.mainCanvas.addEventListener('click', (e) => {
76
- const rect = this.mainCanvas.getBoundingClientRect();
77
- const x = e.clientX - rect.left;
78
- const relativeX = x / rect.width;
79
- this.emit('click', { relativeX });
80
- });
81
- }
82
- destroy() {
83
- this.container.remove();
84
- }
85
- renderLinePeaks(channelData, width, height) {
86
- const { ctx } = this;
87
- ctx.clearRect(0, 0, width, height);
88
- ctx.beginPath();
89
- ctx.moveTo(0, height / 2);
90
- // Channel 0 is left, 1 is right
91
- const leftChannel = channelData[0];
92
- const isMono = channelData.length === 1;
93
- const rightChannel = isMono ? leftChannel : channelData[1];
94
- // Draw left channel in the top half of the canvas
95
- let prevX = -1;
96
- let prevY = 0;
97
- for (let i = 0, len = leftChannel.length; i < len; i++) {
98
- const x = Math.round((i / leftChannel.length) * width);
99
- const y = Math.round(((1 - leftChannel[i]) * height) / 2);
100
- if (x !== prevX || y > prevY) {
101
- ctx.lineTo(x, y);
102
- prevX = x;
103
- prevY = y;
104
- }
105
- }
106
- // Draw right channel in the bottom half of the canvas
107
- prevX = -1;
108
- prevY = 0;
109
- for (let i = rightChannel.length - 1; i >= 0; i--) {
110
- const x = Math.round((i / rightChannel.length) * width);
111
- const y = Math.round(((1 + rightChannel[i]) * height) / 2);
112
- if (x !== prevX || (isMono ? y < -prevY : y > prevY)) {
113
- ctx.lineTo(x, y);
114
- prevX = x;
115
- prevY = y;
116
- }
117
- }
118
- ctx.strokeStyle = ctx.fillStyle = this.options.waveColor;
119
- ctx.stroke();
120
- ctx.fill();
121
- }
122
- renderBarPeaks(channelData, width, height) {
123
- var _a, _b;
124
- const { devicePixelRatio } = window;
125
- const { ctx } = this;
126
- ctx.clearRect(0, 0, width, height);
127
- const barWidthOption = this.options.barWidth || 1;
128
- const barWidth = barWidthOption * devicePixelRatio;
129
- const barGap = ((_a = this.options.barGap) !== null && _a !== void 0 ? _a : barWidthOption / 2) * devicePixelRatio;
130
- const barRadius = (_b = this.options.barRadius) !== null && _b !== void 0 ? _b : 0;
131
- const leftChannel = channelData[0];
132
- const isMono = channelData.length === 1;
133
- const rightChannel = isMono ? leftChannel : channelData[1];
134
- const barCount = Math.floor(width / (barWidth + barGap));
135
- const leftChannelBars = new Float32Array(barCount);
136
- const rightChannelBars = new Float32Array(barCount);
137
- const barIndexScale = barCount / leftChannel.length;
138
- const leftChannelLength = leftChannel.length;
139
- for (let i = 0; i < leftChannelLength; i++) {
140
- const barIndex = Math.round(i * barIndexScale);
141
- leftChannelBars[barIndex] = Math.max(leftChannelBars[barIndex], leftChannel[i]);
142
- rightChannelBars[barIndex] = (isMono ? Math.min : Math.max)(rightChannelBars[barIndex], rightChannel[i]);
143
- }
144
- ctx.beginPath();
145
- for (let i = 0; i < barCount; i++) {
146
- const leftBarHeight = Math.max(1, Math.round((leftChannelBars[i] * height) / 2));
147
- const rightBarHeight = Math.max(1, Math.round(Math.abs(rightChannelBars[i] * height) / 2));
148
- ctx.roundRect(i * (barWidth + barGap), height / 2 - leftBarHeight, barWidth, leftBarHeight + rightBarHeight, barRadius);
149
- }
150
- ctx.fillStyle = this.options.waveColor;
151
- ctx.fill();
152
- }
153
- render(channelData, duration, minPxPerSec = this.options.minPxPerSec) {
154
- const { devicePixelRatio } = window;
155
- const width = Math.max(this.container.clientWidth * devicePixelRatio, duration * minPxPerSec);
156
- const { height } = this.options;
157
- this.mainCanvas.width = width;
158
- this.mainCanvas.height = height;
159
- this.mainCanvas.style.width = Math.round(width / devicePixelRatio) + 'px';
160
- const renderingFn = this.options.barWidth ? this.renderBarPeaks : this.renderLinePeaks;
161
- renderingFn.call(this, channelData, width, height);
162
- this.createProgressMask();
163
- }
164
- createProgressMask() {
165
- const progressCtx = this.progressCanvas.getContext('2d', { desynchronized: true });
166
- if (!progressCtx) {
167
- throw new Error('Failed to get canvas context');
168
- }
169
- this.progressCanvas.width = this.mainCanvas.width;
170
- this.progressCanvas.height = this.mainCanvas.height;
171
- this.progressCanvas.style.width = this.mainCanvas.style.width;
172
- this.progressCanvas.style.height = this.mainCanvas.style.height;
173
- progressCtx.drawImage(this.mainCanvas, 0, 0);
174
- progressCtx.globalCompositeOperation = 'source-in';
175
- progressCtx.fillStyle = this.options.progressColor;
176
- progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
177
- }
178
- renderProgress(progress, autoCenter = false) {
179
- this.progressCanvas.style.clipPath = `inset(0 ${100 - progress * 100}% 0 0)`;
180
- this.cursor.style.left = `${progress * 100}%`;
181
- if (autoCenter) {
182
- const center = this.container.clientWidth / 2;
183
- const fullWidth = this.mainCanvas.clientWidth;
184
- if (fullWidth * progress >= center) {
185
- this.container.scrollLeft = fullWidth * progress - center;
186
- }
187
- }
188
- }
189
- getContainer() {
190
- return this.shadowRoot.querySelector('.scroll');
191
- }
192
- }
193
- export default Renderer;
package/dist/timer.js DELETED
@@ -1,17 +0,0 @@
1
- import EventEmitter from './event-emitter.js';
2
- class Timer extends EventEmitter {
3
- constructor() {
4
- super();
5
- this.unsubscribe = () => undefined;
6
- this.unsubscribe = this.on('tick', () => {
7
- requestAnimationFrame(() => {
8
- this.emit('tick');
9
- });
10
- });
11
- this.emit('tick');
12
- }
13
- destroy() {
14
- this.unsubscribe();
15
- }
16
- }
17
- export default Timer;
Binary file
package/examples/bars.js DELETED
@@ -1,19 +0,0 @@
1
- import WaveSurfer from '/dist/index.js'
2
-
3
- const wavesurfer = WaveSurfer.create({
4
- container: document.body,
5
- waveColor: 'rgb(200, 0, 200)',
6
- progressColor: 'rgb(100, 0, 100)',
7
- url: 'https://wavesurfer-js.org/example/media/demo.wav',
8
-
9
- // Set a bar width
10
- barWidth: 2,
11
- // Optionally, specify the spacing between bars
12
- barGap: 1,
13
- // And the bar radius
14
- barRadius: 2,
15
- })
16
-
17
- wavesurfer.on('seek', function () {
18
- wavesurfer.play()
19
- })
package/examples/basic.js DELETED
@@ -1,8 +0,0 @@
1
- import WaveSurfer from '/dist/index.js'
2
-
3
- const wavesurfer = WaveSurfer.create({
4
- container: document.body,
5
- waveColor: 'rgb(200, 0, 200)',
6
- progressColor: 'rgb(100, 0, 100)',
7
- url: 'https://wavesurfer-js.org/example/media/demo.wav'
8
- })
@@ -1,28 +0,0 @@
1
- import WaveSurfer from '/dist/index.js'
2
-
3
- // Create a canvas gradient
4
- const ctx = document.createElement('canvas').getContext('2d')
5
- const gradient = ctx.createLinearGradient(0, 0, 0, 150)
6
- gradient.addColorStop(0, 'rgb(200, 0, 200)')
7
- gradient.addColorStop(0.7, 'rgb(100, 0, 100)')
8
- gradient.addColorStop(1, 'rgb(0, 0, 0)')
9
-
10
- // Default style with a gradient
11
- WaveSurfer.create({
12
- container: document.body,
13
- waveColor: gradient,
14
- height: 200,
15
- progressColor: 'rgba(0, 0, 100, 0.5)',
16
- url: 'https://wavesurfer-js.org/example/media/demo.wav'
17
- })
18
-
19
-
20
- // SoundCloud-style bars
21
- WaveSurfer.create({
22
- container: document.body,
23
- waveColor: gradient,
24
- height: 200,
25
- barWidth: 2,
26
- progressColor: 'rgba(0, 0, 100, 0.5)',
27
- url: 'https://wavesurfer-js.org/example/media/demo.wav'
28
- })
@@ -1,63 +0,0 @@
1
- import WaveSurfer from '/dist/index.js'
2
- import RegionsPlugin from '/dist/plugins/regions.js'
3
-
4
- // Create an instance of WaveSurfer
5
- const ws = WaveSurfer.create({
6
- container: document.body,
7
- waveColor: 'rgb(200, 0, 200)',
8
- progressColor: 'rgb(100, 0, 100)',
9
- url: 'https://wavesurfer-js.org/example/media/demo.wav'
10
- })
11
-
12
- // Initialize the Regions plugin
13
- const wsRegions = ws.registerPlugin(RegionsPlugin)
14
-
15
- // Give regions a random color when they are created
16
- const random = (min, max) => Math.random() * (max - min) + min
17
- wsRegions.on('region-created', ({ region }) => {
18
- wsRegions.setRegionColor(region, `rgba(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)}, 0.5)`)
19
- })
20
-
21
- // Create some regions at specific time ranges
22
- ws.on('ready', () => {
23
- wsRegions.addRegionAtTime(4, 7, 'First region')
24
- wsRegions.addRegionAtTime(9, 10, 'Middle region')
25
- wsRegions.addRegionAtTime(12, 17, 'Last region')
26
- })
27
-
28
- // Loop a region on click
29
- let loop = true
30
- let activeRegion = null
31
-
32
- wsRegions.on('region-clicked', ({ region }) => {
33
- ws.seekTo(region.startTime)
34
- ws.play()
35
- activeRegion = region
36
- })
37
-
38
- // Track the time
39
- ws.on('audioprocess', ({ currentTime }) => {
40
- // When the end of the region is reached
41
- if (activeRegion && ws.isPlaying() && (currentTime >= activeRegion.endTime)) {
42
- if (loop) {
43
- // If looping, jump to the start of the region
44
- ws.seekTo(activeRegion.startTime)
45
- } else {
46
- // Otherwise, stop playing
47
- ws.pause()
48
- ws.seekTo(activeRegion.endTime)
49
- activeRegion = null
50
- }
51
- }
52
- })
53
-
54
- // Toggle looping with a checkbox
55
- const p = document.createElement('p')
56
- p.innerHTML = `
57
- <label>
58
- <input type="checkbox" checked="${loop}" style="vertical-align: text-top" />
59
- Loop regions on click
60
- </label>
61
- `
62
- document.body.appendChild(p)
63
- document.querySelector('input').onclick = (e) => { loop = e.target.checked }
package/examples/video.js DELETED
@@ -1,19 +0,0 @@
1
- import WaveSurfer from '/dist/index.js'
2
-
3
- // Create a video element and add it to the DOM.
4
- // The video can also be defined in HTML, in which case we just get it by a selector.
5
- const video = document.createElement('video')
6
- video.crossOrigin = 'anonymous'
7
- video.controls = true
8
- video.style.width = '100%'
9
- video.src = 'https://wavesurfer-js.org/example/media/nasa.mp4'
10
- document.body.appendChild(video)
11
-
12
- // Initialize wavesurfer.js
13
- const ws = WaveSurfer.create({
14
- container: document.body,
15
- waveColor: 'rgb(200, 0, 200)',
16
- progressColor: 'rgb(100, 0, 100)',
17
- // Pass the video element in the `media` param
18
- media: document.querySelector('video'),
19
- })
@@ -1,14 +0,0 @@
1
- import WaveSurfer from '/dist/index.js'
2
-
3
- const audio = new Audio()
4
- audio.controls = true
5
- audio.src = '/examples/audio.ogg'
6
- document.body.appendChild(audio)
7
-
8
- const wavesurfer = WaveSurfer.create({
9
- container: document.body,
10
- waveColor: 'rgb(200, 0, 200)',
11
- progressColor: 'rgb(100, 0, 100)',
12
- backend: 'WebAudio',
13
- media: audio,
14
- })
@@ -1,20 +0,0 @@
1
- import EventEmitter, { type GeneralEventTypes } from './event-emitter.js'
2
- import type { WaveSurfer, WaveSurferPluginParams } from './index.js'
3
-
4
- export class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
5
- protected wavesurfer: WaveSurfer
6
- protected renderer: WaveSurfer['renderer']
7
- protected subscriptions: (() => void)[] = []
8
-
9
- constructor(params: WaveSurferPluginParams) {
10
- super()
11
- this.wavesurfer = params.wavesurfer
12
- this.renderer = params.renderer
13
- }
14
-
15
- destroy() {
16
- this.subscriptions.forEach((unsubscribe) => unsubscribe())
17
- }
18
- }
19
-
20
- export default BasePlugin
package/src/decoder.ts DELETED
@@ -1,41 +0,0 @@
1
- // Web Audio decodeAudioData with a minimum allowed sample rate
2
- const SAMPLE_RATE = 3000
3
-
4
- class Decoder {
5
- audioCtx: AudioContext | null = null
6
-
7
- constructor() {
8
- this.audioCtx = new (window.AudioContext ||
9
- (window as unknown as { webkitAudioContext: AudioContext }).webkitAudioContext)({
10
- latencyHint: 'playback',
11
- sampleRate: SAMPLE_RATE,
12
- })
13
- }
14
-
15
- public async decode(audioData: ArrayBuffer): Promise<{
16
- duration: number
17
- channelData: Float32Array[]
18
- }> {
19
- if (!this.audioCtx) {
20
- throw new Error('AudioContext is not initialized')
21
- }
22
-
23
- const buffer = await this.audioCtx.decodeAudioData(audioData)
24
- const channelData = [buffer.getChannelData(0)]
25
- if (buffer.numberOfChannels > 1) {
26
- channelData.push(buffer.getChannelData(1))
27
- }
28
-
29
- return {
30
- duration: buffer.duration,
31
- channelData,
32
- }
33
- }
34
-
35
- destroy() {
36
- this.audioCtx?.close()
37
- this.audioCtx = null
38
- }
39
- }
40
-
41
- export default Decoder
@@ -1,35 +0,0 @@
1
- export interface GeneralEventTypes {
2
- // the name of the event and the data it dispatches with
3
- // e.g. 'entryCreated': { count: 1 }
4
- [eventType: string]: unknown
5
- }
6
-
7
- class EventEmitter<EventTypes extends GeneralEventTypes> {
8
- private eventTarget: EventTarget
9
-
10
- constructor() {
11
- this.eventTarget = new EventTarget()
12
- }
13
-
14
- protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void {
15
- const e = new CustomEvent(String(eventType), { detail })
16
- this.eventTarget.dispatchEvent(e)
17
- }
18
-
19
- /** Subscribe to an event and return a function to unsubscribe */
20
- on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void {
21
- const handler = (e: Event) => {
22
- if (e instanceof CustomEvent) {
23
- callback(e.detail)
24
- }
25
- }
26
-
27
- const eventName = String(eventType)
28
-
29
- this.eventTarget.addEventListener(eventName, handler)
30
-
31
- return () => this.eventTarget.removeEventListener(eventName, handler)
32
- }
33
- }
34
-
35
- export default EventEmitter
package/src/fetcher.ts DELETED
@@ -1,7 +0,0 @@
1
- class Fetcher {
2
- public async load(url: string): Promise<ArrayBuffer> {
3
- return fetch(url).then((response) => response.arrayBuffer())
4
- }
5
- }
6
-
7
- export default Fetcher