wavesurfer.js 6.5.0 → 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.
Files changed (132) hide show
  1. package/.eslintrc.json +24 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +26 -117
  4. package/dist/base-plugin.d.ts +10 -0
  5. package/dist/base-plugin.js +13 -0
  6. package/dist/decoder.d.ts +10 -0
  7. package/dist/decoder.js +43 -0
  8. package/dist/event-emitter.d.ts +11 -0
  9. package/dist/event-emitter.js +21 -0
  10. package/dist/fetcher.d.ts +4 -0
  11. package/dist/fetcher.js +17 -0
  12. package/dist/index.d.ts +92 -0
  13. package/dist/index.js +166 -0
  14. package/dist/player-webaudio.d.ts +8 -0
  15. package/dist/player-webaudio.js +31 -0
  16. package/dist/player.d.ts +16 -0
  17. package/dist/player.js +40 -0
  18. package/dist/plugins/regions.d.ts +48 -0
  19. package/dist/plugins/regions.js +183 -0
  20. package/dist/renderer.d.ts +34 -0
  21. package/dist/renderer.js +193 -0
  22. package/dist/timer.d.ts +10 -0
  23. package/dist/timer.js +17 -0
  24. package/examples/audio.ogg +0 -0
  25. package/examples/bars.js +19 -0
  26. package/examples/basic.js +8 -0
  27. package/examples/gradient.js +28 -0
  28. package/examples/regions.js +63 -0
  29. package/examples/video.js +19 -0
  30. package/examples/webaudio.js +14 -0
  31. package/package.json +19 -99
  32. package/src/base-plugin.ts +20 -0
  33. package/src/decoder.ts +41 -0
  34. package/src/event-emitter.ts +35 -0
  35. package/src/fetcher.ts +7 -0
  36. package/src/index.ts +252 -0
  37. package/src/player-webaudio.ts +34 -0
  38. package/src/player.ts +50 -0
  39. package/src/plugins/regions.ts +240 -0
  40. package/src/renderer.ts +250 -0
  41. package/src/timer.ts +27 -0
  42. package/tsconfig.json +105 -0
  43. package/tutorial/index.html +47 -0
  44. package/tutorial/src/editor.js +70 -0
  45. package/tutorial/src/init.js +66 -0
  46. package/tutorial/src/url.js +25 -0
  47. package/tutorial/style.css +211 -0
  48. package/yarn-error.log +1049 -0
  49. package/LICENSE +0 -29
  50. package/dist/plugin/wavesurfer.cursor.js +0 -418
  51. package/dist/plugin/wavesurfer.cursor.js.map +0 -1
  52. package/dist/plugin/wavesurfer.cursor.min.js +0 -7
  53. package/dist/plugin/wavesurfer.cursor.min.js.map +0 -1
  54. package/dist/plugin/wavesurfer.elan.js +0 -381
  55. package/dist/plugin/wavesurfer.elan.js.map +0 -1
  56. package/dist/plugin/wavesurfer.elan.min.js +0 -7
  57. package/dist/plugin/wavesurfer.elan.min.js.map +0 -1
  58. package/dist/plugin/wavesurfer.markers.js +0 -520
  59. package/dist/plugin/wavesurfer.markers.js.map +0 -1
  60. package/dist/plugin/wavesurfer.markers.min.js +0 -7
  61. package/dist/plugin/wavesurfer.markers.min.js.map +0 -1
  62. package/dist/plugin/wavesurfer.mediasession.js +0 -181
  63. package/dist/plugin/wavesurfer.mediasession.js.map +0 -1
  64. package/dist/plugin/wavesurfer.mediasession.min.js +0 -7
  65. package/dist/plugin/wavesurfer.mediasession.min.js.map +0 -1
  66. package/dist/plugin/wavesurfer.microphone.js +0 -466
  67. package/dist/plugin/wavesurfer.microphone.js.map +0 -1
  68. package/dist/plugin/wavesurfer.microphone.min.js +0 -7
  69. package/dist/plugin/wavesurfer.microphone.min.js.map +0 -1
  70. package/dist/plugin/wavesurfer.minimap.js +0 -440
  71. package/dist/plugin/wavesurfer.minimap.js.map +0 -1
  72. package/dist/plugin/wavesurfer.minimap.min.js +0 -7
  73. package/dist/plugin/wavesurfer.minimap.min.js.map +0 -1
  74. package/dist/plugin/wavesurfer.playhead.js +0 -300
  75. package/dist/plugin/wavesurfer.playhead.js.map +0 -1
  76. package/dist/plugin/wavesurfer.playhead.min.js +0 -7
  77. package/dist/plugin/wavesurfer.playhead.min.js.map +0 -1
  78. package/dist/plugin/wavesurfer.regions.js +0 -1342
  79. package/dist/plugin/wavesurfer.regions.js.map +0 -1
  80. package/dist/plugin/wavesurfer.regions.min.js +0 -7
  81. package/dist/plugin/wavesurfer.regions.min.js.map +0 -1
  82. package/dist/plugin/wavesurfer.spectrogram.js +0 -736
  83. package/dist/plugin/wavesurfer.spectrogram.js.map +0 -1
  84. package/dist/plugin/wavesurfer.spectrogram.min.js +0 -7
  85. package/dist/plugin/wavesurfer.spectrogram.min.js.map +0 -1
  86. package/dist/plugin/wavesurfer.timeline.js +0 -649
  87. package/dist/plugin/wavesurfer.timeline.js.map +0 -1
  88. package/dist/plugin/wavesurfer.timeline.min.js +0 -7
  89. package/dist/plugin/wavesurfer.timeline.min.js.map +0 -1
  90. package/dist/wavesurfer-html-init.js +0 -381
  91. package/dist/wavesurfer-html-init.js.map +0 -1
  92. package/dist/wavesurfer-html-init.min.js +0 -7
  93. package/dist/wavesurfer-html-init.min.js.map +0 -1
  94. package/dist/wavesurfer.js +0 -6145
  95. package/dist/wavesurfer.js.map +0 -1
  96. package/dist/wavesurfer.min.js +0 -7
  97. package/dist/wavesurfer.min.js.map +0 -1
  98. package/src/drawer.canvasentry.js +0 -427
  99. package/src/drawer.js +0 -413
  100. package/src/drawer.multicanvas.js +0 -617
  101. package/src/html-init.js +0 -241
  102. package/src/mediaelement-webaudio.js +0 -77
  103. package/src/mediaelement.js +0 -443
  104. package/src/peakcache.js +0 -127
  105. package/src/plugin/cursor/index.js +0 -350
  106. package/src/plugin/elan/index.js +0 -307
  107. package/src/plugin/markers/index.js +0 -436
  108. package/src/plugin/mediasession/index.js +0 -94
  109. package/src/plugin/microphone/index.js +0 -415
  110. package/src/plugin/minimap/index.js +0 -403
  111. package/src/plugin/playhead/index.js +0 -226
  112. package/src/plugin/regions/index.js +0 -478
  113. package/src/plugin/regions/region.js +0 -883
  114. package/src/plugin/spectrogram/fft.js +0 -222
  115. package/src/plugin/spectrogram/index.js +0 -522
  116. package/src/plugin/timeline/index.js +0 -600
  117. package/src/util/absMax.js +0 -16
  118. package/src/util/clamp.js +0 -11
  119. package/src/util/fetch.js +0 -222
  120. package/src/util/frame.js +0 -13
  121. package/src/util/get-id.js +0 -22
  122. package/src/util/index.js +0 -14
  123. package/src/util/max.js +0 -16
  124. package/src/util/min.js +0 -16
  125. package/src/util/observer.js +0 -144
  126. package/src/util/orientation.js +0 -98
  127. package/src/util/prevent-click.js +0 -19
  128. package/src/util/request-animation-frame.js +0 -15
  129. package/src/util/silence-mode.js +0 -42
  130. package/src/util/style.js +0 -16
  131. package/src/wavesurfer.js +0 -1797
  132. 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": "6.5.0",
