Browse Source

fix(memory): close blob URL leak race in useVideoBlobUrl

- Track created blob URL in async chain with createdUrl variable
- Cleanup function revokes orphaned URLs created after cancel flag set
- Remove unnecessary prevInputRef deduplication (React handles this)
- Fixes race where rapid videoUrl changes orphan blob URLs in memory
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
0bca6e6969
  1. 11
      src/hooks/useVideoBlobUrl.ts

11
src/hooks/useVideoBlobUrl.ts

@ -17,13 +17,8 @@ import { useEffect, useRef, useState } from "react";
export function useVideoBlobUrl(videoUrl: string | null): string | null { export function useVideoBlobUrl(videoUrl: string | null): string | null {
const [blobUrl, setBlobUrl] = useState<string | null>(null); const [blobUrl, setBlobUrl] = useState<string | null>(null);
const prevBlobUrlRef = useRef<string | null>(null); const prevBlobUrlRef = useRef<string | null>(null);
const prevInputRef = useRef<string | null>(null);
useEffect(() => { useEffect(() => {
// Input unchanged — skip
if (videoUrl === prevInputRef.current) return;
prevInputRef.current = videoUrl;
// Revoke previous blob URL // Revoke previous blob URL
if (prevBlobUrlRef.current) { if (prevBlobUrlRef.current) {
URL.revokeObjectURL(prevBlobUrlRef.current); URL.revokeObjectURL(prevBlobUrlRef.current);
@ -47,11 +42,13 @@ export function useVideoBlobUrl(videoUrl: string | null): string | null {
setBlobUrl(videoUrl); setBlobUrl(videoUrl);
let cancelled = false; let cancelled = false;
let createdUrl: string | null = null;
fetch(videoUrl) fetch(videoUrl)
.then((r) => r.blob()) .then((r) => r.blob())
.then((blob) => { .then((blob) => {
if (cancelled) return; if (cancelled) return;
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
createdUrl = url;
prevBlobUrlRef.current = url; prevBlobUrlRef.current = url;
setBlobUrl(url); setBlobUrl(url);
}) })
@ -61,6 +58,10 @@ export function useVideoBlobUrl(videoUrl: string | null): string | null {
return () => { return () => {
cancelled = true; cancelled = true;
// If a blob URL was created after we decided to cancel, revoke it
if (createdUrl && createdUrl !== prevBlobUrlRef.current) {
URL.revokeObjectURL(createdUrl);
}
}; };
} }

Loading…
Cancel
Save