feat: complete bark battle playable demo
This commit is contained in:
@@ -22,3 +22,64 @@ export function isMicrophoneApiSupported(windowLike: { isSecureContext?: boolean
|
||||
export function stopMediaStreamTracks(stream: MediaStream) {
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
|
||||
export type BrowserMicrophoneSampler = {
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
export type BrowserMicrophoneVolumeHandler = (volume: number, atMs: number) => void;
|
||||
|
||||
export async function startBrowserMicrophoneSampler(onVolume: BrowserMicrophoneVolumeHandler): Promise<BrowserMicrophoneSampler> {
|
||||
const supported = isMicrophoneApiSupported(window);
|
||||
if (!supported.ok) {
|
||||
throw Object.assign(new Error(supported.reason), { reason: supported.reason });
|
||||
}
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
|
||||
if (!AudioContextCtor) {
|
||||
stopMediaStreamTracks(stream);
|
||||
throw Object.assign(new Error('audio-context-blocked'), { reason: 'audio-context-blocked' });
|
||||
}
|
||||
const audioContext = new AudioContextCtor();
|
||||
if (audioContext.state === 'suspended') {
|
||||
await audioContext.resume();
|
||||
}
|
||||
const analyser = audioContext.createAnalyser();
|
||||
analyser.fftSize = 512;
|
||||
const source = audioContext.createMediaStreamSource(stream);
|
||||
source.connect(analyser);
|
||||
const data = new Uint8Array(analyser.fftSize);
|
||||
const sampleStartedAtMs = window.performance.now();
|
||||
let rafId = 0;
|
||||
const sample = () => {
|
||||
analyser.getByteTimeDomainData(data);
|
||||
let sum = 0;
|
||||
for (const value of data) {
|
||||
const centered = (value - 128) / 128;
|
||||
sum += centered * centered;
|
||||
}
|
||||
const volume = Math.min(1, Math.sqrt(sum / data.length) * 3.5);
|
||||
onVolume(volume, window.performance.now() - sampleStartedAtMs);
|
||||
rafId = window.requestAnimationFrame(sample);
|
||||
};
|
||||
sample();
|
||||
return {
|
||||
stop: () => {
|
||||
window.cancelAnimationFrame(rafId);
|
||||
source.disconnect();
|
||||
void audioContext.close();
|
||||
stopMediaStreamTracks(stream);
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const reason = error && typeof error === 'object' && 'reason' in error ? (error as { reason: MicrophoneFailureReason }).reason : mapGetUserMediaError(error);
|
||||
throw Object.assign(new Error(reason), { reason });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
webkitAudioContext?: typeof AudioContext;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user