• Das Erstellen neuer Accounts wurde ausgesetzt. Bei berechtigtem Interesse bitte Kontaktaufnahme über die üblichen Wege. Beste Grüße der Admin

[FRAGE] ‎OffscreenCanvas + WebWorker Problem

xorg1990

New member
Hi, habe noch ein größeres Problem.

Ich erhalte immer diese Fehlermeldung: multiChannelAnalyser.js:115 Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it was detached.


Ok, ich kann also nur das OffScreencanvas einmal den webworker zuweisen.

Aber wie bekomme ich meine FFT Daten in den worker, es gibt doch nur ein message event oder kann ich eigene events erstellen.

z.B self.addEventListener('meinEigenesEvent', function(e) {}

Aktuell ist der worker code so:
Code:
var workerID;

self.addEventListener('message', function(e) {
	if(e.data.workerID){
		workerID = e.data.workerID;
	}else{
	let canvas = e.data.canvas;
  	let context = canvas.getContext("2d");
	let  timeDomainData = e.data.fftTimeData;
	let width = canvas.width;
	let height = canvas.height;


  	  context.fillStyle = "rgba(51, 51, 51, 0.4)";
      context.fillRect(0, 0, width, height);

      context.strokeStyle = "#1abc9c";
      context.beginPath();
      for (let i = 0, imax = timeDomainData.length; i < imax; i++) {
        let x = linlin(i, 0, imax, 0, width);
        let y = linlin(timeDomainData[i], -1, 1, height, 0);

        context.lineTo(x, y);
      }
      context.stroke();
      context.commit();
  }
});

function linlin(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) / (inMax - inMin) * (outMax - outMin) + outMin;
}

und aufgerufen wird er so
Code:
  		function draw(){
  			for(let key in analysers){
  				let offscreen = spectrums[key];
  				let analyser =  analysers[key];
  				let worker = renderWorkers[key];
  				let TimeData = new Float32Array(analyser.frequencyBinCount)
  				analyser.getFloatTimeDomainData(TimeData);
  				worker.postMessage({workerID: null, canvas: offscreen, fftTimeData : TimeData}, [offscreen]);
  			}
  		}

aussehen müsste der worker code in etwa so:

Code:
    let context;
  let width;
  let height;

self.addEventListener('message', function(e) {
	if(e.data.workerID){
		workerID = e.data.workerID;
	}else if(e.data.workerID){
	let canvas = e.data.canvas;
  context = canvas.getContext("2d");
	width = canvas.width;
	height = canvas.height;
  }else{
      let  timeDomainData = e.data.fftTimeData;
  	  context.fillStyle = "rgba(51, 51, 51, 0.4)";
      context.fillRect(0, 0, width, height);

      context.strokeStyle = "#1abc9c";
      context.beginPath();
      for (let i = 0, imax = timeDomainData.length; i < imax; i++) {
        let x = linlin(i, 0, imax, 0, width);
        let y = linlin(timeDomainData[i], -1, 1, height, 0);

        context.lineTo(x, y);
      }
      context.stroke();
      context.commit();
  }
});

Ein zweites Message event wäre mir lieber geht das?
 
Zuletzt bearbeitet:
Ich erhalte immer diese Fehlermeldung: multiChannelAnalyser.js:115 Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it was detached.


Ok, ich kann also nur das OffScreencanvas einmal den webworker zuweisen.
ich würde vermuten überhaupt nicht, jedenfalls laut fehlermeldung.
entweder du erzeugst das OffscreenCanvas erst im worker oder du clonst es nicht
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)

Aber wie bekomme ich meine FFT Daten in den worker, es gibt doch nur ein message event oder kann ich eigene events erstellen.
???
über die data-eigenschaft des event-objekts, so wie du es machst, wo ist dein problem???
 
Ich klone überhaupt nichts, ich erzeuge ganz normal ein canvas im dom und teile den worker das mit was mir transferControlToOffscreen() zurück gibt .

Das steht in diesem Object let offscreen = spectrums[key];


Das worker script schaut nun so aus:

