Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems disconnecting nodes with AudioContext (Web Audio API)

I'm building a component that shows information about a video's audio. I use the AudioContext interface to get audio samples from a HTML5 video element. It works fine the first time I create the component, but when the component is unmounted and then recreated at a later point, I get the following error message:

Uncaught InvalidStateError: Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode.

Here's how I get the audio:

const video = document.querySelectorAll('video')[0]

if (!window.audioContext) {
  window.audioContext = new (window.AudioContext || window.webkitAudioContext)
}

if (!this.source && !this.scriptNode) {
  this.source = window.audioContext.createMediaElementSource(video)
  this.scriptNode = window.audioContext.createScriptProcessor(4096, 1, 1)
}

this.scriptNode.onaudioprocess = (evt) => {
 // Processing audio works fine...
}

this.source.connect(this.scriptNode)
this.scriptNode.connect(window.audioContext.destination)

And when the component is unmounted I do:

if (this.source && this.scriptNode) {
  this.source.disconnect(this.scriptNode)
  this.scriptNode.disconnect(window.audioContext.destination)
}

I thought this would put me in a state where I can safely create and connect new nodes. But the next time the component is mounted, this block throws the error mentioned earlier:

if (!this.source && !this.scriptNode) {
  this.source = window.audioContext.createMediaElementSource(video) // this throws the error
  this.scriptNode = window.audioContext.createScriptProcessor(4096, 1, 1)
}

I could get it to work by making everything global, i.e. putting source and scriptNode on window rather than this. But that won't work if my video element changes. What's the correct way to do this?

like image 922
tobiasandersen Avatar asked Jul 19 '16 14:07

tobiasandersen


1 Answers

You're not destroying the node you created with context.createMediaSourceElement. You have your on page video element which hasn't changed, all you've done is disconnected the video audio stream from your audio graph. Therefore the video element is still bound to an AudioNode, in this case 'this.source'. Instead of trying to recreate source just detect if the source is already defined.

if (this.source == undefined) {
  // Build element
  this.source = window.audioContext.createMediaElementSource(video);
}

this.scriptNode = window.audioContext.createScriptProcessor(4096, 1, 1)
this.source.connect(this.scriptNode);
this.scriptNode.connect(window.audioContext.destination);
like image 189
Nick Jillings Avatar answered Oct 04 '22 14:10

Nick Jillings