wavewave
Version:
Record and stream WAV audio data in the browser across all platforms
153 lines (138 loc) • 3.81 kB
JavaScript
/**
* from: https://github.com/flo-bit/svelte-audio-visualizations/blob/main/src/lib/visualizations/wavtools/lib/audio_file_player.js
*/
import { AudioAnalysis } from "./analysis/audio_analysis.js"
/**
* Plays audio files (mp3, wav, etc.)
* @class
*/
export class AudioFilePlayer {
/**
* Creates a new AudioFilePlayer instance
* @param {{sampleRate?: number}} options
* @returns {AudioFilePlayer}
*/
constructor({ sampleRate = 44100 } = {}) {
this.sampleRate = sampleRate
this.context = null
this.analyser = null
this.source = null
this.buffer = null
this.isPlaying = false
this.startTime = 0
this.pauseTime = 0
}
/**
* Connects the audio context and enables output to speakers
* @returns {Promise<true>}
*/
async connect() {
this.context = new AudioContext({
sampleRate: this.sampleRate,
})
if (this.context.state === "suspended") {
await this.context.resume()
}
const analyser = this.context.createAnalyser()
analyser.fftSize = 8192
analyser.smoothingTimeConstant = 0.1
this.analyser = analyser
return true
}
/**
* Loads an audio file and decodes it
* @param {string|ArrayBuffer|Blob} audioInput - URL string, ArrayBuffer, or Blob of the audio file
* @returns {Promise<void>}
*/
async loadFile(audioInput) {
if (!this.context) {
await this.connect()
}
let arrayBuffer
if (typeof audioInput === "string") {
// Fetch the audio file from URL
const response = await fetch(audioInput)
arrayBuffer = await response.arrayBuffer()
} else if (audioInput instanceof Blob) {
arrayBuffer = await audioInput.arrayBuffer()
} else if (audioInput instanceof ArrayBuffer) {
arrayBuffer = audioInput
} else {
throw new Error("audioInput must be a URL string, ArrayBuffer, or Blob")
}
// Decode the audio data
this.buffer = await this.context.decodeAudioData(arrayBuffer)
}
/**
* Plays the loaded audio file
* @returns {void}
*/
play() {
if (this.isPlaying) {
return
}
if (!this.buffer) {
throw new Error("No audio buffer loaded. Please call loadFile() first.")
}
this.source = this.context.createBufferSource()
this.source.buffer = this.buffer
this.source.connect(this.analyser)
this.analyser.connect(this.context.destination)
const offset = this.pauseTime || 0
this.source.start(0, offset)
this.startTime = this.context.currentTime - offset
this.isPlaying = true
this.source.onended = () => {
this.isPlaying = false
this.pauseTime = 0
}
}
/**
* Pauses the playback
* @returns {void}
*/
pause() {
if (!this.isPlaying) {
return
}
this.source.stop()
this.pauseTime = this.context.currentTime - this.startTime
this.isPlaying = false
}
/**
* Stops the playback and resets play position
* @returns {void}
*/
stop() {
if (this.source) {
this.source.stop()
}
this.isPlaying = false
this.pauseTime = 0
}
/**
* Gets the current frequency domain data from the playing track
* @param {"frequency"|"music"|"voice"} [analysisType]
* @param {number} [minDecibels] default -100
* @param {number} [maxDecibels] default -30
* @returns {import('./analysis/audio_analysis.js').AudioAnalysisOutputType}
*/
getFrequencies(
analysisType = "frequency",
minDecibels = -100,
maxDecibels = -30,
) {
if (!this.analyser) {
throw new Error("Not connected, please call .connect() first")
}
return AudioAnalysis.getFrequencies(
this.analyser,
this.sampleRate,
null,
analysisType,
minDecibels,
maxDecibels,
)
}
}
globalThis.AudioFilePlayer = AudioFilePlayer