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

destroy() method throws DOMException error #3637

Closed
akici opened this issue Apr 6, 2024 · 8 comments · Fixed by #3641
Closed

destroy() method throws DOMException error #3637

akici opened this issue Apr 6, 2024 · 8 comments · Fixed by #3641
Labels

Comments

@akici
Copy link

akici commented Apr 6, 2024

Bug description

destroy() method throws the following DOMException since v7.7.6. Related Commit. Looks like it's related to AbortController object.

Environment

  • Browser: Chrome Version 123.0.6312.87 (Official Build) (arm64)
  • OS: MacOS Sonoma 14.1.2 (23B92)
  • Framework: Angular v17.3.0

Stack trace

DOMException: signal is aborted without reason
    at _u.destroy (http://localhost:4000/@fs/Users/ayberk.akici/Projects/doctors-copilot-front-app-new/.angular/cache/17.3.3/vite/deps/wavesurfer__js.js?v=0576242c:913:87)
    at _WaveSurferService.<anonymous> (http://localhost:4000/main.js:1576:24)
    at Generator.next (<anonymous>)
    at http://localhost:4000/main.js:37:61
    at new ZoneAwarePromise (http://localhost:4000/polyfills.js:2849:21)
    at __async (http://localhost:4000/main.js:21:10)
    at _WaveSurferService.destroy (http://localhost:4000/main.js:1571:12)
    at _RecorderService.<anonymous> (http://localhost:4000/main.js:2100:36)
    at Generator.next (<anonymous>)
    at http://localhost:4000/main.js:37:61
    at new ZoneAwarePromise (http://localhost:4000/polyfills.js:2849:21)
    at __async (http://localhost:4000/main.js:21:10)
    at mediaRecorder.onstop (http://localhost:4000/main.js:2097:39)
(anonymous) @ console.js:36
api.onUnhandledError @ zone.js:1049
handleUnhandledRejection @ zone.js:1071
api.microtaskDrainDone @ zone.js:1065
drainMicroTaskQueue @ zone.js:589
Promise.then (async)
nativeScheduleMicroTask @ zone.js:558
scheduleMicroTask @ zone.js:569
scheduleTask @ zone.js:392
scheduleTask @ zone.js:217
scheduleMicroTask @ zone.js:237
scheduleResolveOrReject @ zone.js:1250
resolvePromise @ zone.js:1187
(anonymous) @ zone.js:1103
(anonymous) @ zone.js:1119
Promise.then (async)
(anonymous) @ zone.js:1503
ZoneAwarePromise @ zone.js:1425
Ctor.then @ zone.js:1502
(anonymous) @ fetch.js:43
(anonymous) @ wavesurfer.esm.js:15
(anonymous) @ wavesurfer.esm.js:15
ZoneAwarePromise @ zone.js:1425
t @ wavesurfer.esm.js:15
fetchBlob @ wavesurfer.esm.js:15
(anonymous) @ wavesurfer.esm.js:15
(anonymous) @ wavesurfer.esm.js:15
ZoneAwarePromise @ zone.js:1425
t @ wavesurfer.esm.js:15
loadAudio @ wavesurfer.esm.js:15
(anonymous) @ wavesurfer.esm.js:15
(anonymous) @ wavesurfer.esm.js:15
ZoneAwarePromise @ zone.js:1425
t @ wavesurfer.esm.js:15
load @ wavesurfer.esm.js:15
o2 @ record.js:15
e2.onstop @ record.js:15

Minimal code snippet

this.mediaRecorder.onstop = () => {
    this.recorderState.update('STOPPED');
    this.countUpTimerService.pause();
    this.waveSurferService.destroy();
    this.stopAudioTracks();
 }

export class WaveSurferService {
  waveSurfer: WaveSurfer | undefined;
  waveSurferRecordPlugin!: RecordPlugin | undefined;

  startWaveSurferRecorderMic(
    elem: HTMLElement,
    recordHandlerFn: (ms: MediaStream) => Promise<void>,
    deviceId = ''
  ): void {
    if (!this.waveSurfer || !this.waveSurferRecordPlugin) {
      this.initWaveSurfer(elem);
    }
    this.waveSurferRecordPlugin
      ?.startMic({ deviceId })
      .then(async stream => {
        await recordHandlerFn(stream);
      })
      .catch(error => {
        console.error('Error accessing microphone:', error);
        throw error;
      });
  }

 destroy(): void {
    this.waveSurferRecordPlugin?.stopMic();
    this.waveSurferRecordPlugin?.stopRecording();
    this.waveSurferRecordPlugin?.destroy();
    this.waveSurfer?.stop();
    this.waveSurfer?.destroy();
    this.waveSurfer = undefined;
    this.waveSurferRecordPlugin = undefined;
  }
  
private initWaveSurfer(elem: HTMLElement): void {
    this.waveSurfer = WaveSurfer.create({
      container: elem,
      waveColor: '#3D83DF',
      progressColor: 'transparent',
      barWidth: 4.5,
      barGap: 10,
      height: 200,
      barRadius: 4
    });
    this.waveSurferRecordPlugin = this.waveSurfer.registerPlugin(RecordPlugin.create());
  }
}

Expected result

Not having exception

Obtained result

DOMException

Screenshots

Screenshot 2024-04-06 at 18 09 25
@akici akici added the bug label Apr 6, 2024
@katspaugh
Copy link
Owner

Thanks @akici!

@wfk007 we should probably only abort if there’s an ongoing fetch, wdyt? Also load probably shouldn’t throw an error in that case.

@wfk007
Copy link
Contributor

wfk007 commented Apr 7, 2024

Thanks @akici!

@wfk007 we should probably only abort if there’s an ongoing fetch, wdyt? Also load probably shouldn’t throw an error in that case.

@katspaugh

  • If the fetch is completed, abortController.abort() will not throw an error. AbortController only aborts the asynchronous operation before it has completed.
  • the throwing error seems come from this PR:
image

@akici can you provide me a minimum reproducible repo to check if the abort error is expected? This PR will help you save network bandwidth and even help you avoid the risk of memory leaks, at least this is one of the reasons for the memory leak problem in my system.

we can focus on the following:

  1. Is the aborted operation in your system expected or not?
  2. Do we need to catch this exception on wavesurfer or the user side?

@akici
Copy link
Author

akici commented Apr 8, 2024

@wfk007 Here is the code example.
Steps to reproduce the error

  1. Start recording
  2. Stop recording

@wfk007
Copy link
Contributor

wfk007 commented Apr 8, 2024

@akici @katspaugh I figured out the reason:

  1. click start and the waveSurferRecordPlugin started
  2. wavesurfer continuously loads channelData
  3. click stop and the wavesurfer destroyed
  4. abortController.abort: No exception will occur at this time but the abortController is forever aborted.
  5. waveSurferRecordPlugin stopped and triggered the emitWithBlob function call
  6. wavesurfer.load function call with blob URL
  7. Fetcher with an aborted signal and throw err in load
image

@wfk007
Copy link
Contributor

wfk007 commented Apr 8, 2024

@katspaugh I opened a pr to fix it: https://github.com/katspaugh/wavesurfer.js/pull/3641/files, Please help me review it at your convenience.

@katspaugh
Copy link
Owner

I've released 7.7.10 with @wfk007's fix. @akici please let us know if it fixed it for you. Cheers!

@akici
Copy link
Author

akici commented Apr 8, 2024

I've released 7.7.10 with @wfk007's fix. @akici please let us know if it fixed it for you. Cheers!

Works like a charm, cheers @katspaugh!

@akici
Copy link
Author

akici commented Apr 8, 2024

wfk007

Thank you! @wfk007

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

Successfully merging a pull request may close this issue.

3 participants