console.log('Done writing input stream.');
try {
this.recorder.requestData();
this.recorder.stop();
this.msd.disconnect();
this.osc.disconnect();
this.track.stop();
await this.audioWriter.close();
await this.audioWriter.closed;
this.generator.stop();
await this.ac.close();
} catch (err) {
console.error(err);
} finally {
console.log(
`readOffset:${this.readOffset}, duration:${this.duration}, ac.currentTime:${this.ac.currentTime}`,
`generator.readyState:${this.generator.readyState}, audioWriter.desiredSize:${this.audioWriter.desiredSize}`,
`inputController.desiredSize:${this.inputController.desiredSize}, ac.state:${this.ac.state}`
);
}
},
})
)
.catch(console.warn),
this.audioReadable
.pipeTo(
new WritableStream({
abort(e) {
console.error(e.message);
},
write: async ({ timestamp }) => {
const uint8 = new Int8Array(441 * 4);
const { value, done } = await this.inputReader.read();
if (!done) uint8.set(new Int8Array(value));
const uint16 = new Uint16Array(uint8.buffer);
//
https://stackoverflow.com/a/35248852 const channels = [new Float32Array(441), new Float32Array(441)];
for (let i = 0, j = 0, n = 1; i < uint16.length; i++) {
const int = uint16[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;
// deinterleave
channels[(n = ++n % 2)][!n ? j++ : j - 1] = float;
}
const data = new Float32Array(882);
data.set(channels.shift(), 0);
data.set(channels.shift(), 441);
const frame = new AudioData({
timestamp,
data,
sampleRate: 44100,
format: 'f32-planar',
numberOfChannels: 2,
numberOfFrames: 441,
});
this.duration += frame.duration;
await this.audioWriter.write(frame);
},
close: () => {
console.log('Done reading input stream.');
},
})
)
.catch((e) => {
console.error(e);
}),
this.ac.resume(),
]);
return this.promise;
} catch (err) {
console.error(err);
}
}
}
audioStream = new AudioStream(
`parec -d @DEFAULT_MONITOR@`
);
// audioStream.mediaStream: live MediaStream
audioStream
.start()
.then(async (ab) => {
// ab: ArrayBuffer representation of WebM file from MediaRecorder
console.log(audioStream);
const blob = new Blob([ab], { type: 'audio/webm;codecs=opus' });
console.log(URL.createObjectURL(blob));
const {origin} = audioStream.src;
globalThis.audioStream = AudioStream = null;
let permission = await navigator.permissions.query({
name: 'notifications',
});
if (permission.state !== 'granted') {
permission = await Notification.requestPermission();
}
if (permission.state === 'granted' || permission === 'granted') {
const saveFileNotification = new Notification('Save file?', {
body: `Click "Activate" to download captured system audio recording.`,
icon: `${origin}/download_music_icon.png`,
});
saveFileNotification.onclick = async (e) => {
try {
const handle = await showSaveFilePicker({
startIn: 'music',
suggestedName: 'recording' + new Date().getTime() + '.webm',
});
console.log(handle);
const writable = await handle.createWritable();
const writer = writable.getWriter();
await writer.write(blob);
await writer.close();
} catch (err) {
console.error(err);
}
};
saveFileNotification.onshow = async (e) => {
await new Promise((resolve) => setTimeout(resolve, 1000 * 30));
e.target.close();
};
}
})
.catch(console.error);
}
fetch(url, {method:'post', body:<ReadableStrem|command as string>, signal:abortSignal})
.then((r) => r.body.pipeTo(...))
in PHP
stream_set_blocking($input = fopen("php://input", "r"), 0);
passthru(stream_get_contents($input))
getting fetch() to work cross-origin is not. I would prefer to not use an iframe at all, but alas that is what works.