wavesurfer.js 6.5.1 → 7.0.0-alpha.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.
- 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
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
})
|
package/package.json
CHANGED
|
@@ -1,106 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wavesurfer.js",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "dist/
|
|
6
|
-
"
|
|
7
|
-
"example": "example"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist/",
|
|
11
|
-
"src/"
|
|
12
|
-
],
|
|
3
|
+
"version": "7.0.0-alpha.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
13
7
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"build:main:min": "webpack --config ./build-config/webpack.prod.main.min.js",
|
|
20
|
-
"build:plugins": "webpack --config ./build-config/webpack.prod.plugins.js",
|
|
21
|
-
"build:plugins:min": "webpack --config ./build-config/webpack.prod.plugins.min.js",
|
|
22
|
-
"build:htmlinit": "webpack --config ./build-config/webpack.prod.htmlinit.js",
|
|
23
|
-
"build:htmlinit:min": "webpack --config ./build-config/webpack.prod.htmlinit.min.js",
|
|
24
|
-
"build:normal": "npm run build:main && npm run build:plugins && npm run build:htmlinit",
|
|
25
|
-
"build:minified": "npm run build:main:min && npm run build:plugins:min && npm run build:htmlinit:min",
|
|
26
|
-
"lint": "npm run lint:js && npm run lint:html",
|
|
27
|
-
"lint:js": "eslint src example spec",
|
|
28
|
-
"lint:html": "htmlhint example",
|
|
29
|
-
"lint-js-html": "lint-staged",
|
|
30
|
-
"doc": "esdoc",
|
|
31
|
-
"test": "karma start karma.conf.js",
|
|
32
|
-
"test:chrome": "karma start karma.conf.js --browsers=Chrome --single-run=false",
|
|
33
|
-
"prepublishOnly": "not-in-install && npm run build || in-install",
|
|
34
|
-
"prepare": "npm run build"
|
|
35
|
-
},
|
|
36
|
-
"repository": {
|
|
37
|
-
"type": "git",
|
|
38
|
-
"url": "git://github.com/wavesurfer-js/wavesurfer.js.git"
|
|
39
|
-
},
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"registry": "https://registry.npmjs.org"
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
10
|
+
"lint": "eslint --ext .ts src --fix",
|
|
11
|
+
"prettier": "prettier -w src",
|
|
12
|
+
"docs": "npx typedoc src/index.ts"
|
|
42
13
|
},
|
|
43
|
-
"author": "",
|
|
44
|
-
"license": "
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/wavesurfer-js/wavesurfer.js/issues"
|
|
47
|
-
},
|
|
48
|
-
"keywords": [
|
|
49
|
-
"waveform",
|
|
50
|
-
"audio",
|
|
51
|
-
"music",
|
|
52
|
-
"player",
|
|
53
|
-
"webaudio",
|
|
54
|
-
"microphone",
|
|
55
|
-
"visualization"
|
|
56
|
-
],
|
|
14
|
+
"author": "katspaugh <katspaugh@gmail.com>",
|
|
15
|
+
"license": "ISC",
|
|
57
16
|
"devDependencies": {
|
|
58
|
-
"@
|
|
59
|
-
"@
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"babel-plugin-istanbul": "^6.1.1",
|
|
67
|
-
"date-fns": "^2.29.3",
|
|
68
|
-
"debounce": "^1.2.1",
|
|
69
|
-
"esdoc": "^1.1.0",
|
|
70
|
-
"esdoc-accessor-plugin": "^1.0.0",
|
|
71
|
-
"esdoc-brand-plugin": "^1.0.1",
|
|
72
|
-
"esdoc-coverage-plugin": "^1.1.0",
|
|
73
|
-
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
|
|
74
|
-
"esdoc-integrate-test-plugin": "^1.0.0",
|
|
75
|
-
"esdoc-standard-plugin": "^1.0.0",
|
|
76
|
-
"eslint": "^8.26.0",
|
|
77
|
-
"htmlhint": "^1.1.4",
|
|
78
|
-
"in-publish": "^2.0.1",
|
|
79
|
-
"jasmine-core": "^4.5.0",
|
|
80
|
-
"karma": "^6.4.1",
|
|
81
|
-
"karma-chrome-launcher": "3.1.1",
|
|
82
|
-
"karma-coverage": "^2.2.0",
|
|
83
|
-
"karma-firefox-launcher": "^2.1.2",
|
|
84
|
-
"karma-jasmine": "5.1.0",
|
|
85
|
-
"karma-jasmine-matchers": "5.0.0",
|
|
86
|
-
"karma-verbose-reporter": "0.0.8",
|
|
87
|
-
"karma-webpack": "^5.0.0",
|
|
88
|
-
"lint-staged": "^13.1.0",
|
|
89
|
-
"load-script": "^2.0.0",
|
|
90
|
-
"pre-commit": "^1.2.2",
|
|
91
|
-
"webpack": "^5.75.0",
|
|
92
|
-
"webpack-cli": "^5.0.1",
|
|
93
|
-
"webpack-dev-server": "^4.11.1",
|
|
94
|
-
"webpack-merge": "^5.8.0"
|
|
95
|
-
},
|
|
96
|
-
"homepage": "https://wavesurfer-js.org",
|
|
97
|
-
"pre-commit": "lint-js-html",
|
|
98
|
-
"lint-staged": {
|
|
99
|
-
"*.js": [
|
|
100
|
-
"eslint"
|
|
101
|
-
],
|
|
102
|
-
"*.html": [
|
|
103
|
-
"htmlhint"
|
|
104
|
-
]
|
|
17
|
+
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
|
18
|
+
"@typescript-eslint/parser": "^5.54.0",
|
|
19
|
+
"eslint": "^8.35.0",
|
|
20
|
+
"eslint-config-prettier": "^8.7.0",
|
|
21
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
22
|
+
"prettier": "^2.8.4",
|
|
23
|
+
"typedoc": "^0.23.26",
|
|
24
|
+
"typescript": "^4.9.5"
|
|
105
25
|
}
|
|
106
26
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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
|
|
@@ -0,0 +1,35 @@
|
|
|
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
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import Fetcher from './fetcher.js'
|
|
2
|
+
import Decoder from './decoder.js'
|
|
3
|
+
import Renderer from './renderer.js'
|
|
4
|
+
import Player from './player.js'
|
|
5
|
+
import WebAudioPlayer from './player-webaudio.js'
|
|
6
|
+
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js'
|
|
7
|
+
import Timer from './timer.js'
|
|
8
|
+
import BasePlugin from './base-plugin.js'
|
|
9
|
+
|
|
10
|
+
export enum PlayerType {
|
|
11
|
+
WebAudio = 'WebAudio',
|
|
12
|
+
MediaElement = 'MediaElement',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type WaveSurferOptions = {
|
|
16
|
+
/** HTML element or CSS selector */
|
|
17
|
+
container: HTMLElement | string | null
|
|
18
|
+
/** Height of the waveform in pixels */
|
|
19
|
+
height?: number
|
|
20
|
+
/** The color of the waveform */
|
|
21
|
+
waveColor?: string
|
|
22
|
+
/** The color of the progress mask */
|
|
23
|
+
progressColor?: string
|
|
24
|
+
/** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
25
|
+
barWidth?: number
|
|
26
|
+
/** Spacing between bars in pixels */
|
|
27
|
+
barGap?: number
|
|
28
|
+
/** Rounded borders for bars */
|
|
29
|
+
barRadius?: number
|
|
30
|
+
/** Minimum pixels per second of audio (zoom) */
|
|
31
|
+
minPxPerSec?: number
|
|
32
|
+
/** Audio URL */
|
|
33
|
+
url?: string
|
|
34
|
+
/** Pre-computed audio data */
|
|
35
|
+
channelData?: Float32Array[]
|
|
36
|
+
/** Pre-computed duration */
|
|
37
|
+
duration?: number
|
|
38
|
+
/** Player "backend", the default is MediaElement */
|
|
39
|
+
backend?: PlayerType
|
|
40
|
+
/** Use an existing media element instead of creating one */
|
|
41
|
+
media?: HTMLMediaElement
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const defaultOptions = {
|
|
45
|
+
height: 128,
|
|
46
|
+
waveColor: '#999',
|
|
47
|
+
progressColor: '#555',
|
|
48
|
+
minPxPerSec: 0,
|
|
49
|
+
backend: 'MediaElement',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type WaveSurferEvents = {
|
|
53
|
+
ready: { duration: number }
|
|
54
|
+
canplay: void
|
|
55
|
+
play: void
|
|
56
|
+
pause: void
|
|
57
|
+
audioprocess: { currentTime: number }
|
|
58
|
+
seek: { time: number }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type WaveSurferPluginParams = {
|
|
62
|
+
wavesurfer: WaveSurfer
|
|
63
|
+
renderer: WaveSurfer['renderer']
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
67
|
+
private options: WaveSurferOptions & typeof defaultOptions
|
|
68
|
+
|
|
69
|
+
private fetcher: Fetcher
|
|
70
|
+
private decoder: Decoder
|
|
71
|
+
private renderer: Renderer
|
|
72
|
+
private player: Player
|
|
73
|
+
private timer: Timer
|
|
74
|
+
|
|
75
|
+
private plugins: BasePlugin<GeneralEventTypes>[] = []
|
|
76
|
+
private subscriptions: Array<() => void> = []
|
|
77
|
+
private channelData: Float32Array[] | null = null
|
|
78
|
+
private duration = 0
|
|
79
|
+
|
|
80
|
+
/** Create a new WaveSurfer instance */
|
|
81
|
+
public static create(options: WaveSurferOptions) {
|
|
82
|
+
return new WaveSurfer(options)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Create a new WaveSurfer instance */
|
|
86
|
+
constructor(options: WaveSurferOptions) {
|
|
87
|
+
super()
|
|
88
|
+
|
|
89
|
+
this.options = Object.assign({}, defaultOptions, options)
|
|
90
|
+
|
|
91
|
+
this.fetcher = new Fetcher()
|
|
92
|
+
this.decoder = new Decoder()
|
|
93
|
+
this.timer = new Timer()
|
|
94
|
+
|
|
95
|
+
this.player = new (this.options.backend === PlayerType.WebAudio ? WebAudioPlayer : Player)({
|
|
96
|
+
media: this.options.media,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
this.renderer = new Renderer({
|
|
100
|
+
container: this.options.container,
|
|
101
|
+
height: this.options.height,
|
|
102
|
+
waveColor: this.options.waveColor,
|
|
103
|
+
progressColor: this.options.progressColor,
|
|
104
|
+
minPxPerSec: this.options.minPxPerSec,
|
|
105
|
+
barWidth: this.options.barWidth,
|
|
106
|
+
barGap: this.options.barGap,
|
|
107
|
+
barRadius: this.options.barRadius,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
this.initPlayerEvents()
|
|
111
|
+
this.initRendererEvents()
|
|
112
|
+
this.initTimerEvents()
|
|
113
|
+
|
|
114
|
+
const url = this.options.url || this.options.media?.src
|
|
115
|
+
if (url) {
|
|
116
|
+
this.load(url, this.options.channelData, this.options.duration)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private initPlayerEvents() {
|
|
121
|
+
this.subscriptions.push(
|
|
122
|
+
this.player.on('timeupdate', () => {
|
|
123
|
+
const currentTime = this.getCurrentTime()
|
|
124
|
+
this.renderer.renderProgress(currentTime / this.duration, this.isPlaying())
|
|
125
|
+
this.emit('audioprocess', { currentTime })
|
|
126
|
+
}),
|
|
127
|
+
|
|
128
|
+
this.player.on('play', () => {
|
|
129
|
+
this.emit('play')
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
this.player.on('pause', () => {
|
|
133
|
+
this.emit('pause')
|
|
134
|
+
}),
|
|
135
|
+
|
|
136
|
+
this.player.on('canplay', () => {
|
|
137
|
+
this.emit('canplay')
|
|
138
|
+
}),
|
|
139
|
+
|
|
140
|
+
this.player.on('seeking', () => {
|
|
141
|
+
this.emit('seek', { time: this.getCurrentTime() })
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private initRendererEvents() {
|
|
147
|
+
// Seek on click
|
|
148
|
+
this.subscriptions.push(
|
|
149
|
+
this.renderer.on('click', ({ relativeX }) => {
|
|
150
|
+
const time = this.getDuration() * relativeX
|
|
151
|
+
this.seekTo(time)
|
|
152
|
+
}),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private initTimerEvents() {
|
|
157
|
+
// The timer fires every 16ms for a smooth progress animation
|
|
158
|
+
this.subscriptions.push(
|
|
159
|
+
this.timer.on('tick', () => {
|
|
160
|
+
if (this.isPlaying()) {
|
|
161
|
+
const currentTime = this.getCurrentTime()
|
|
162
|
+
this.renderer.renderProgress(currentTime / this.duration, true)
|
|
163
|
+
this.emit('audioprocess', { currentTime })
|
|
164
|
+
}
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Unmount wavesurfer */
|
|
170
|
+
public destroy() {
|
|
171
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe())
|
|
172
|
+
this.plugins.forEach((plugin) => plugin.destroy())
|
|
173
|
+
this.timer.destroy()
|
|
174
|
+
this.player.destroy()
|
|
175
|
+
this.decoder.destroy()
|
|
176
|
+
this.renderer.destroy()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Load an audio file by URL */
|
|
180
|
+
public async load(url: string, channelData?: Float32Array[], duration?: number) {
|
|
181
|
+
this.player.loadUrl(url)
|
|
182
|
+
|
|
183
|
+
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
184
|
+
if (channelData == null || duration == null) {
|
|
185
|
+
const audio = await this.fetcher.load(url)
|
|
186
|
+
const data = await this.decoder.decode(audio)
|
|
187
|
+
channelData = data.channelData
|
|
188
|
+
duration = data.duration
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.channelData = channelData
|
|
192
|
+
this.duration = duration
|
|
193
|
+
|
|
194
|
+
this.renderer.render(this.channelData, this.duration)
|
|
195
|
+
|
|
196
|
+
this.emit('ready', { duration: this.duration })
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Zoom in or out */
|
|
200
|
+
public zoom(minPxPerSec: number) {
|
|
201
|
+
if (this.channelData == null || this.duration == null) {
|
|
202
|
+
throw new Error('No audio loaded')
|
|
203
|
+
}
|
|
204
|
+
this.renderer.render(this.channelData, this.duration, minPxPerSec)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Start playing the audio */
|
|
208
|
+
public play() {
|
|
209
|
+
this.player.play()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Pause the audio */
|
|
213
|
+
public pause() {
|
|
214
|
+
this.player.pause()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Skip to a time position in seconds */
|
|
218
|
+
public seekTo(time: number) {
|
|
219
|
+
this.player.seekTo(time)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Check if the audio is playing */
|
|
223
|
+
public isPlaying(): boolean {
|
|
224
|
+
return this.player.isPlaying()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Get the duration of the audio in seconds */
|
|
228
|
+
public getDuration(): number {
|
|
229
|
+
return this.duration
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Get the current audio position in seconds */
|
|
233
|
+
public getCurrentTime(): number {
|
|
234
|
+
return this.player.getCurrentTime()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Register and initialize a plugin */
|
|
238
|
+
public registerPlugin<T extends BasePlugin<GeneralEventTypes>>(
|
|
239
|
+
CustomPlugin: new (params: WaveSurferPluginParams) => T,
|
|
240
|
+
): T {
|
|
241
|
+
const plugin = new CustomPlugin({
|
|
242
|
+
wavesurfer: this,
|
|
243
|
+
renderer: this.renderer,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
this.plugins.push(plugin)
|
|
247
|
+
|
|
248
|
+
return plugin
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export default WaveSurfer
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Player from './player.js'
|
|
2
|
+
|
|
3
|
+
class WebAudioPlayer extends Player {
|
|
4
|
+
audioCtx: AudioContext | null = null
|
|
5
|
+
sourceNode: MediaElementAudioSourceNode | null = null
|
|
6
|
+
|
|
7
|
+
destroy() {
|
|
8
|
+
this.sourceNode?.disconnect()
|
|
9
|
+
this.sourceNode = null
|
|
10
|
+
this.audioCtx?.close()
|
|
11
|
+
this.audioCtx = null
|
|
12
|
+
|
|
13
|
+
super.destroy()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
loadUrl(url: string) {
|
|
17
|
+
super.loadUrl(url)
|
|
18
|
+
|
|
19
|
+
if (!this.audioCtx) {
|
|
20
|
+
this.audioCtx = new (window.AudioContext ||
|
|
21
|
+
(window as unknown as { webkitAudioContext: AudioContext }).webkitAudioContext)({
|
|
22
|
+
latencyHint: 'playback',
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (this.sourceNode) {
|
|
27
|
+
this.sourceNode.disconnect()
|
|
28
|
+
}
|
|
29
|
+
this.sourceNode = this.audioCtx.createMediaElementSource(this.media)
|
|
30
|
+
this.sourceNode.connect(this.audioCtx.destination)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default WebAudioPlayer
|
package/src/player.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class Player {
|
|
2
|
+
protected media: HTMLMediaElement
|
|
3
|
+
private isExternalMedia = false
|
|
4
|
+
|
|
5
|
+
constructor({ media }: { media?: HTMLMediaElement }) {
|
|
6
|
+
if (media) {
|
|
7
|
+
this.media = media
|
|
8
|
+
this.isExternalMedia = true
|
|
9
|
+
} else {
|
|
10
|
+
this.media = document.createElement('audio')
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void {
|
|
15
|
+
this.media.addEventListener(event, callback)
|
|
16
|
+
return () => this.media.removeEventListener(event, callback)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
destroy() {
|
|
20
|
+
if (!this.isExternalMedia) {
|
|
21
|
+
this.media.remove()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
loadUrl(src: string) {
|
|
26
|
+
this.media.src = src
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getCurrentTime() {
|
|
30
|
+
return this.media.currentTime
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
play() {
|
|
34
|
+
this.media.play()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pause() {
|
|
38
|
+
this.media.pause()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
isPlaying() {
|
|
42
|
+
return this.media.currentTime > 0 && !this.media.paused && !this.media.ended
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
seekTo(time: number) {
|
|
46
|
+
this.media.currentTime = time
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default Player
|