mirror of
https://github.com/infinilabs/coco-app.git
synced 2026-08-29 10:09:30 +02:00
fix(camera): apply device switch live without requiring a remount
The switch-camera effect listed `ready` in its dependency array and called `setReady(false)` at the start of the swap. That state change re-ran the effect, whose cleanup flipped the in-flight switch's `cancelled` flag and bailed out after the old stream had already been stopped — leaving the viewport frozen until the user closed and re-opened the extension. Drop `ready` from the deps (and the guard); the stream-already-matches check still makes the initial render a no-op. Also call `video.play()` explicitly after `srcObject` swap as a safety net for browsers that don't auto-resume. Agent-Logs-Url: https://github.com/infinilabs/coco-app/sessions/422e57d1-7fed-4d31-a713-77c149765e46 Co-authored-by: medcl <64487+medcl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2dee55b7ad
commit
0ec8b51922
@@ -255,15 +255,26 @@ const Camera = () => {
|
||||
}, [t, stopCurrentStream, recheckNonce]);
|
||||
|
||||
// Switch camera when device selection changes (after initial setup).
|
||||
//
|
||||
// NOTE: `ready` is intentionally NOT in the dependency array. We DO call
|
||||
// `setReady(false)` below to show the loading spinner during the switch,
|
||||
// but if `ready` were a dep, that state change would re-run this effect,
|
||||
// which would (a) trip the cleanup's `cancelled = true` on the in-flight
|
||||
// switch and (b) immediately early-return on the new run because `ready`
|
||||
// is false. The old stream is already stopped by then, so the video stays
|
||||
// frozen until the component remounts — which is exactly the bug the user
|
||||
// reported ("I have to close and re-open the extension to switch camera").
|
||||
useEffect(() => {
|
||||
if (permission !== "granted" || !ready || !selectedDeviceId) return;
|
||||
if (permission !== "granted" || !selectedDeviceId) return;
|
||||
|
||||
// Check if the current stream already uses the selected device.
|
||||
// Check if the current stream already uses the selected device. This
|
||||
// makes the initial render (where selectedDeviceId is set from the
|
||||
// already-running stream) a no-op.
|
||||
if (streamRef.current) {
|
||||
const currentTrack = streamRef.current.getVideoTracks()[0];
|
||||
const currentDeviceId = currentTrack?.getSettings()?.deviceId;
|
||||
if (currentDeviceId === selectedDeviceId) {
|
||||
return; // Already using this device.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +300,16 @@ const Camera = () => {
|
||||
|
||||
if (videoRef.current) {
|
||||
videoRef.current.srcObject = mediaStream;
|
||||
// Some browsers don't auto-resume play() after srcObject swap
|
||||
// even with the `autoPlay` attribute; calling .play() explicitly
|
||||
// is harmless if it's already playing.
|
||||
videoRef.current.play().catch((err) => {
|
||||
console.error(
|
||||
`[Camera] video.play() after switch failed: ${
|
||||
(err as { name?: string })?.name ?? "Error"
|
||||
} - ${(err as { message?: string })?.message ?? err}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setLastError(null);
|
||||
@@ -312,7 +333,8 @@ const Camera = () => {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [selectedDeviceId, ready, permission, stopCurrentStream]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedDeviceId, permission, stopCurrentStream]);
|
||||
|
||||
const takePhoto = useCallback(async () => {
|
||||
if (!videoRef.current || !canvasRef.current) return;
|
||||
|
||||
Reference in New Issue
Block a user