chrisguttandin / extendable-media-recorder

An extendable drop-in replacement for the native MediaRecorder.
MIT License
258 stars 13 forks source link

error: There was no instance of an encoder stored with the given id #641

Closed andrejhronco closed 3 years ago

andrejhronco commented 3 years ago

I'm using this is a react project where a voice is recorded passed through an fx chain and recorded again. It's unclear why the error occurs since it's not consistently reproducible. sometime it happens after a couple of recordings, sometimes not for many.

What does the error mean?

Appreciate your work on this, works great when it works

chrisguttandin commented 3 years ago

Hi @AndrejHronco,

the error occurs when some audio data is send to the worker to be encoded but no encoder is instantiated with the given id. It could be that it hasn't been instantiated yet or that it is gone already.

Does it happen in a certain browser? Internally a different strategy is used depending on what the browser supports out of the box. It's of course possible that one of those strategies is buggy while the others work as expected.

Do you re-use an instance of a MediaRecorder for multiple recordings? That wasn't supported until recently.

andrejhronco commented 3 years ago

hi @chrisguttandin thanks for the quick reply and explanation. this only seems to happen on Chrome and i'm creating a new instance of the mediarecorder for the second recording.

chrisguttandin commented 3 years ago

I know it's a stupid question but are you on the latest version? I recently fixed a bug which caused a similar issue.

If not I'm afraid I would need to see some sample code to debug this issue.

andrejhronco commented 3 years ago

yes, using the latest release of both packages:

"extendable-media-recorder": "^6.1.58",
"extendable-media-recorder-wav-encoder": "^7.0.50"
chrisguttandin commented 3 years ago

okay, that would have been too easy. Are you able to share the code which triggers the error?

andrejhronco commented 3 years ago

below is the class that uses extendable-media-recorder. The userMedia stream is captured and processed, that blob is passed to a Tone.Player --> Tone.Destination. The Destination is connected to a MediaStreamDestination node whose stream is passed to a new instance of the AudioRecorder. This is so I can record userMedia, pass it through an effects chain, and record that as well.

import { MediaRecorder, register } from 'extendable-media-recorder';
import { connect } from 'extendable-media-recorder-wav-encoder';
import axios from  'axios'

// debug
const debugType = localStorage.getItem('debug')
const debug = debugType === 'recorder'

export default class AudioRecorder {
  constructor(source) {
    this.options = { mimeType: 'audio/wav' }
    this.mimeType = { 'type': 'audio/wav' }
    this._chunks = []
    this.stream = source
  }

  _processChunks = () => {
    // build blob from chunks
    const blob = new Blob(this._chunks, this.mimeType);
    // build blob url to pass to Tone
    const url = URL.createObjectURL(blob);

    if (debug) console.log(`>> process chunks: ${blob.size}`)

    // reset chunks
    this._chunks = [];
    // reset media recorder
    this.mediaRecorder = null

    return {
      url: url,
      blob: blob
    }
  }

  init = async () => {
    await register(await connect());
  }

  start = () => {
    try {
      this.mediaRecorder = new MediaRecorder(this.stream, this.options);
    } catch (error) {
      console.error(`>> Couldn't get media stream: ${error.toString()}`);
    }

    // start recording user media
    this.mediaRecorder.start();

    return new Promise((resolve, reject) => {
      try {
        // process blob
        this.mediaRecorder.ondataavailable = (e) => {
          this._chunks.push(e.data);

          const processed = this._processChunks()
          // return processed blob when ready
          resolve(processed)
        }
      } catch (error) {
        reject(error)
      }
    })
  }

  stop = () => {
    if (this.mediaRecorder.state === 'recording') this.mediaRecorder.stop()
  }
}
chrisguttandin commented 3 years ago

Thanks @AndrejHronco, does that mean you run two instances of the MediaRecorder at the same time? If the error occurs does it happen when starting the MediaRecorder, when stopping the MediaRecorder or sometime in between?

andrejhronco commented 3 years ago

Two separate instances, first captures the clean audio, then a new instance is created right after for the effects version. The error occurs most frequently right after stop is called or shortly afterwards. The error occurs on line 49 of module.ts

chrisguttandin commented 3 years ago

I finally managed to reproduce the bug. Well, at least I reproduced and fixed a bug. I hope it was the one that you reported. :-)

Chrome sometimes fires more than one dataavailable event after the MediaRecoder gets stopped. It appears to be more like when running multiple recorders at the same time.

The problem should be fixed in v6.1.59. Many thanks for reporting it.

andrejhronco commented 3 years ago

Fantastic! works well every time now. Thank you for your excellent work Chris.