mirror of
https://github.com/kevin-DL/vue-audio-recorder.git
synced 2026-01-11 02:44:29 +00:00
Add WAV support
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
- Records limit
|
||||
- A lot of callbacks
|
||||
- Individual an audio player
|
||||
- MP3 support
|
||||
- MP3/WAV support
|
||||
|
||||
### Tested in (desktop)
|
||||
|
||||
@@ -35,9 +35,10 @@ npm i vue-audio-recorder --save
|
||||
| attempts | Number | Number of recording attempts |
|
||||
| headers | Object | HTTP headers |
|
||||
| time | Number | Time limit for the record (minutes) |
|
||||
| bit-rate | Number | Default: 128 |
|
||||
| bit-rate | Number | Default: 128 (only for MP3) |
|
||||
| sample-rate | Number | Default: 44100 |
|
||||
| filename | String | Download/Upload filename |
|
||||
| format | String | WAV/MP3. Default: mp3 |
|
||||
| upload-url | String | URL for uploading |
|
||||
| show-download-button | Boolean | If it is true show a download button. Default: true |
|
||||
| show-upload-button | Boolean | If it is true show an upload button. Default: true |
|
||||
@@ -106,7 +107,6 @@ npm run build
|
||||
## TODO
|
||||
|
||||
- Clear record list
|
||||
- Return WAV format
|
||||
- Responsive design
|
||||
|
||||
## Authors
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
<audio-recorder v-if="showRecorder"
|
||||
upload-url="some url"
|
||||
filename="ninja"
|
||||
format="wav"
|
||||
:attempts="3"
|
||||
:time="2"
|
||||
:headers="headers"
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
const type = this.record.blob.type.split('/')[1]
|
||||
const link = document.createElement('a')
|
||||
link.href = this.record.url
|
||||
link.download = `${this.filename}.mp3`
|
||||
link.download = `${this.filename}.${type}`
|
||||
link.click()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +348,8 @@
|
||||
pauseRecording : this.pauseRecording,
|
||||
micFailed : this.micFailed,
|
||||
bitRate : this.bitRate,
|
||||
sampleRate : this.sampleRate
|
||||
sampleRate : this.sampleRate,
|
||||
format : this.format
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -27,9 +27,9 @@ export default class {
|
||||
this.dataBuffer = []
|
||||
|
||||
return {
|
||||
id : Date.now(),
|
||||
blob : blob,
|
||||
url : URL.createObjectURL(blob)
|
||||
id : Date.now(),
|
||||
blob : blob,
|
||||
url : URL.createObjectURL(blob)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Encoder from './encoder'
|
||||
import Mp3Encoder from './mp3-encoder'
|
||||
import WavEncoder from './wav-encoder'
|
||||
import { convertTimeMMSS } from './utils'
|
||||
|
||||
export default class {
|
||||
@@ -7,8 +8,9 @@ export default class {
|
||||
this.pauseRecording = options.pauseRecording
|
||||
this.afterRecording = options.afterRecording
|
||||
this.micFailed = options.micFailed
|
||||
this.format = options.format
|
||||
|
||||
this.lameOptions = {
|
||||
this.encoderOptions = {
|
||||
bitRate : options.bitRate,
|
||||
sampleRate : options.sampleRate
|
||||
}
|
||||
@@ -22,6 +24,8 @@ export default class {
|
||||
this.duration = 0
|
||||
this.volume = 0
|
||||
|
||||
this.wavSamples = []
|
||||
|
||||
this._duration = 0
|
||||
}
|
||||
|
||||
@@ -44,8 +48,8 @@ export default class {
|
||||
this.isPause = false
|
||||
this.isRecording = true
|
||||
|
||||
if (!this.lameEncoder) {
|
||||
this.lameEncoder = new Encoder(this.lameOptions)
|
||||
if (this._isMp3() && !this.lameEncoder) {
|
||||
this.lameEncoder = new Mp3Encoder(this.encoderOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +59,20 @@ export default class {
|
||||
this.processor.disconnect()
|
||||
this.context.close()
|
||||
|
||||
const record = this.lameEncoder.finish()
|
||||
let record = null
|
||||
|
||||
if (this._isMp3()) {
|
||||
record = this.lameEncoder.finish()
|
||||
} else {
|
||||
let wavEncoder = new WavEncoder({
|
||||
bufferSize : this.bufferSize,
|
||||
sampleRate : this.encoderOptions.sampleRate,
|
||||
samples : this.wavSamples
|
||||
})
|
||||
record = wavEncoder.finish()
|
||||
this.wavSamples = []
|
||||
}
|
||||
|
||||
record.duration = convertTimeMMSS(this.duration)
|
||||
this.records.push(record)
|
||||
|
||||
@@ -98,7 +115,11 @@ export default class {
|
||||
const sample = ev.inputBuffer.getChannelData(0)
|
||||
let sum = 0.0
|
||||
|
||||
this.lameEncoder.encode(sample)
|
||||
if (this._isMp3()) {
|
||||
this.lameEncoder.encode(sample)
|
||||
} else {
|
||||
this.wavSamples.push(new Float32Array(sample))
|
||||
}
|
||||
|
||||
for (let i = 0; i < sample.length; ++i) {
|
||||
sum += sample[i] * sample[i]
|
||||
@@ -115,4 +136,8 @@ export default class {
|
||||
_micError (error) {
|
||||
this.micFailed && this.micFailed(error)
|
||||
}
|
||||
|
||||
_isMp3 () {
|
||||
return this.format.toLowerCase() === 'mp3'
|
||||
}
|
||||
}
|
||||
|
||||
65
src/lib/wav-encoder.js
Normal file
65
src/lib/wav-encoder.js
Normal file
@@ -0,0 +1,65 @@
|
||||
export default class {
|
||||
constructor (options) {
|
||||
this.bufferSize = options.bufferSize || 4096
|
||||
this.sampleRate = options.sampleRate
|
||||
this.samples = options.samples
|
||||
}
|
||||
|
||||
finish () {
|
||||
this._joinSamples()
|
||||
|
||||
let buffer = new ArrayBuffer(44 + this.samples.length * 2)
|
||||
let view = new DataView(buffer)
|
||||
|
||||
this._writeString(view, 0, 'RIFF') // RIFF identifier
|
||||
view.setUint32(4, 36 + this.samples.length * 2, true) // RIFF chunk length
|
||||
this._writeString(view, 8, 'WAVE') // RIFF type
|
||||
this._writeString(view, 12, 'fmt ') // format chunk identifier
|
||||
view.setUint32(16, 16, true) // format chunk length
|
||||
view.setUint16(20, 1, true) // sample format (raw)
|
||||
view.setUint16(22, 1, true) // channel count
|
||||
view.setUint32(24, this.sampleRate, true) // sample rate
|
||||
view.setUint32(28, this.sampleRate * 4, true) // byte rate (sample rate * block align)
|
||||
view.setUint16(32, 4, true) // block align (channel count * bytes per sample)
|
||||
view.setUint16(34, 16, true) // bits per sample
|
||||
this._writeString(view, 36, 'data') // data chunk identifier
|
||||
view.setUint32(40, this.samples.length * 2, true) // data chunk length
|
||||
|
||||
this._floatTo16BitPCM(view, 44, this.samples)
|
||||
|
||||
const blob = new Blob([view], {type: 'audio/wav'})
|
||||
|
||||
return {
|
||||
id : Date.now(),
|
||||
blob : blob,
|
||||
url : URL.createObjectURL(blob)
|
||||
}
|
||||
}
|
||||
|
||||
_floatTo16BitPCM (output, offset, input) {
|
||||
for (let i = 0; i < input.length; i++, offset += 2) {
|
||||
let s = Math.max(-1, Math.min(1, input[i]))
|
||||
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
|
||||
}
|
||||
}
|
||||
|
||||
_joinSamples () {
|
||||
let recordLength = this.samples.length * this.bufferSize
|
||||
let joinedSamples = new Float64Array(recordLength)
|
||||
let offset = 0
|
||||
|
||||
for (let i = 0; i < this.samples.length; i++) {
|
||||
let sample = this.samples[i]
|
||||
joinedSamples.set(sample, offset)
|
||||
offset += sample.length
|
||||
}
|
||||
|
||||
this.samples = joinedSamples
|
||||
}
|
||||
|
||||
_writeString (view, offset, string) {
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export default {
|
||||
props: {
|
||||
filename : { type: String, default: 'record' },
|
||||
format : { type: String, default: 'mp3' },
|
||||
headers : { type: Object, default: () => ({}) },
|
||||
uploadUrl : { type: String }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user