button.addEventListener("click", startRecording);
async function startRecording() {
// 保留用户点击产生的 user activation。
const streamPromise = window.toy.requestMicrophone();
const stream = await streamPromise;
// Toy SDK 返回 WebRTC 远端音轨;静音播放只负责启动解码。
const sink = document.createElement("audio");
sink.autoplay = true;
sink.muted = true;
sink.playsInline = true;
sink.srcObject = stream;
document.body.appendChild(sink);
await sink.play();
return { stream, sink };
}
const context = new AudioContext();
await context.resume();
const source = context.createMediaStreamSource(stream);
const analyser = context.createAnalyser();
analyser.fftSize = 512;
source.connect(analyser);
const samples = new Float32Array(analyser.fftSize);
function readDbfs() {
analyser.getFloatTimeDomainData(samples);
let sumSquares = 0;
for (const sample of samples) sumSquares += sample * sample;
const rms = Math.sqrt(sumSquares / samples.length);
return Math.max(-60, 20 * Math.log10(Math.max(rms, 0.001)));
}
const chunks = [];
const destination = context.createMediaStreamDestination();
source.connect(destination);
const recordingStream = destination.stream;
const recorder = new MediaRecorder(recordingStream);
recorder.ondataavailable = (event) => {
if (event.data.size > 0) chunks.push(event.data);
};
recorder.onstop = () => {
const blob = new Blob(chunks, { type: recorder.mimeType });
const recordingUrl = URL.createObjectURL(blob);
audioPlayer.src = recordingUrl;
playbackButton.onclick = () => audioPlayer.play();
};
recorder.start(250);
async function releaseMedia(stream, context, sink) {
sink.pause();
sink.srcObject = null;
sink.remove();
await context.close();
try {
await window.toy.stopMedia(stream);
} catch {
stream.getTracks().forEach((track) => track.stop());
}
}
function clearRecording(recordingUrl) {
audioPlayer.pause();
audioPlayer.removeAttribute("src");
URL.revokeObjectURL(recordingUrl);
}