Code:
var workerID;
var context;
var width;
var height;
self.addEventListener('message', function(e) {
	if(e.data.workerID){
		workerID = e.data.workerID;
	}else if(e.data.canvas){
	let canvas = e.data.canvas;
  	context = canvas.getContext("2d");
	width = canvas.width;
	height = canvas.height;
  }else{
      let  timeDomainData = e.data.fftTimeData;
  	  context.fillStyle = "rgba(51, 51, 51, 0.4)";
      context.fillRect(0, 0, width, height);

      context.strokeStyle = "#1abc9c";
      context.beginPath();
      for (let i = 0, imax = timeDomainData.length; i < imax; i++) {
        let x = linlin(i, 0, imax, 0, width);
        let y = linlin(timeDomainData[i], -1, 1, height, 0);

        context.lineTo(x, y);
      }
      context.stroke();
      context.commit();
  }
});
function linlin(value, inMin, inMax, outMin, outMax) {
      return (value - inMin) / (inMax - inMin) * (outMax - outMin) + outMin;
}

Was mich stört ist diser if Else baum:
Code:
	if(e.data.workerID){
		workerID = e.data.workerID;
	}else if(e.data.canvas){
	let canvas = e.data.canvas;
  	context = canvas.getContext("2d");
	width = canvas.width;
	height = canvas.height;
  }else{

Dafür hätte ich gerne separate onmessage funktionen im worker, geht das.

Zum Beispiel: self.addEventListener('setWorkerID', function(e) {.....

Wenn ich jeden key einzeln überprüfen muss ist dann extrem umständlich.
 
Ich klone überhaupt nichts, ich erzeuge ganz normal ein canvas im dom und teile den worker das mit was mir transferControlToOffscreen() zurück gibt .
und alles was du einem worker sendest wird geklont
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Transferring_data_to_and_from_workers_further_details

Was mich stört ist diser if Else baum:

Code:
worker.postMessage({func: "workerID", data: {workerID: 99}});
worker.postMessage({func: "onCanvas", data: {canvas: canvas, width = canvas.width}});

Code:
function onWorkerID(data)
{
  
}

function onCanvas(data)
{
  
}

this.addEventListener('message', function(e)
{
  switch (e.data.func)
  {
  case "workerID";
     onWorkerID(e.data.data);
     break;
    case "onCanvas";
     // oder so auch ohne switch/case, dann aber fehleranfälliger
     this[e.data.func](e.data.data);
     break;
  default:
    // err
  }
});

- - - Aktualisiert - - -

oder du clonst es nicht
ok, das hab ich übersehen
Code:
worker.postMessage({workerID: null, canvas: offscreen, fftTimeData : TimeData}, [offscreen]);
dann sollte das aber gehen

- - - Aktualisiert - - -

achso, dann ist aber die frage, wieso du schreibst
Ok, ich kann also nur das OffScreencanvas einmal den webworker zuweisen.
das sind doch alles eigene OffScreencanvas?
 
Code:
function onWorkerID(data)
{
  
}

function onCanvas(data)
{
  
}

this.addEventListener('message', function(e)
{
  switch (e.data.func)
  {
  case "workerID";
     onWorkerID(e.data.data);
     break;
    case "onCanvas";
     // oder so auch ohne switch/case, dann aber fehleranfälliger
     this[e.data.func](e.data.data);
     break;
  default:
    // err
  }
});
Hm ok, auch nicht so das was ich mir vorgestellt habe, aber immerhin. Dachte man kann direkt auf Funktionen in workern zugreifen.

tsseh schrieb:
das sind doch alles eigene OffScreencanvas?
Ja sind es.

Ich erstelle pro Audio Kanal mehrere canvas im dom, für jeden Kanal wird eine eigenes Spektrum gerendert im worker.
Bei einer 7.1 Übertragung ist das eine menge an Daten. 8 mal FFT berechnen und rendern. Da kommt die ‎OffscreenCanvas wie gerufen.

Hier mal spaß sichtshalber die komplette klasse, wenn du noch Verbesserungs ideen hast immer raus damit.
Code:
function multiChannelAnalyser(audioContext,Channels ,fftSize){//leanftBank alle boxen links, center  alle boxen front, rightbank alle boxen rechts
		const numOfChannels = Channels;
		const splitter = audioContext.createChannelSplitter(numOfChannels);
		splitter.channelCountMode = "explicit";
		splitter.channelCount = numOfChannels;
  		let analysers = {};
  		let renderWorkers = {};


  		for(let i=0; i<numOfChannels;i++){
  			let channelID;
  			switch(i){
  			  case 1://channel 1 right channel 
		        generateCanvas("right");
		        channelID = "right";
		      break;
		       case 2: //channel 2 center channel 
		        generateCanvas("center");
		        channelID = "center";
		      break;
		       case 3: //channel 3 LFE channel 
		        generateCanvas("LFE");
		        channelID = "LFE";
		      break;
		       case 4: //channel 4 links hinten
		        generateCanvas("leftSourroundBack");
		        channelID = "leftSourroundBack";
		      break;
		       case 5://channel 5 rechts hinten
		        generateCanvas("rightSourroundBack");
		        channelID = "rightSourroundBack";
		      break;
		       case 6://channel  6 links mitte
		        generateCanvas("leftSourround");
		        channelID = "leftSourround";
		      break;
		       case 7://channel  7 rechts mitte
		         generateCanvas("rightSourround");
		        channelID = "rightSourround";
		      break;
		      //channel 0 left channel 
		       default: generateCanvas("left");
		       channelID = "left";
  			}

   			analysers[channelID] = audioContext.createAnalyser();
  			analysers[channelID].fftSize = fftSize;
  			analysers[channelID].channelCountMode = "explicit";
  			analysers[channelID].channelCount = 1;
  			
  			splitter.connect(analysers[channelID], i, 0);//channel i zu channel 0
  		}

  		function generateCanvas(chStr){
  			//<canvas style="background-color: rgba(51, 51, 51, 0.4); width=320; height=160; id="'+chStr.trim()+'" width="320" height="160"
  			let canvas = $("<canvas></canvas>").css({
					backgroundColor: "rgba(51, 51, 51, 0.4)"
  			}).attr({
  					id: chStr,
  				    width : 320,
					height: 160
  			});

			let worker = renderWorkers[chStr] = new Worker('workerRenderScript.js');
  			//renderWorkers[channelID].addEventListener('message', onMsg, false);
        	renderWorkers[chStr].addEventListener('error', onError, false);
        	renderWorkers[chStr].postMessage({workerID: chStr, fftTimeData : null});
        	

  			switch(chStr){
  			  case "right"://channel 1 right channel 
  			  		$("#centerBank").prepend(canvas);
		      break;
		       case "center": //channel 2 center channel 
		        	$("#center_LFE").append(canvas);
		      break;
		       case "LFE": //channel 3 LFE channel
		           $("#center_LFE").append(canvas);
		      break;
		       case "leftSourroundBack": //channel 4 links hinten
		       		$("#leftBank").append(canvas);
		      break;
		       case "rightSourroundBack"://channel 5 rechts hinten
		       		$("#rightBank").append(canvas);
		      break;
		       case "leftSourround"://channel  6 links mitte
		       		$("#leftBank").append(canvas);
		      break;
		       case "rightSourround"://channel  7 rechts mitte
		       		$("#rightBank").append(canvas);
		      break;
		      //channel 0 left channel 
		       default: $("#centerBank").prepend(canvas);
  			}
  			let offscreen = document.querySelector("#"+chStr).transferControlToOffscreen();
  			worker.postMessage({workerID: null, canvas: offscreen, fftTimeData : null}, [offscreen]);
  		}

/*
  		function createElementFromHTML(htmlString) {
		  let div = document.createElement('div');
		  div.innerHTML = htmlString.trim();
		  // Change this to div.childNodes to support multiple top-level nodes
		  return div.firstChild; 
		}
*/
  		function connectSrc(src){
			src.connect(splitter);
  		}

  		function draw(){
  			for(let key in analysers){
  				let analyser =  analysers[key];
  				let worker = renderWorkers[key];
  				let TimeData = new Float32Array(analyser.frequencyBinCount)
  				analyser.getFloatTimeDomainData(TimeData);
  				worker.postMessage({workerID: null, canvas: null, fftTimeData : TimeData});
  			}
  		}

  		function onError(e){
  			console.log("Worker Error:", e);
  		}

  		return {
  			connect : connectSrc,
  			draw : draw
  		};

  	}
 
Zurück
Oben