4
- "description": "Interactive navigable audio visualization using Web Audio and Canvas",
5
- "main": "dist/wavesurfer.js",
6
- "directories": {
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
- "start": "npm run build:plugins && npm run build:htmlinit && webpack serve --config ./build-config/webpack.dev.main.js",
15
- "start:htmlinit": "npm run build:main && npm run build:plugins && webpack serve --config ./build-config/webpack.dev.htmlinit.js",
16
- "start:plugins": "npm run build:main && npm run build:htmlinit && webpack serve --config ./build-config/webpack.dev.plugins.js",
17
- "build": "npm run build:normal && npm run build:minified",
18
- "build:main": "webpack --config ./build-config/webpack.prod.main.js",
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": "BSD-3-Clause",
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
- "@babel/core": "^7.20.12",
59
- "@babel/eslint-parser": "^7.19.1",
60
- "@babel/plugin-proposal-class-properties": "^7.18.6",
61
- "@babel/preset-env": "^7.20.2",
62
- "@babel/register": "^7.18.9",
63
- "@chiragrupani/karma-chromium-edge-launcher": "^2.2.2",
64
- "babel-loader": "^9.1.2",
65
- "babel-plugin-add-module-exports": "^1.0.4",
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
@@ -0,0 +1,7 @@
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
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