wavesurfer.js 6.5.1 → 7.0.0-alpha.1
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/.eslintrc.json +24 -0
- package/.prettierrc +8 -0
- package/README.md +26 -117
- package/dist/base-plugin.d.ts +10 -0
- package/dist/base-plugin.js +13 -0
- package/dist/decoder.d.ts +10 -0
- package/dist/decoder.js +43 -0
- package/dist/event-emitter.d.ts +11 -0
- package/dist/event-emitter.js +21 -0
- package/dist/fetcher.d.ts +4 -0
- package/dist/fetcher.js +17 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +166 -0
- package/dist/player-webaudio.d.ts +8 -0
- package/dist/player-webaudio.js +31 -0
- package/dist/player.d.ts +16 -0
- package/dist/player.js +40 -0
- package/dist/plugins/regions.d.ts +48 -0
- package/dist/plugins/regions.js +183 -0
- package/dist/renderer.d.ts +34 -0
- package/dist/renderer.js +193 -0
- package/dist/timer.d.ts +10 -0
- package/dist/timer.js +17 -0
- package/examples/audio.ogg +0 -0
- package/examples/bars.js +19 -0
- package/examples/basic.js +8 -0
- package/examples/gradient.js +28 -0
- package/examples/regions.js +63 -0
- package/examples/video.js +19 -0
- package/examples/webaudio.js +14 -0
- package/package.json +19 -99
- package/src/base-plugin.ts +20 -0
- package/src/decoder.ts +41 -0
- package/src/event-emitter.ts +35 -0
- package/src/fetcher.ts +7 -0
- package/src/index.ts +252 -0
- package/src/player-webaudio.ts +34 -0
- package/src/player.ts +50 -0
- package/src/plugins/regions.ts +240 -0
- package/src/renderer.ts +250 -0
- package/src/timer.ts +27 -0
- package/tsconfig.json +105 -0
- package/tutorial/index.html +47 -0
- package/tutorial/src/editor.js +70 -0
- package/tutorial/src/init.js +66 -0
- package/tutorial/src/url.js +25 -0
- package/tutorial/style.css +211 -0
- package/yarn-error.log +1049 -0
- package/LICENSE +0 -29
- package/dist/plugin/wavesurfer.cursor.js +0 -418
- package/dist/plugin/wavesurfer.cursor.js.map +0 -1
- package/dist/plugin/wavesurfer.cursor.min.js +0 -7
- package/dist/plugin/wavesurfer.cursor.min.js.map +0 -1
- package/dist/plugin/wavesurfer.elan.js +0 -381
- package/dist/plugin/wavesurfer.elan.js.map +0 -1
- package/dist/plugin/wavesurfer.elan.min.js +0 -7
- package/dist/plugin/wavesurfer.elan.min.js.map +0 -1
- package/dist/plugin/wavesurfer.markers.js +0 -520
- package/dist/plugin/wavesurfer.markers.js.map +0 -1
- package/dist/plugin/wavesurfer.markers.min.js +0 -7
- package/dist/plugin/wavesurfer.markers.min.js.map +0 -1
- package/dist/plugin/wavesurfer.mediasession.js +0 -181
- package/dist/plugin/wavesurfer.mediasession.js.map +0 -1
- package/dist/plugin/wavesurfer.mediasession.min.js +0 -7
- package/dist/plugin/wavesurfer.mediasession.min.js.map +0 -1
- package/dist/plugin/wavesurfer.microphone.js +0 -466
- package/dist/plugin/wavesurfer.microphone.js.map +0 -1
- package/dist/plugin/wavesurfer.microphone.min.js +0 -7
- package/dist/plugin/wavesurfer.microphone.min.js.map +0 -1
- package/dist/plugin/wavesurfer.minimap.js +0 -440
- package/dist/plugin/wavesurfer.minimap.js.map +0 -1
- package/dist/plugin/wavesurfer.minimap.min.js +0 -7
- package/dist/plugin/wavesurfer.minimap.min.js.map +0 -1
- package/dist/plugin/wavesurfer.playhead.js +0 -300
- package/dist/plugin/wavesurfer.playhead.js.map +0 -1
- package/dist/plugin/wavesurfer.playhead.min.js +0 -7
- package/dist/plugin/wavesurfer.playhead.min.js.map +0 -1
- package/dist/plugin/wavesurfer.regions.js +0 -1335
- package/dist/plugin/wavesurfer.regions.js.map +0 -1
- package/dist/plugin/wavesurfer.regions.min.js +0 -7
- package/dist/plugin/wavesurfer.regions.min.js.map +0 -1
- package/dist/plugin/wavesurfer.spectrogram.js +0 -736
- package/dist/plugin/wavesurfer.spectrogram.js.map +0 -1
- package/dist/plugin/wavesurfer.spectrogram.min.js +0 -7
- package/dist/plugin/wavesurfer.spectrogram.min.js.map +0 -1
- package/dist/plugin/wavesurfer.timeline.js +0 -649
- package/dist/plugin/wavesurfer.timeline.js.map +0 -1
- package/dist/plugin/wavesurfer.timeline.min.js +0 -7
- package/dist/plugin/wavesurfer.timeline.min.js.map +0 -1
- package/dist/wavesurfer-html-init.js +0 -381
- package/dist/wavesurfer-html-init.js.map +0 -1
- package/dist/wavesurfer-html-init.min.js +0 -7
- package/dist/wavesurfer-html-init.min.js.map +0 -1
- package/dist/wavesurfer.js +0 -6145
- package/dist/wavesurfer.js.map +0 -1
- package/dist/wavesurfer.min.js +0 -7
- package/dist/wavesurfer.min.js.map +0 -1
- package/src/drawer.canvasentry.js +0 -427
- package/src/drawer.js +0 -413
- package/src/drawer.multicanvas.js +0 -617
- package/src/html-init.js +0 -241
- package/src/mediaelement-webaudio.js +0 -77
- package/src/mediaelement.js +0 -443
- package/src/peakcache.js +0 -127
- package/src/plugin/cursor/index.js +0 -350
- package/src/plugin/elan/index.js +0 -307
- package/src/plugin/markers/index.js +0 -436
- package/src/plugin/mediasession/index.js +0 -94
- package/src/plugin/microphone/index.js +0 -415
- package/src/plugin/minimap/index.js +0 -403
- package/src/plugin/playhead/index.js +0 -226
- package/src/plugin/regions/index.js +0 -478
- package/src/plugin/regions/region.js +0 -875
- package/src/plugin/spectrogram/fft.js +0 -222
- package/src/plugin/spectrogram/index.js +0 -522
- package/src/plugin/timeline/index.js +0 -600
- package/src/util/absMax.js +0 -16
- package/src/util/clamp.js +0 -11
- package/src/util/fetch.js +0 -222
- package/src/util/frame.js +0 -13
- package/src/util/get-id.js +0 -22
- package/src/util/index.js +0 -14
- package/src/util/max.js +0 -16
- package/src/util/min.js +0 -16
- package/src/util/observer.js +0 -144
- package/src/util/orientation.js +0 -98
- package/src/util/prevent-click.js +0 -19
- package/src/util/request-animation-frame.js +0 -15
- package/src/util/silence-mode.js +0 -42
- package/src/util/style.js +0 -16
- package/src/wavesurfer.js +0 -1797
- package/src/webaudio.js +0 -779
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": true,
|
|
4
|
+
"es2021": true
|
|
5
|
+
},
|
|
6
|
+
"extends": [
|
|
7
|
+
"eslint:recommended",
|
|
8
|
+
"plugin:@typescript-eslint/recommended",
|
|
9
|
+
"prettier",
|
|
10
|
+
"plugin:prettier/recommended"
|
|
11
|
+
],
|
|
12
|
+
"overrides": [
|
|
13
|
+
],
|
|
14
|
+
"parser": "@typescript-eslint/parser",
|
|
15
|
+
"parserOptions": {
|
|
16
|
+
"ecmaVersion": "latest",
|
|
17
|
+
"sourceType": "module"
|
|
18
|
+
},
|
|
19
|
+
"plugins": [
|
|
20
|
+
"@typescript-eslint"
|
|
21
|
+
],
|
|
22
|
+
"rules": {
|
|
23
|
+
}
|
|
24
|
+
}
|
package/.prettierrc
ADDED
package/README.md
CHANGED
|
@@ -1,141 +1,50 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wavesurfer.ts
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
 [](https://gitter.im/wavesurfer-js/wavesurfer.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
5
|
-
[](https://www.gitpoap.io/gh/wavesurfer-js/wavesurfer.js)
|
|
3
|
+
An experimental rewrite of [wavesufer.js](https://github.com/wavesurfer-js/wavesurfer.js) to play around with new ideas.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## Goals
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
* TypeScript API
|
|
8
|
+
* Better architecture
|
|
9
|
+
* Minimize the available options and provide sensible defaults
|
|
10
|
+
* Improve the decoding and rendering performance
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
## Non-goals
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
Have a question about integrating wavesurfer.js on your website? Feel free to ask in our forum: https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/q-a
|
|
14
|
+
Keeping backwards compatibility with earlier versions of wavesurfer.js.
|
|
15
15
|
|
|
16
|
-
##
|
|
17
|
-
Install the package:
|
|
16
|
+
## Architecture
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
yarn add wavesurfer.js
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
And import it like so:
|
|
28
|
-
```
|
|
29
|
-
import WaveSurfer from 'wavesurfer.js'
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
If you're not using a package manager, simply insert the script from a CDN:
|
|
33
|
-
```
|
|
34
|
-
<script src="https://unpkg.com/wavesurfer.js"></script>
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Create a container in your HTML:
|
|
38
|
-
```html
|
|
39
|
-
<div id="waveform"></div>
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Create an instance of wavesufer.js, passing the container selector and a few [options](https://wavesurfer-js.org/docs/options.html):
|
|
43
|
-
|
|
44
|
-
```javascript
|
|
45
|
-
var wavesurfer = WaveSurfer.create({
|
|
46
|
-
container: '#waveform',
|
|
47
|
-
waveColor: 'violet',
|
|
48
|
-
progressColor: 'purple'
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Subscribe to some [events](https://wavesurfer-js.org/docs/events.html):
|
|
53
|
-
|
|
54
|
-
```javascript
|
|
55
|
-
wavesurfer.on('ready', function () {
|
|
56
|
-
wavesurfer.play();
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Load an audio file from a URL:
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
wavesurfer.load('example/media/demo.wav');
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Documentation
|
|
18
|
+
Principles:
|
|
19
|
+
* Modular and event-driven
|
|
20
|
+
* Allow swapping the decoding, rendering and playback mechanisms
|
|
21
|
+
* Extensible with plugins
|
|
67
22
|
|
|
68
|
-
|
|
23
|
+

|
|
69
24
|
|
|
70
|
-
##
|
|
25
|
+
## Development
|
|
71
26
|
|
|
72
|
-
|
|
73
|
-
[the projects page](https://wavesurfer-js.org/projects/).
|
|
74
|
-
|
|
75
|
-
## Contributing
|
|
76
|
-
|
|
77
|
-
Have an idea and want to contribute to wavesurfer.js?
|
|
78
|
-
Please first start a discussion in the [Ideas section of our forum](https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/ideas) to coordinate with the maintainers.
|
|
79
|
-
|
|
80
|
-
### Development
|
|
81
|
-
|
|
82
|
-
[](https://github.com/wavesurfer-js/wavesurfer.js/actions?workflow=wavesurfer.js)
|
|
83
|
-
[](https://coveralls.io/github/wavesurfer-js/wavesurfer.js)
|
|
84
|
-

|
|
85
|
-
|
|
86
|
-
Install development dependencies:
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
npm install
|
|
90
|
-
```
|
|
91
|
-
Development tasks automatically rebuild certain parts of the library when files are changed (`start` – wavesurfer, `start:plugins` – plugins). Start a dev task and go to `localhost:8080/example/` to test the current build.
|
|
92
|
-
|
|
93
|
-
Start development server for core library:
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
npm run start
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Start development server for plugins:
|
|
27
|
+
Install dev dependencies:
|
|
100
28
|
|
|
101
29
|
```
|
|
102
|
-
|
|
30
|
+
yarn
|
|
103
31
|
```
|
|
104
32
|
|
|
105
|
-
|
|
33
|
+
Start the TypeScript compiler in watch mode:
|
|
106
34
|
|
|
107
35
|
```
|
|
108
|
-
|
|
36
|
+
yarn build -w
|
|
109
37
|
```
|
|
110
38
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
npm run test
|
|
115
|
-
```
|
|
39
|
+
Run an HTTP server to view the examples:
|
|
116
40
|
|
|
117
|
-
Build documentation with esdoc (generated files are placed in the `doc` directory.)
|
|
118
41
|
```
|
|
119
|
-
|
|
42
|
+
python3 -m http.server --cgi 8080
|
|
120
43
|
```
|
|
121
44
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
## Editing documentation
|
|
125
|
-
The homepage and documentation files are maintained in the [`gh-pages` branch](https://github.com/wavesurfer-js/wavesurfer.js/tree/gh-pages). Contributions to the documentation are especially welcome.
|
|
126
|
-
|
|
127
|
-
## Updating the NPM package
|
|
128
|
-
When preparing a new release, update the version in the `package.json` and have it merged to master. The new version of the package will be published to NPM automatically via GitHub Actions.
|
|
129
|
-
|
|
130
|
-
## Credits
|
|
131
|
-
|
|
132
|
-
The main maintainer: <img src="https://avatars.githubusercontent.com/u/305679" width="16" height="16" /> [Thijs Triemstra](https://github.com/thijstriemstra)
|
|
133
|
-
|
|
134
|
-
Many thanks to [all the awesome contributors](https://github.com/wavesurfer-js/wavesurfer.js/contributors)!
|
|
135
|
-
|
|
136
|
-
## License
|
|
45
|
+
Open http://localhost:8080/examples/basic/ in your browser.
|
|
46
|
+
There's no hot reload yet, so you'll need to reload the page manually on every change.
|
|
137
47
|
|
|
138
|
-
|
|
48
|
+
## Feedback
|
|
139
49
|
|
|
140
|
-
|
|
141
|
-
[BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause).
|
|
50
|
+
See https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
+
import type { WaveSurfer, WaveSurferPluginParams } from './index.js';
|
|
3
|
+
export declare class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
|
|
4
|
+
protected wavesurfer: WaveSurfer;
|
|
5
|
+
protected renderer: WaveSurfer['renderer'];
|
|
6
|
+
protected subscriptions: (() => void)[];
|
|
7
|
+
constructor(params: WaveSurferPluginParams);
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
10
|
+
export default BasePlugin;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
export class BasePlugin extends EventEmitter {
|
|
3
|
+
constructor(params) {
|
|
4
|
+
super();
|
|
5
|
+
this.subscriptions = [];
|
|
6
|
+
this.wavesurfer = params.wavesurfer;
|
|
7
|
+
this.renderer = params.renderer;
|
|
8
|
+
}
|
|
9
|
+
destroy() {
|
|
10
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export default BasePlugin;
|
package/dist/decoder.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
// Web Audio decodeAudioData with a minimum allowed sample rate
|
|
11
|
+
const SAMPLE_RATE = 3000;
|
|
12
|
+
class Decoder {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.audioCtx = null;
|
|
15
|
+
this.audioCtx = new (window.AudioContext ||
|
|
16
|
+
window.webkitAudioContext)({
|
|
17
|
+
latencyHint: 'playback',
|
|
18
|
+
sampleRate: SAMPLE_RATE,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
decode(audioData) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
if (!this.audioCtx) {
|
|
24
|
+
throw new Error('AudioContext is not initialized');
|
|
25
|
+
}
|
|
26
|
+
const buffer = yield this.audioCtx.decodeAudioData(audioData);
|
|
27
|
+
const channelData = [buffer.getChannelData(0)];
|
|
28
|
+
if (buffer.numberOfChannels > 1) {
|
|
29
|
+
channelData.push(buffer.getChannelData(1));
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
duration: buffer.duration,
|
|
33
|
+
channelData,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
destroy() {
|
|
38
|
+
var _a;
|
|
39
|
+
(_a = this.audioCtx) === null || _a === void 0 ? void 0 : _a.close();
|
|
40
|
+
this.audioCtx = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export default Decoder;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface GeneralEventTypes {
|
|
2
|
+
[eventType: string]: unknown;
|
|
3
|
+
}
|
|
4
|
+
declare class EventEmitter<EventTypes extends GeneralEventTypes> {
|
|
5
|
+
private eventTarget;
|
|
6
|
+
constructor();
|
|
7
|
+
protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void;
|
|
8
|
+
/** Subscribe to an event and return a function to unsubscribe */
|
|
9
|
+
on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void;
|
|
10
|
+
}
|
|
11
|
+
export default EventEmitter;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class EventEmitter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.eventTarget = new EventTarget();
|
|
4
|
+
}
|
|
5
|
+
emit(eventType, detail) {
|
|
6
|
+
const e = new CustomEvent(String(eventType), { detail });
|
|
7
|
+
this.eventTarget.dispatchEvent(e);
|
|
8
|
+
}
|
|
9
|
+
/** Subscribe to an event and return a function to unsubscribe */
|
|
10
|
+
on(eventType, callback) {
|
|
11
|
+
const handler = (e) => {
|
|
12
|
+
if (e instanceof CustomEvent) {
|
|
13
|
+
callback(e.detail);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const eventName = String(eventType);
|
|
17
|
+
this.eventTarget.addEventListener(eventName, handler);
|
|
18
|
+
return () => this.eventTarget.removeEventListener(eventName, handler);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export default EventEmitter;
|
package/dist/fetcher.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
class Fetcher {
|
|
11
|
+
load(url) {
|
|
12
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
13
|
+
return fetch(url).then((response) => response.arrayBuffer());
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export default Fetcher;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
+
import BasePlugin from './base-plugin.js';
|
|
3
|
+
export declare enum PlayerType {
|
|
4
|
+
WebAudio = "WebAudio",
|
|
5
|
+
MediaElement = "MediaElement"
|
|
6
|
+
}
|
|
7
|
+
export type WaveSurferOptions = {
|
|
8
|
+
/** HTML element or CSS selector */
|
|
9
|
+
container: HTMLElement | string | null;
|
|
10
|
+
/** Height of the waveform in pixels */
|
|
11
|
+
height?: number;
|
|
12
|
+
/** The color of the waveform */
|
|
13
|
+
waveColor?: string;
|
|
14
|
+
/** The color of the progress mask */
|
|
15
|
+
progressColor?: string;
|
|
16
|
+
/** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
17
|
+
barWidth?: number;
|
|
18
|
+
/** Spacing between bars in pixels */
|
|
19
|
+
barGap?: number;
|
|
20
|
+
/** Rounded borders for bars */
|
|
21
|
+
barRadius?: number;
|
|
22
|
+
/** Minimum pixels per second of audio (zoom) */
|
|
23
|
+
minPxPerSec?: number;
|
|
24
|
+
/** Audio URL */
|
|
25
|
+
url?: string;
|
|
26
|
+
/** Pre-computed audio data */
|
|
27
|
+
channelData?: Float32Array[];
|
|
28
|
+
/** Pre-computed duration */
|
|
29
|
+
duration?: number;
|
|
30
|
+
/** Player "backend", the default is MediaElement */
|
|
31
|
+
backend?: PlayerType;
|
|
32
|
+
/** Use an existing media element instead of creating one */
|
|
33
|
+
media?: HTMLMediaElement;
|
|
34
|
+
};
|
|
35
|
+
export type WaveSurferEvents = {
|
|
36
|
+
ready: {
|
|
37
|
+
duration: number;
|
|
38
|
+
};
|
|
39
|
+
canplay: void;
|
|
40
|
+
play: void;
|
|
41
|
+
pause: void;
|
|
42
|
+
audioprocess: {
|
|
43
|
+
currentTime: number;
|
|
44
|
+
};
|
|
45
|
+
seek: {
|
|
46
|
+
time: number;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export type WaveSurferPluginParams = {
|
|
50
|
+
wavesurfer: WaveSurfer;
|
|
51
|
+
renderer: WaveSurfer['renderer'];
|
|
52
|
+
};
|
|
53
|
+
export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
54
|
+
private options;
|
|
55
|
+
private fetcher;
|
|
56
|
+
private decoder;
|
|
57
|
+
private renderer;
|
|
58
|
+
private player;
|
|
59
|
+
private timer;
|
|
60
|
+
private plugins;
|
|
61
|
+
private subscriptions;
|
|
62
|
+
private channelData;
|
|
63
|
+
private duration;
|
|
64
|
+
/** Create a new WaveSurfer instance */
|
|
65
|
+
static create(options: WaveSurferOptions): WaveSurfer;
|
|
66
|
+
/** Create a new WaveSurfer instance */
|
|
67
|
+
constructor(options: WaveSurferOptions);
|
|
68
|
+
private initPlayerEvents;
|
|
69
|
+
private initRendererEvents;
|
|
70
|
+
private initTimerEvents;
|
|
71
|
+
/** Unmount wavesurfer */
|
|
72
|
+
destroy(): void;
|
|
73
|
+
/** Load an audio file by URL */
|
|
74
|
+
load(url: string, channelData?: Float32Array[], duration?: number): Promise<void>;
|
|
75
|
+
/** Zoom in or out */
|
|
76
|
+
zoom(minPxPerSec: number): void;
|
|
77
|
+
/** Start playing the audio */
|
|
78
|
+
play(): void;
|
|
79
|
+
/** Pause the audio */
|
|
80
|
+
pause(): void;
|
|
81
|
+
/** Skip to a time position in seconds */
|
|
82
|
+
seekTo(time: number): void;
|
|
83
|
+
/** Check if the audio is playing */
|
|
84
|
+
isPlaying(): boolean;
|
|
85
|
+
/** Get the duration of the audio in seconds */
|
|
86
|
+
getDuration(): number;
|
|
87
|
+
/** Get the current audio position in seconds */
|
|
88
|
+
getCurrentTime(): number;
|
|
89
|
+
/** Register and initialize a plugin */
|
|
90
|
+
registerPlugin<T extends BasePlugin<GeneralEventTypes>>(CustomPlugin: new (params: WaveSurferPluginParams) => T): T;
|
|
91
|
+
}
|
|
92
|
+
export default WaveSurfer;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
import Fetcher from './fetcher.js';
|
|
11
|
+
import Decoder from './decoder.js';
|
|
12
|
+
import Renderer from './renderer.js';
|
|
13
|
+
import Player from './player.js';
|
|
14
|
+
import WebAudioPlayer from './player-webaudio.js';
|
|
15
|
+
import EventEmitter from './event-emitter.js';
|
|
16
|
+
import Timer from './timer.js';
|
|
17
|
+
export var PlayerType;
|
|
18
|
+
(function (PlayerType) {
|
|
19
|
+
PlayerType["WebAudio"] = "WebAudio";
|
|
20
|
+
PlayerType["MediaElement"] = "MediaElement";
|
|
21
|
+
})(PlayerType || (PlayerType = {}));
|
|
22
|
+
const defaultOptions = {
|
|
23
|
+
height: 128,
|
|
24
|
+
waveColor: '#999',
|
|
25
|
+
progressColor: '#555',
|
|
26
|
+
minPxPerSec: 0,
|
|
27
|
+
backend: 'MediaElement',
|
|
28
|
+
};
|
|
29
|
+
export class WaveSurfer extends EventEmitter {
|
|
30
|
+
/** Create a new WaveSurfer instance */
|
|
31
|
+
static create(options) {
|
|
32
|
+
return new WaveSurfer(options);
|
|
33
|
+
}
|
|
34
|
+
/** Create a new WaveSurfer instance */
|
|
35
|
+
constructor(options) {
|
|
36
|
+
var _a;
|
|
37
|
+
super();
|
|
38
|
+
this.plugins = [];
|
|
39
|
+
this.subscriptions = [];
|
|
40
|
+
this.channelData = null;
|
|
41
|
+
this.duration = 0;
|
|
42
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
43
|
+
this.fetcher = new Fetcher();
|
|
44
|
+
this.decoder = new Decoder();
|
|
45
|
+
this.timer = new Timer();
|
|
46
|
+
this.player = new (this.options.backend === PlayerType.WebAudio ? WebAudioPlayer : Player)({
|
|
47
|
+
media: this.options.media,
|
|
48
|
+
});
|
|
49
|
+
this.renderer = new Renderer({
|
|
50
|
+
container: this.options.container,
|
|
51
|
+
height: this.options.height,
|
|
52
|
+
waveColor: this.options.waveColor,
|
|
53
|
+
progressColor: this.options.progressColor,
|
|
54
|
+
minPxPerSec: this.options.minPxPerSec,
|
|
55
|
+
barWidth: this.options.barWidth,
|
|
56
|
+
barGap: this.options.barGap,
|
|
57
|
+
barRadius: this.options.barRadius,
|
|
58
|
+
});
|
|
59
|
+
this.initPlayerEvents();
|
|
60
|
+
this.initRendererEvents();
|
|
61
|
+
this.initTimerEvents();
|
|
62
|
+
const url = this.options.url || ((_a = this.options.media) === null || _a === void 0 ? void 0 : _a.src);
|
|
63
|
+
if (url) {
|
|
64
|
+
this.load(url, this.options.channelData, this.options.duration);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
initPlayerEvents() {
|
|
68
|
+
this.subscriptions.push(this.player.on('timeupdate', () => {
|
|
69
|
+
const currentTime = this.getCurrentTime();
|
|
70
|
+
this.renderer.renderProgress(currentTime / this.duration, this.isPlaying());
|
|
71
|
+
this.emit('audioprocess', { currentTime });
|
|
72
|
+
}), this.player.on('play', () => {
|
|
73
|
+
this.emit('play');
|
|
74
|
+
}), this.player.on('pause', () => {
|
|
75
|
+
this.emit('pause');
|
|
76
|
+
}), this.player.on('canplay', () => {
|
|
77
|
+
this.emit('canplay');
|
|
78
|
+
}), this.player.on('seeking', () => {
|
|
79
|
+
this.emit('seek', { time: this.getCurrentTime() });
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
initRendererEvents() {
|
|
83
|
+
// Seek on click
|
|
84
|
+
this.subscriptions.push(this.renderer.on('click', ({ relativeX }) => {
|
|
85
|
+
const time = this.getDuration() * relativeX;
|
|
86
|
+
this.seekTo(time);
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
initTimerEvents() {
|
|
90
|
+
// The timer fires every 16ms for a smooth progress animation
|
|
91
|
+
this.subscriptions.push(this.timer.on('tick', () => {
|
|
92
|
+
if (this.isPlaying()) {
|
|
93
|
+
const currentTime = this.getCurrentTime();
|
|
94
|
+
this.renderer.renderProgress(currentTime / this.duration, true);
|
|
95
|
+
this.emit('audioprocess', { currentTime });
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
/** Unmount wavesurfer */
|
|
100
|
+
destroy() {
|
|
101
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
102
|
+
this.plugins.forEach((plugin) => plugin.destroy());
|
|
103
|
+
this.timer.destroy();
|
|
104
|
+
this.player.destroy();
|
|
105
|
+
this.decoder.destroy();
|
|
106
|
+
this.renderer.destroy();
|
|
107
|
+
}
|
|
108
|
+
/** Load an audio file by URL */
|
|
109
|
+
load(url, channelData, duration) {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
this.player.loadUrl(url);
|
|
112
|
+
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
113
|
+
if (channelData == null || duration == null) {
|
|
114
|
+
const audio = yield this.fetcher.load(url);
|
|
115
|
+
const data = yield this.decoder.decode(audio);
|
|
116
|
+
channelData = data.channelData;
|
|
117
|
+
duration = data.duration;
|
|
118
|
+
}
|
|
119
|
+
this.channelData = channelData;
|
|
120
|
+
this.duration = duration;
|
|
121
|
+
this.renderer.render(this.channelData, this.duration);
|
|
122
|
+
this.emit('ready', { duration: this.duration });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Zoom in or out */
|
|
126
|
+
zoom(minPxPerSec) {
|
|
127
|
+
if (this.channelData == null || this.duration == null) {
|
|
128
|
+
throw new Error('No audio loaded');
|
|
129
|
+
}
|
|
130
|
+
this.renderer.render(this.channelData, this.duration, minPxPerSec);
|
|
131
|
+
}
|
|
132
|
+
/** Start playing the audio */
|
|
133
|
+
play() {
|
|
134
|
+
this.player.play();
|
|
135
|
+
}
|
|
136
|
+
/** Pause the audio */
|
|
137
|
+
pause() {
|
|
138
|
+
this.player.pause();
|
|
139
|
+
}
|
|
140
|
+
/** Skip to a time position in seconds */
|
|
141
|
+
seekTo(time) {
|
|
142
|
+
this.player.seekTo(time);
|
|
143
|
+
}
|
|
144
|
+
/** Check if the audio is playing */
|
|
145
|
+
isPlaying() {
|
|
146
|
+
return this.player.isPlaying();
|
|
147
|
+
}
|
|
148
|
+
/** Get the duration of the audio in seconds */
|
|
149
|
+
getDuration() {
|
|
150
|
+
return this.duration;
|
|
151
|
+
}
|
|
152
|
+
/** Get the current audio position in seconds */
|
|
153
|
+
getCurrentTime() {
|
|
154
|
+
return this.player.getCurrentTime();
|
|
155
|
+
}
|
|
156
|
+
/** Register and initialize a plugin */
|
|
157
|
+
registerPlugin(CustomPlugin) {
|
|
158
|
+
const plugin = new CustomPlugin({
|
|
159
|
+
wavesurfer: this,
|
|
160
|
+
renderer: this.renderer,
|
|
161
|
+
});
|
|
162
|
+
this.plugins.push(plugin);
|
|
163
|
+
return plugin;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export default WaveSurfer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Player from './player.js';
|
|
2
|
+
class WebAudioPlayer extends Player {
|
|
3
|
+
constructor() {
|
|
4
|
+
super(...arguments);
|
|
5
|
+
this.audioCtx = null;
|
|
6
|
+
this.sourceNode = null;
|
|
7
|
+
}
|
|
8
|
+
destroy() {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
(_a = this.sourceNode) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
11
|
+
this.sourceNode = null;
|
|
12
|
+
(_b = this.audioCtx) === null || _b === void 0 ? void 0 : _b.close();
|
|
13
|
+
this.audioCtx = null;
|
|
14
|
+
super.destroy();
|
|
15
|
+
}
|
|
16
|
+
loadUrl(url) {
|
|
17
|
+
super.loadUrl(url);
|
|
18
|
+
if (!this.audioCtx) {
|
|
19
|
+
this.audioCtx = new (window.AudioContext ||
|
|
20
|
+
window.webkitAudioContext)({
|
|
21
|
+
latencyHint: 'playback',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (this.sourceNode) {
|
|
25
|
+
this.sourceNode.disconnect();
|
|
26
|
+
}
|
|
27
|
+
this.sourceNode = this.audioCtx.createMediaElementSource(this.media);
|
|
28
|
+
this.sourceNode.connect(this.audioCtx.destination);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export default WebAudioPlayer;
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare class Player {
|
|
2
|
+
protected media: HTMLMediaElement;
|
|
3
|
+
private isExternalMedia;
|
|
4
|
+
constructor({ media }: {
|
|
5
|
+
media?: HTMLMediaElement;
|
|
6
|
+
});
|
|
7
|
+
on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
|
|
8
|
+
destroy(): void;
|
|
9
|
+
loadUrl(src: string): void;
|
|
10
|
+
getCurrentTime(): number;
|
|
11
|
+
play(): void;
|
|
12
|
+
pause(): void;
|
|
13
|
+
isPlaying(): boolean;
|
|
14
|
+
seekTo(time: number): void;
|
|
15
|
+
}
|
|
16
|
+
export default Player;
|