Запись аудио в браузере

Ольга Маланова

<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>

navigator

объект для получения различной информации о браузере, сетевом соединении, операционной системе и т.д.

MediaDevices 

наследник EventHandler, входная точка в API, используется для определения и доступа к медиа устройствам, доступным для агента пользователя

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
    }
  }
}
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 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>

Полезные ссылки

Спасибо за внимание!

tg @omalanova

Made with Slides.com