At a different extension test I am able to fetch() directly, without using externally_connectable, which is what I am trying to do.
On the chrome://extension URL I am able to fetch() localhost:port directly at console, avoiding messaging (text, not stream) altogether
.then(r => r.body).then(async (readable) => {
const initial = 1; // 6.4KiB (65536)
const maximum = 500; // 32 KiB
let readOffset = 0;
let writeOffset = 0;
let duration = 0;
let init = false;
// TODO process odd length Uint8Array without writing to Memory
const memory = new WebAssembly.Memory({
initial,
maximum,
shared: true,
});
const ac = new AudioContext({
sampleRate: 22050,
latencyHint: 0,
});
await ac.suspend();
const msd = new MediaStreamAudioDestinationNode(ac, {
channelCount: 1,
});
const { stream } = msd;
const [track] = stream.getAudioTracks();
const osc = new OscillatorNode(ac, { frequency: 0 });
const processor = new MediaStreamTrackProcessor(track);
const generator = new MediaStreamTrackGenerator({ kind: 'audio' });
const { writable } = generator;
const { readable: audioReadable } = processor;
const audioWriter = writable.getWriter();
const mediaStream = new MediaStream([generator]);
const audioReader = audioReadable.getReader();
const source = new MediaStreamAudioSourceNode(ac, {mediaStream});
source.connect(ac.destination);
osc.connect(msd);
osc.start();
track.onmute = track.onunmute = track.onended = (e) => console.log(e);
// const recorder = new MediaRecorder(mediaStream);
// recorder.ondataavailable = ({ data }) => console.log(URL.createObjectURL(data));
// recorder.start();
return await Promise.all([
readable.pipeTo(
new WritableStream({
async write(value, c) {
console.log(
`Uint8Array.buffer.byteLength: ${value.buffer.byteLength}`
);
if (readOffset + value.byteLength > memory.buffer.byteLength) {
console.log(
`memory.buffer.byteLength before grow(): ${memory.buffer.byteLength}.`
);
memory.grow(3);
console.log(
`memory.buffer.byteLength after grow(): ${memory.buffer.byteLength}`
);
}
let sab = new Int8Array(memory.buffer);
let i = 0;
if (!init) {
init = true;
i = 44;
}
for (; i < value.buffer.byteLength; i++, readOffset++) {
if (readOffset + 1 >= memory.buffer.byteLength) {
console.log(
`memory.buffer.byteLength before grow() for loop: ${memory.buffer.byteLength}.`
);
memory.grow(3);
console.log(
`memory.buffer.byteLength after grow() for loop: ${memory.buffer.byteLength}`
);
sab = new Int8Array(memory.buffer);
}
sab[readOffset] = value[i];
}
},
close() {
console.log('Done writing input stream.');
},
})
),
audioReader.read().then(async function process({ value, done }) {
// avoid clipping start of MediaStreamTrackGenerator output
if (ac.currentTime < value.buffer.duration * 100) {
return audioWriter
.write(value)
.then(() => audioReader.read().then(process));
}
if (writeOffset > readOffset) {
// avoid clipping end of MediaStreamTrackGenerator output
if (ac.currentTime < duration + value.buffer.duration * 200) {
return audioReader.read().then(process);
} else {
msd.disconnect();
osc.disconnect();
source.disconnect();
track.stop();
audioReader.releaseLock();
await audioReadable.cancel();
audioWriter.releaseLock();
generator.stop();
await ac.close();
console.log(
`readOffset: ${readOffset}, writeOffset: ${writeOffset}, duration: ${duration}, ac.currentTime: ${ac.currentTime}`
, source.mediaStream.getTracks()[0], track, processor, generator);
return await Promise.all([
new Promise((resolve) => (stream.oninactive = resolve)),
new Promise((resolve) => (ac.onstatechange = resolve)),
]);
}
}
const { timestamp } = value;
const int8 = new Int8Array(440);
const sab = new Int8Array(memory.buffer);
for (let i = 0; i < 440; i++) {
int8[i] = sab[writeOffset];
++writeOffset;
}
const int16 = new Int16Array(int8.buffer);
const floats = new Float32Array(220);
for (let i = 0; i < int16.length; i++) {
const int = int16[i];
// If the high bit is on, then it is a negative number, and actually counts backwards.
const float =
int >= 0x8000 ? -(0x10000 - int) / 0x8000 : int / 0x7fff;
floats[i] = float;
}
const buffer = new AudioBuffer({
numberOfChannels: 1,
length: floats.length,
sampleRate: 22050,
});
buffer.copyToChannel(floats, 0, 0);
duration += buffer.duration;
const frame = new AudioFrame({ timestamp, buffer });
return audioWriter.write(frame).then(() => {
return audioReader.read().then(process);
});
}),
, ac.resume()]);
}).then(console.log, console.error);