chrisguttandin / extendable-media-recorder

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

error when calling recorder.stop() #658

Closed abhijeet-toptal closed 1 month ago

abhijeet-toptal commented 2 years ago

Uncaught (in promise) Error: Another request was made to initiate an encoding. at Worker. (module.ts:49:1)

chrisguttandin commented 2 years ago

Unfortunately this kind of error is difficult to reproduce. It indicates that somehow two encodings where started where there should only be one. Could you please provide a minimal example which reproduces the error?

It may also depend on the browser. It's therefore interesting to know if this is happening in all browsers or only in a certain one.

Rashair commented 1 year ago

I encountered similar error when I was trying to start a recorder on an inactive stream - this was an issue with React hooks on my part. This also may be cause by strict mode rendering twice if your hooks are not properly built.

chrisguttandin commented 1 year ago

Hi @Rashair, thanks for letting me know.

Did that happen only in one browser or in all browsers? If it was only one which one was it? I'm curious because the code path is different depending on the browser.

Rashair commented 1 year ago

I'm sure it happened in Chrome, I don't think I've tested it on other browsers.

chrisguttandin commented 1 year ago

Could that be an issue with hot module replacement?

I'm not too familiar with the way it works. But I could imagine the error can occur when the Web Worker which does the actual encoding is kept alive but the main thread code somehow gets refreshed. The result would be that their shared state gets out of sync.

FDufBos commented 8 months ago

I've had this issue using next 14 with the pages router. Will ssr cause it?

chrisguttandin commented 7 months ago

To be honest, I don't know. I'm happy to debug this further with a minimal test case to reproduce the error. But without a way to reproduce it, it's very hard to know what's actually going on.

seamory commented 6 months ago

I face the same issue. and following is my code.


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

function init() {
  connect().then(async (v) => {
    await register(v)
  })
}

init()

export class WavRecorder {
  private readonly recorder: IMediaRecorder
  private readonly blob: Promise<Blob>
  private readonly audioContext: AudioContext
  private readonly mediaStreamAudioSourceNode: MediaStreamAudioSourceNode
  private readonly mediaStreamAudioDestinationNode: MediaStreamAudioDestinationNode

  constructor(stream: MediaStream) {
    this.audioContext = new AudioContext({ sampleRate: 16000 }); // Force sample rate to 16000.
    this.mediaStreamAudioSourceNode = new MediaStreamAudioSourceNode(this.audioContext, { mediaStream: stream });
    this.mediaStreamAudioDestinationNode = new MediaStreamAudioDestinationNode(this.audioContext, {
      channelCount: 1, // Force channel to 1. If channel count is 2, voice recognize may be error.
      channelCountMode: "explicit", // Apply channelCount value.
    });
    this.mediaStreamAudioSourceNode.connect(this.mediaStreamAudioDestinationNode);

    const chunks: Blob[] = []

    const mediaRecorder = new MediaRecorder(this.mediaStreamAudioDestinationNode.stream, { mimeType: 'audio/wav' });
    this.blob = new Promise<Blob>((resolve) => {
      mediaRecorder.onstop = (evt) => {
        const blob = new Blob(chunks, { type: mediaRecorder.mimeType })
        resolve(blob)
      }
    })
    mediaRecorder.ondataavailable = (evt) => {
      chunks.push(evt.data)
    }
    mediaRecorder.start(1000)
    this.recorder = mediaRecorder
  }

  async stop() {
    this.mediaStreamAudioDestinationNode.disconnect()
    this.mediaStreamAudioSourceNode.disconnect()
    this.audioContext.close()
    this.recorder.stop()
    return await this.blob
  }
}
chrisguttandin commented 6 months ago

@seamory How do you run this code? Do you use SSR or hot reloading as mentioned above?

seamory commented 6 months ago

@seamory How do you run this code? Do you use SSR or hot reloading as mentioned above?

I run this code on browser platform with edge latest and chrome latest, not use SSR. Actually it happened during development and hot reloading is working during development. But I confuse why the issue could be also happened when I open the development url address at first time or I refreshed the page.

chrisguttandin commented 6 months ago

@seamory Could you add breakpoints or log something when the MediaRecorder gets created and when you call stop(). I'm curious if this happens to be out of order for some reason.

bschelling commented 4 months ago

@chrisguttandin the hot module replacement is a great suspect - I ran into this a couple of times during development using vue.js but didn't really bother. I'll debug it.

chrisguttandin commented 3 months ago

Thanks, @bschelling for taking a stab. Did your debugging reveal anything suspicious?

Jrfidellis commented 1 month ago

I'm facing this issue, but it only seems to happen when I call mediaRecorder.stop() just after mediaRecorder.start() is called.

@chrisguttandin From these logs you can see that the error happens consistently when stop is called around 1000ms after start.

Has errors:

start recording
connect took 1.60
stream took 157.60
STOPED. Time since started: 309.40
**Uncaught (in promise) Error: Another request was made to initiate an encoding.**

start recording
connect took 0.50
stream took 126.90
STOPED. Time since started: 686.90
**Uncaught (in promise) Error: Another request was made to initiate an encoding.**

start recording
connect took 1.10
stream took 146.90
STOPED. Time since started: 952.90
**Uncaught (in promise) Error: Another request was made to initiate an encoding.**

No errors:

start recording
connect took 0.40
stream took 129.90
STOPED. Time since started: 2026.10

start recording
connect took 0.70
stream took 140.60
STOPED. Time since started: 1641.30

start recording
connect took 0.50
stream took 141.70
STOPED. Time since started: 1078.30

start recording
connect took 1.30
stream took 162.20
STOPED. Time since started: 1082.70

This is the code (React hook):


type MicListener = (base64Audio: string, sampleRate: number, last: boolean) => void

var startTime: number

export const useMicStream = (
    config: () => void,
    onData: MicListener
) => {
    const [mediaRecorder, setMediaRecorder] = useState<IMediaRecorder>()

    const start = (ratio: number) => {
        console.log('start recording')
        if (mediaRecorder) {
            throw new Error('MediaRecorder is already initialized')
        }

        startTime = performance.now()
        Promise.all([
            navigator.mediaDevices.getUserMedia({
                audio: true, video: false
            }),
            connect().then((port) => {
                if (!MediaRecorder.isTypeSupported('audio/wav')) {
                    return register(port).then(() => {
                        console.log('register took', performance.now() - startTime)
                    })
                }
                console.log('connect took', performance.now() - startTime)
            })
        ]).then(([stream]) => {
            console.log('stream took', performance.now() - startTime)
            const mediaRecorder = new MediaRecorder(stream, {
                mimeType: 'audio/wav',
            })

            config()
            setMediaRecorder(mediaRecorder)

            mediaRecorder.ondataavailable = (event) => {}

            startTime = performance.now()
            mediaRecorder.start(ratio)
        }).catch((error) => {
            console.error('userMedia ', error)
        })
    }

    const stop = () => {
        console.log('STOPED. Time since started: ', performance.now() - startTime)
        mediaRecorder?.stop()
        setMediaRecorder(undefined)
    }

    return {
        start,
        stop,
        isRecording: mediaRecorder?.state === 'recording',
    }
}
chrisguttandin commented 1 month ago

Thanks @Jrfidellis for providing the example code. I was able to reproduce the problem. As you said it only happened when stopping the MediaRecorder early on. And it only happened when using the timeslice parameter which is also the case in the example code provided by @seamory.

The problem was caused by a race condition. It was possible that the MediaRecorder was stopped already when the first part of the recording was requested. Which in turn triggered the unhandled error.

The problem should be fixed now in v9.2.9. Thank you all for your contributions.