Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to stopAndUnloadAsync a recording that just got started #1709

Closed
James2516 opened this issue Apr 28, 2018 · 15 comments · Fixed by #9877
Closed

Unable to stopAndUnloadAsync a recording that just got started #1709

James2516 opened this issue Apr 28, 2018 · 15 comments · Fixed by #9877

Comments

@James2516
Copy link

Reproducible Demo

https://snack.expo.io/rJ6j67G6f

After calling await recording.startAsync(), it needs at least ~300ms before recording.stopAndUnloadAsync() can be called, otherwise it'll fail and subsequent startAsync calls will result in

error: Only one Recording object can be prepared at a given time.

Tested on Android.

@Kumagor0
Copy link

Kumagor0 commented Feb 3, 2019

I am having the same problem with Expo 32.0.2 on Android.

If I call recording.stopAndUnloadAsync() less than 120ms after recording.startAsync() is resolved, I get the following error:

Stop encountered an error: recording not stopped
- node_modules/react-native/Libraries/BatchedBridge/NativeModules.js:95:55 in <unknown>
- node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:397:4 in __invokeCallback
- ... 4 more stack frames from framework internals

@pexea12
Copy link

pexea12 commented May 22, 2019

I am facing the same issue no matter how long I wait.

@nhim175
Copy link

nhim175 commented Jun 17, 2019

I'm facing this issue too. It says: Cannot unload a Recording that has not been prepared.

@kashsbd
Copy link

kashsbd commented Sep 20, 2019

hello guys, I face the same error. Did you solve the error ?

@cwilby
Copy link

cwilby commented Jan 23, 2020

I had this issue after getting overly ES6.

const { recording: { stopAndUnloadAsync } } = this;

await stopAndUnloadAsync(); // causes error

vs

await this.recording.stopAndUnloadAsync();

This allows stopAndUnloadAsync to correctly evaluate this._canRecord.

@galzate-blumer
Copy link

Some updates about this issue? I'm having this issue on latest version and I haven't way to stop a recording

@kjanjuha
Copy link

I'm still having this issue too. Any solutions? It is something to do with the fact that its all asynchronous

@tanbt
Copy link

tanbt commented Aug 15, 2020

I also got this problem so far, stopAndUnloadAsync just keep waiting, no additional message. Same problem on IOS.

@tanbt
Copy link

tanbt commented Aug 19, 2020

I made it work by seriously consider the docs

Note that only one recorder is allowed to exist in the state between prepareToRecordAsync and stopAndUnloadAsync at any given time.

by introducing a hook for recording instance const [recording, setRecording] = useState<Audio.Recording>();.

Here's the full source: https://gist.github.com/tanbt/857da772db304c0f1562b28c823a0777 (just for the recording feature).

@tanbt
Copy link

tanbt commented Aug 19, 2020

@IjzerenHein So I can say this isn't a bug, but the docs' example can be improved
https://docs.expo.io/versions/latest/sdk/audio/#example-2

@IjzerenHein
Copy link
Contributor

Thanks for investigating this @tanbt! We'll have a closer look at this!

@IjzerenHein IjzerenHein self-assigned this Aug 20, 2020
@IjzerenHein IjzerenHein added the AV label Aug 20, 2020
@IjzerenHein
Copy link
Contributor

IjzerenHein commented Aug 21, 2020

Hi! I've taken a closer look at this and updated the snack to work with SDK 38 and add some additional error handling.

https://snack.expo.io/@ijzerenhein/43e79d

What appears to be going on, is that when stopAndUnloadAsync is called too quickly after start, it fails with the error recording not stopped. In the snack, if you were to hit "Record" again after that it would throw the Only one Recording object can be prepared at a given time. error because the previous recording has not been cleaned up correctly.

After digging around, it seems that the MediaRecorder class on Android will purposely cause stop to fail when the recording has not yet received any valid recording data. This article gives a good explanation: https://www.hiren.dev/2017/01/android-media-recorder-stop-failed-1007.html

  1. You called stop method too early.

According to documentation on MediaRecorder.java class it says

Stops recording. Call this after start(). Once recording is stopped, you will have to configure it again as if it has just been constructed. Note that a RuntimeException is intentionally thrown to the application, if no valid audio/video data has been received when stop() is called. This happens if stop() is called immediately after start(). The failure lets the application take action accordingly to clean up the output file (delete the output file, for instance), since the output file is not properly constructed when this happens.

That means if you start recording and in couple of seconds you call stop recording there is no significant data of recording hence stop method throws an exception. So you must wait for sometime. From my testing I found out that you should at least for minimum 10 seconds. So the solution is to have stop method called inside try catch block and handle the exception properly or do not allow user to stop recording till 10 seconds of start of recording. You can either disable stop button and make it enable after sometime. Once the exception is thrown, you should clean the the resources, release media recorder and camera and all other objects.

So in short, this behaviour is sort of by design in Android. But unfortunately expo-av is not handling this case or giving an appropriate error. We'll have a look on how to address this.

@Aryk
Copy link

Aryk commented Oct 21, 2020

For anyone who needs this fix on managed until the SDK 40 release comes out...

          try {
            await recordRef.current.stopAndUnloadAsync();
          } catch (e) {
            if (e.message.includes("Stop encountered an error: recording not stopped")) {
              await ExponentAV.unloadAudioRecorder();
              await recordRef.current._cleanupForUnloadedRecorder({durationMillis: 0} as any);
            } else {
              await handleError(e, {userMessage: "An error occurred stopping the recording."});
            }
          } finally {
            recordRef.current = undefined;
            timerStop();
            success = true;
          }

@jasonrichdarmawan
Copy link

@Aryk could you tell me more about ExponentAV.unloadAudioRecorder();?

I use await recordingInstance._cleanupForUnloadedRecorder(); and it solves the problem.

  /**
   * // TODO: fix Error: Cannot unload a Recording that has already been unloaded. || Error: Cannot unload a Recording that has not been prepared.
   * Steps to reproduce: Spam the 'Press to record the audio' button
   * Severity: Minor
   *
   * https://github.com/expo/expo/issues/1709
   */
  const recorderStop = async () => {
    await new Promise((resolve) => setTimeout(resolve, 1000)); // handle Error: Stop encountered an error: recording not stopped
    const status = await recorder.getStatusAsync();
    if (status.isRecording === true) {
      try {
        await recorder.stopAndUnloadAsync();
        console.log("recorder stopped");
      } catch (error) {
        if (
          error.message.includes(
            "Stop encountered an error: recording not stopped"
          )
        ) {
          await recorder._cleanupForUnloadedRecorder({
            canRecord: false,
            durationMillis: 0,
            isRecording: false,
            isDoneRecording: false,
          });
          console.log(`recorderStop() error handler: ${error}`);
        } else if (
          error.message.includes(
            "Cannot unload a Recording that has already been unloaded."
          ) ||
          error.message.includes(
            "Error: Cannot unload a Recording that has not been prepared."
          )
        ) {
          console.log(`recorderStop() error handler: ${error}`);
        } else {
          console.error(`recorderStop(): ${error}`);
        }
      }
    }
  };

@Aryk
Copy link

Aryk commented Dec 6, 2020

@Aryk could you tell me more about ExponentAV.unloadAudioRecorder();?

I use await recordingInstance._cleanupForUnloadedRecorder(); and it solves the problem.

Look at the stopAndUnloadAsync function. I added it because I saw it there as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.