Запись аудио в браузере
Ольга Маланова
<embed>

<audio>

Плагины




Воспроизведение аудио
Web Audio API

const AudioContext = window.AudioContext || window.webkitAudioContext
const audioCtx = new AudioContext()
const sourceNode = audioCtx.createMediaStreamSource(stream)
const gainNode = audioCtx.createGain()
const finishNode = audioCtx.destination
sourceNode.connect(gainNode);
gainNode.connect(finishNode)
Web Audio API

const AudioContext = window.AudioContext || window.webkitAudioContext
const audioCtx = new AudioContext()
const sourceNode = audioCtx.createMediaStreamSource(stream)
const gainNode = audioCtx.createGain()
const finishNode = audioCtx.destination
sourceNode.connect(gainNode);
gainNode.connect(finishNode)



Доступ к потоку медиаданных

<input type="file"/>
<input type="file" accept="audio/*;capture=microphone">

browser


Android



iOS

getUserMedia

<device type="media" onchange="update(this.data)"></device>
<audio></auidio>
<script>
function update(stream) {
document.querySelector('audio').src = stream.url;
}
</script>
<device>

getUserMedia
async getMedia = () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
successCallback(stream)
} catch (err) {
handleError(err)
}
}
async getDevices = () => {
try {
const devices = await navigator.mediaDevices.enumerateDevices()
devices.map(device => { const { label, kind, deviceInfo) = device } // kind === 'audioinput'
} catch (err) {
handleError(err)
}
}
}
Пользовательский интерфейс



Обработка и сохранение


MediaStream
ondataavailable
Blob
MediaRecorder API
MediaRecorder
export default {
name: 'MediaRecorderComponent',
data () {
return {
mediaRecorder: null
isAudioAccessReceived: false,
isRecording: false,
chunks: [],
audio: this.$refs.audio
}
},
methods: {
toggleRecord () {
if (!this.isAudioAccessReceived) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
this.mediaRecorder = new window.MediaRecorder(stream, { type: 'audio/ogg' })
this.mediaRecorder.ondataavailable = () => this.chunks.push(event.data)
mediaRecorder.onstop = (e) => {
const blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
this.chunks = [];
const audioURL = window.URL.createObjectURL(blob);
audio.src = audioURL;
}
this.isAudioAccessReceived = true
} catch (e) { /* handle error */ }
}
if (!this.isRecording) {
this.mediaRecorder.start()
} else {
this.mediaRecorder.stop()
}
this.isRecording = !this.isRecording
}
}
}
MediaRecorder API
export default {
name: 'MediaRecorderComponent',
data () {
return {
mediaRecorder: null
isAudioAccessReceived: false,
isRecording: false,
chunks: [],
audio: this.$refs.audio
}
},
methods: {
toggleRecord () {
if (!this.isAudioAccessReceived) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
this.mediaRecorder = new window.MediaRecorder(stream, { type: 'audio/ogg' })
this.mediaRecorder.ondataavailable = () => this.chunks.push(event.data)
mediaRecorder.onstop = (e) => {
const blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
this.chunks = [];
const audioURL = window.URL.createObjectURL(blob);
audio.src = audioURL;
}
this.isAudioAccessReceived = true
} catch (e) { /* handle error */ }
}
if (!this.isRecording) {
this.mediaRecorder.start()
} else {
this.mediaRecorder.stop()
}
this.isRecording = !this.isRecording
}
}
}
MediaRecorder API
MediaRecorder Support

Мобильная история

Android




iOS



Движки

Chrome on iOS

Определение API
export default async ({ app, Vue, store }) => {
if (!navigator.mediaDevices) navigator.mediaDevices = {}
if (!navigator.mediaDevices.getUserMedia) {
var getUserMedia = (
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.getUserMedia
)
if (getUserMedia) {
navigator.mediaDevices.getUserMedia = function (constraints) {
return new Promise(function (resolve, reject) {
getUserMedia(constraints,
function (stream) { resolve(stream) },
function (error) { reject(error) }
)
})
}
} else {
navigator.mediaDevices.getUserMedia = function () {
return new Promise(function (resolve, reject) {
reject('getUserMedia is not supported in this browser.')
})
}
}
}
}
Альтернативы

