This commit is contained in:
Arjun Patel
2026-06-01 14:15:22 -07:00
parent 2d9b4805e0
commit 580703fdf6
21 changed files with 302 additions and 220 deletions
@@ -21,42 +21,45 @@ export function useAudioSource(
>(new WeakMap());
useEffect(() => {
if (!source) {
setAudioSource(null);
return;
}
// Build the source node (creating an AudioContext as needed) plus optional
// teardown for contexts we own; the branches converge on one setState.
let result: AudioSource | null = null;
let cleanup: (() => void) | undefined;
if (source instanceof MediaStream) {
const ctx = new AudioContext();
ctx.resume();
const sourceNode = ctx.createMediaStreamSource(source);
setAudioSource({ sourceNode, ctx });
return () => {
ctx.close();
};
result = { sourceNode, ctx };
cleanup = () => ctx.close();
} else if (source) {
// HTMLAudioElement — createMediaElementSource can only be called once per
// element, so reuse a cached context/node when we have one.
const cached = elementSourceCache.current.get(source);
if (cached) {
cached.ctx.resume();
result = cached;
} else {
const ctx = new AudioContext();
ctx.resume();
const sourceNode = ctx.createMediaElementSource(source);
// Connect element source to destination so audio is still audible
sourceNode.connect(ctx.destination);
elementSourceCache.current.set(source, { sourceNode, ctx });
result = { sourceNode, ctx };
cleanup = () => {
ctx.close();
elementSourceCache.current.delete(source);
};
}
}
// HTMLAudioElement — createMediaElementSource can only be called once per element
const cached = elementSourceCache.current.get(source);
if (cached) {
cached.ctx.resume();
setAudioSource(cached);
return;
}
// Publishing an imperatively-created Web Audio node — external-resource
// sync, not a re-render cascade.
// eslint-disable-next-line react-hooks/set-state-in-effect
setAudioSource(result);
const ctx = new AudioContext();
ctx.resume();
const sourceNode = ctx.createMediaElementSource(source);
// Connect element source to destination so audio is still audible
sourceNode.connect(ctx.destination);
elementSourceCache.current.set(source, { sourceNode, ctx });
setAudioSource({ sourceNode, ctx });
return () => {
ctx.close();
elementSourceCache.current.delete(source);
};
return cleanup;
}, [source]);
return audioSource;
@@ -23,16 +23,13 @@ export function ScreenSourcePicker({
getSources().then((result) => {
setSources(result);
setLoading(false);
// Auto-select if there's only one source.
if (result.length === 1) {
setSelectedId(result[0].id);
}
});
}, [getSources]);
// Auto-select if there's only one source
useEffect(() => {
if (!loading && sources.length === 1) {
setSelectedId(sources[0].id);
}
}, [loading, sources]);
const screens = sources.filter((s) => s.id.startsWith("screen:"));
const windows = sources.filter((s) => s.id.startsWith("window:"));