RecorderJS
export default {
name: 'AudioRecorder',
data () { return { recorder: null, audioContext: null } },
created () { this.audioContext = new this.AudioContextClass() },
beforeDestroy () { this.audioContext = null },
methods: {
toggleRecord () {
if (this.isRecording) {
this.isRecording = false
setTimeout(() => {
this.recorder.stop()
this.stream.getAudioTracks()[0].stop()
this.recorder.exportWAV(finalUserData => { successCallback(finalUserData) })
}, 700)
} else {
navigator.mediaDevices.getUserMedia({
audio: true
})
.then((stream) => {
this.recorder = new window.Recorder(
this.audioContext.createMediaStreamSource(stream), {
numChannels: 1, type: 'audio/wav'
}
)
this.recorder.record()
this.isRecording = true
})
.catch(err => {
console.error('The following getUserMedia error occured: ' + err)
})
}
},
}
}
WebAudioRecorder
let webAudioRecorder = new WebAudioRecorder(source, {
workerDir: 'web_audio_recorder_js/',
encoding: 'mp3',
options: {
encodeAfterRecord: true,
mp3: { bitRate: '320' }
}
});
webAudioRecorder.onComplete = (webAudioRecorder, blob) => {
/* on complete handler */
}
webAudioRecorder.onError = (webAudioRecorder, err) => {
/* on error handler */
}
webAudioRecorder.startRecording();
webAudioRecorder.finishRecording();
MediaStreamRecorder
const mediaConstraints = {
audio: true
}
navigator.getUserMedia(mediaConstraints, onMediaSuccess, onMediaError)
function onMediaSuccess(stream) {
/* media success handler */
}
function onMediaError(e) {
console.error('media error', e)
}
mediaRecorder.stop()
mediaRecorder.start()
mediaRecorder.save()

RecordRTC
let stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
let recorder = new RecordRTCPromisesHandler(stream, {
type: 'video'
});
recorder.startRecording();
const sleep = m => new Promise(r => setTimeout(r, m));
await sleep(3000);
await recorder.stopRecording();
let blob = await recorder.getBlob();
invokeSaveAsDialog(blob);

AudioRecorderPolyfill

window.MediaRecorder = require('audio-recorder-polyfill')
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
recorder = new MediaRecorder(stream)
recorder.addEventListener('dataavailable', e => {
audio.src = URL.createObjectURL(e.data)
})
recorder.start()
})

WebRTC adapter
Готовые компоненты

VueAudioRecorder
/* boot/audioRecorder.js */
import AudioRecorder from 'vue-audio-recorder'
export default ({ Vue }) => {
Vue.use(AudioRecorder)
}

VueAudioRecorder
/* MyVueAudioRecorder.vue */
<template>
<audio-recorder
upload-url="some url"
:attempts="3"
:time="2"
:before-recording="callback"
:after-recording="callback"
:before-upload="callback"
:successful-upload="callback"
:failed-upload="callback"></audio-recorder>
</template>
<script>
export default {
name: 'MyVueAudioRecorder',
methods: {
callback (msg) {
console.debug('Event: ', msg)
}
}
}
</script>
Полезные ссылки
- https://github.com/yandex/audio-js/blob/master/tutorial/sound.md
- https://developers.google.com/web/fundamentals/media/recording-audio
- https://gist.github.com/borismus/1032746
- https://github.com/mattdiamond/Recorderjs
- https://github.com/higuma/web-audio-recorder-js
- https://recordrtc.org/
- https://github.com/streamproc/MediaStreamRecorder
- https://developer.apple.com/app-store/review/guidelines/
- https://github.com/ai/audio-recorder-polyfill
- https://webrtc.github.io/samples/
- https://webaudioapi.com/samples/
Спасибо за внимание!
tg @omalanova
Copy of Запись аудио в браузере
By Olga Malanova
Copy of Запись аудио в браузере
- 461