diff --git a/apps/desktop/src-tauri/src/upload.rs b/apps/desktop/src-tauri/src/upload.rs index 91b442dc6..f6e923697 100644 --- a/apps/desktop/src-tauri/src/upload.rs +++ b/apps/desktop/src-tauri/src/upload.rs @@ -186,12 +186,15 @@ impl UploadProgressUpdater { async fn send_api_update(app: &AppHandle, video_id: String, uploaded: u64, total: u64) { let response = app .authed_api_request("/api/desktop/video/progress", |client, url| { - client.post(url).json(&json!({ - "videoId": video_id, - "uploaded": uploaded, - "total": total, - "updatedAt": chrono::Utc::now().to_rfc3339() - })) + client + .post(url) + .header("X-Cap-Desktop-Version", env!("CARGO_PKG_VERSION")) + .json(&json!({ + "videoId": video_id, + "uploaded": uploaded, + "total": total, + "updatedAt": chrono::Utc::now().to_rfc3339() + })) }) .await; diff --git a/apps/web/actions/video/upload.ts b/apps/web/actions/video/upload.ts index d6dc394a3..c77820242 100644 --- a/apps/web/actions/video/upload.ts +++ b/apps/web/actions/video/upload.ts @@ -7,7 +7,7 @@ import { import { db } from "@cap/database"; import { getCurrentUser } from "@cap/database/auth/session"; import { nanoId } from "@cap/database/helpers"; -import { s3Buckets, videos } from "@cap/database/schema"; +import { s3Buckets, videos, videoUploads } from "@cap/database/schema"; import { buildEnv, NODE_ENV, serverEnv } from "@cap/env"; import { userIsPro } from "@cap/utils"; import { eq } from "drizzle-orm"; @@ -231,6 +231,10 @@ export async function createVideoAndGetUploadUrl({ await db().insert(videos).values(videoData); + await db().insert(videoUploads).values({ + videoId: idToUse, + }); + const fileKey = `${user.id}/${idToUse}/${ isScreenshot ? "screenshot/screen-capture.jpg" : "result.mp4" }`; diff --git a/apps/web/app/(org)/dashboard/caps/Caps.tsx b/apps/web/app/(org)/dashboard/caps/Caps.tsx index 1623cb271..b294f513a 100644 --- a/apps/web/app/(org)/dashboard/caps/Caps.tsx +++ b/apps/web/app/(org)/dashboard/caps/Caps.tsx @@ -74,14 +74,7 @@ export const Caps = ({ const previousCountRef = useRef(0); const [selectedCaps, setSelectedCaps] = useState([]); const [isDraggingCap, setIsDraggingCap] = useState(false); - const { - isUploading, - setIsUploading, - setUploadingCapId, - setUploadProgress, - uploadingCapId, - setUploadingThumbnailUrl, - } = useUploadingContext(); + const { uploadStatus } = useUploadingContext(); const anyCapSelected = selectedCaps.length > 0; @@ -262,11 +255,13 @@ export const Caps = ({ toast.success("Cap deleted successfully"); router.refresh(); }, - onError: () => { - toast.error("Failed to delete cap"); - }, + onError: () => toast.error("Failed to delete cap"), }); + const isUploading = uploadStatus !== undefined; + const uploadingCapId = + uploadStatus && "capId" in uploadStatus ? uploadStatus.capId : undefined; + const visibleVideos = useMemo( () => isUploading && uploadingCapId @@ -293,21 +288,7 @@ export const Caps = ({ New Folder - { - setIsUploading(true); - setUploadingCapId(id); - setUploadingThumbnailUrl(thumbnailUrl); - setUploadProgress(0); - }} - size="sm" - onComplete={() => { - setIsUploading(false); - setUploadingCapId(null); - setUploadingThumbnailUrl(undefined); - setUploadProgress(0); - }} - /> + {folders.length > 0 && ( <> diff --git a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx index f52eb9405..18def19ad 100644 --- a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx +++ b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx @@ -1,52 +1,73 @@ "use client"; import type React from "react"; -import { createContext, useContext, useState } from "react"; +import { createContext, useContext, useEffect, useState } from "react"; interface UploadingContextType { - isUploading: boolean; - setIsUploading: (value: boolean) => void; - uploadingCapId: string | null; - setUploadingCapId: (id: string | null) => void; - uploadingThumbnailUrl: string | undefined; - setUploadingThumbnailUrl: (url: string | undefined) => void; - uploadProgress: number; - setUploadProgress: (progress: number) => void; + uploadStatus: UploadStatus | undefined; + setUploadStatus: (state: UploadStatus | undefined) => void; } +export type UploadStatus = + | { + status: "parsing"; + } + | { + status: "creating"; + } + | { + status: "converting"; + capId: string; + progress: number; + } + | { + status: "uploadingThumbnail"; + capId: string; + progress: number; + } + | { + status: "uploadingVideo"; + capId: string; + progress: number; + thumbnailUrl: string | undefined; + }; + const UploadingContext = createContext( undefined, ); export function useUploadingContext() { const context = useContext(UploadingContext); - if (!context) { + if (!context) throw new Error( "useUploadingContext must be used within an UploadingProvider", ); - } return context; } export function UploadingProvider({ children }: { children: React.ReactNode }) { - const [isUploading, setIsUploading] = useState(false); - const [uploadingCapId, setUploadingCapId] = useState(null); - const [uploadingThumbnailUrl, setUploadingThumbnailUrl] = useState< - string | undefined - >(undefined); - const [uploadProgress, setUploadProgress] = useState(0); + const [state, setState] = useState(); + + // Prevent the user closing the tab while uploading + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (state?.status) { + e.preventDefault(); + // Chrome requires returnValue to be set + e.returnValue = ""; + return ""; + } + }; + + window.addEventListener("beforeunload", handleBeforeUnload); + return () => window.removeEventListener("beforeunload", handleBeforeUnload); + }, [state]); return ( {children} diff --git a/apps/web/app/(org)/dashboard/caps/components/NewFolderDialog.tsx b/apps/web/app/(org)/dashboard/caps/components/NewFolderDialog.tsx index b1812247e..8ebfa801e 100644 --- a/apps/web/app/(org)/dashboard/caps/components/NewFolderDialog.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/NewFolderDialog.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Button, Dialog, diff --git a/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx b/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx index 343d9e504..6062c4526 100644 --- a/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx @@ -5,48 +5,27 @@ import { userIsPro } from "@cap/utils"; import { faUpload } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useRouter } from "next/navigation"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { toast } from "sonner"; import { createVideoAndGetUploadUrl } from "@/actions/video/upload"; import { useDashboardContext } from "@/app/(org)/dashboard/Contexts"; import { useUploadingContext } from "@/app/(org)/dashboard/caps/UploadingContext"; import { UpgradeModal } from "@/components/UpgradeModal"; -type UploadState = { - videoId?: string; - uploaded: number; - total: number; - pendingTask?: ReturnType; - lastUpdateTime: number; -}; - export const UploadCapButton = ({ - onStart, - onProgress, - onComplete, size = "md", folderId, }: { - onStart?: (id: string, thumbnail?: string) => void; - onProgress?: (id: string, progress: number, uploadProgress?: number) => void; - onComplete?: (id: string) => void; size?: "sm" | "lg" | "md"; grey?: boolean; folderId?: string; }) => { const { user } = useDashboardContext(); const inputRef = useRef(null); - const { isUploading, setIsUploading, setUploadProgress } = - useUploadingContext(); + const { uploadStatus, setUploadStatus } = useUploadingContext(); const [upgradeModalOpen, setUpgradeModalOpen] = useState(false); const router = useRouter(); - const [uploadState, setUploadState] = useState({ - uploaded: 0, - total: 0, - lastUpdateTime: Date.now(), - }); - const handleClick = () => { if (!user) return; @@ -64,12 +43,11 @@ export const UploadCapButton = ({ const file = e.target.files?.[0]; if (!file || !user) return; - setIsUploading(true); - setUploadProgress(0); - try { - const parser = await import("@remotion/media-parser"); - const webcodecs = await import("@remotion/webcodecs"); + const parser = await import("@remotion/media-parser"); + const webcodecs = await import("@remotion/webcodecs"); + try { + setUploadStatus({ status: "parsing" }); const metadata = await parser.parseMedia({ src: file, fields: { @@ -85,6 +63,7 @@ export const UploadCapButton = ({ ? Math.round(metadata.durationInSeconds) : undefined; + setUploadStatus({ status: "creating" }); const videoData = await createVideoAndGetUploadUrl({ duration, resolution: metadata.dimensions @@ -98,12 +77,8 @@ export const UploadCapButton = ({ }); const uploadId = videoData.id; - // Initial start with thumbnail as undefined - onStart?.(uploadId); - onProgress?.(uploadId, 10); - const fileSizeMB = file.size / (1024 * 1024); - onProgress?.(uploadId, 15); + setUploadStatus({ status: "converting", capId: uploadId, progress: 0 }); let optimizedBlob: Blob; @@ -137,7 +112,11 @@ export const UploadCapButton = ({ onProgress: ({ overallProgress }) => { if (overallProgress !== null) { const progressValue = overallProgress * 100; - onProgress?.(uploadId, progressValue); + setUploadStatus({ + status: "converting", + capId: uploadId, + progress: progressValue, + }); } }, }); @@ -300,10 +279,6 @@ export const UploadCapButton = ({ ? URL.createObjectURL(thumbnailBlob) : undefined; - // Pass the thumbnail URL to the parent component - onStart?.(uploadId, thumbnailUrl); - onProgress?.(uploadId, 100); - const formData = new FormData(); Object.entries(videoData.presignedPostData.fields).forEach( ([key, value]) => { @@ -312,40 +287,111 @@ export const UploadCapButton = ({ ); formData.append("file", optimizedBlob); - setUploadProgress(0); - - await new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open("POST", videoData.presignedPostData.url); - - xhr.upload.onprogress = (event) => { - if (event.lengthComputable) { - const percent = (event.loaded / event.total) * 100; - setUploadProgress(percent); - onProgress?.(uploadId, 100, percent); - - setUploadState({ - videoId: uploadId, - uploaded: event.loaded, - total: event.total, - lastUpdateTime: Date.now(), - pendingTask: uploadState.pendingTask, - }); - } + setUploadStatus({ + status: "uploadingVideo", + capId: uploadId, + progress: 0, + thumbnailUrl, + }); + + // Create progress tracking state outside React + const createProgressTracker = () => { + const uploadState = { + videoId: uploadId, + uploaded: 0, + total: 0, + pendingTask: undefined as ReturnType | undefined, + lastUpdateTime: Date.now(), }; - xhr.onload = () => { - if (xhr.status >= 200 && xhr.status < 300) { - sendProgressUpdate(uploadId, uploadState.total, uploadState.total); - resolve(); + const scheduleProgressUpdate = (uploaded: number, total: number) => { + uploadState.uploaded = uploaded; + uploadState.total = total; + uploadState.lastUpdateTime = Date.now(); + + // Clear any existing pending task + if (uploadState.pendingTask) { + clearTimeout(uploadState.pendingTask); + uploadState.pendingTask = undefined; + } + + const shouldSendImmediately = uploaded >= total; + + if (shouldSendImmediately) { + // Don't send completion update immediately - let xhr.onload handle it + // to avoid double progress updates + return; } else { - reject(new Error(`Upload failed with status ${xhr.status}`)); + // Schedule delayed update (after 2 seconds) + uploadState.pendingTask = setTimeout(() => { + if (uploadState.videoId) { + sendProgressUpdate( + uploadState.videoId, + uploadState.uploaded, + uploadState.total, + ); + } + uploadState.pendingTask = undefined; + }, 2000); } }; - xhr.onerror = () => reject(new Error("Upload failed")); - xhr.send(formData); - }); + const cleanup = () => { + if (uploadState.pendingTask) { + clearTimeout(uploadState.pendingTask); + uploadState.pendingTask = undefined; + } + }; + + const getTotal = () => uploadState.total; + + return { scheduleProgressUpdate, cleanup, getTotal }; + }; + + const progressTracker = createProgressTracker(); + + try { + await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open("POST", videoData.presignedPostData.url); + + xhr.upload.onprogress = (event) => { + if (event.lengthComputable) { + const percent = (event.loaded / event.total) * 100; + setUploadStatus({ + status: "uploadingVideo", + capId: uploadId, + progress: percent, + thumbnailUrl, + }); + + progressTracker.scheduleProgressUpdate(event.loaded, event.total); + } + }; + + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + progressTracker.cleanup(); + // Guarantee final 100% progress update + const total = progressTracker.getTotal() || 1; + sendProgressUpdate(uploadId, total, total); + resolve(); + } else { + progressTracker.cleanup(); + reject(new Error(`Upload failed with status ${xhr.status}`)); + } + }; + xhr.onerror = () => { + progressTracker.cleanup(); + reject(new Error("Upload failed")); + }; + + xhr.send(formData); + }); + } catch (uploadError) { + progressTracker.cleanup(); + throw uploadError; + } if (thumbnailBlob) { const screenshotData = await createVideoAndGetUploadUrl({ @@ -362,6 +408,11 @@ export const UploadCapButton = ({ ); screenshotFormData.append("file", thumbnailBlob); + setUploadStatus({ + status: "uploadingThumbnail", + capId: uploadId, + progress: 0, + }); await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", screenshotData.presignedPostData.url); @@ -370,7 +421,11 @@ export const UploadCapButton = ({ if (event.lengthComputable) { const percent = (event.loaded / event.total) * 100; const thumbnailProgress = 90 + percent * 0.1; - onProgress?.(uploadId, 100, thumbnailProgress); + setUploadStatus({ + status: "uploadingThumbnail", + capId: uploadId, + progress: thumbnailProgress, + }); } }; @@ -387,22 +442,13 @@ export const UploadCapButton = ({ xhr.send(screenshotFormData); }); - } else { } - onProgress?.(uploadId, 100, 100); - onComplete?.(uploadId); router.refresh(); } catch (err) { console.error("Video upload failed", err); } finally { - setIsUploading(false); - setUploadProgress(0); - setUploadState({ - uploaded: 0, - total: 0, - lastUpdateTime: Date.now(), - }); + setUploadStatus(undefined); if (inputRef.current) inputRef.current.value = ""; } }; @@ -433,69 +479,7 @@ export const UploadCapButton = ({ } }; - // Prevent the user closing the tab while uploading - useEffect(() => { - const handleBeforeUnload = (e: BeforeUnloadEvent) => { - if (isUploading) { - e.preventDefault(); - // Chrome requires returnValue to be set - e.returnValue = ""; - return ""; - } - }; - - window.addEventListener("beforeunload", handleBeforeUnload); - return () => window.removeEventListener("beforeunload", handleBeforeUnload); - }, [isUploading]); - - useEffect(() => { - if (!uploadState.videoId || uploadState.uploaded === 0 || !isUploading) - return; - - // Clear any existing pending task - if (uploadState.pendingTask) clearTimeout(uploadState.pendingTask); - - const shouldSendImmediately = uploadState.uploaded >= uploadState.total; - - if (shouldSendImmediately) { - // Send completion update immediately and clear state - sendProgressUpdate( - uploadState.videoId, - uploadState.uploaded, - uploadState.total, - ); - - setUploadState((prev) => ({ - ...prev, - pendingTask: undefined, - })); - } else { - // Schedule delayed update (after 2 seconds) - const newPendingTask = setTimeout(() => { - if (uploadState.videoId) { - sendProgressUpdate( - uploadState.videoId, - uploadState.uploaded, - uploadState.total, - ); - } - }, 2000); - - setUploadState((prev) => ({ - ...prev, - pendingTask: newPendingTask, - })); - } - - return () => { - if (uploadState.pendingTask) clearTimeout(uploadState.pendingTask); - }; - }, [ - uploadState.videoId, - uploadState.uploaded, - uploadState.total, - isUploading, - ]); + const isUploading = !!uploadStatus; return ( <> diff --git a/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx b/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx index 18806ea85..dd6966490 100644 --- a/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx @@ -1,28 +1,30 @@ "use client"; import { LogoSpinner } from "@cap/ui"; -import { - calculateStrokeDashoffset, - getProgressCircleConfig, - getUploadStatus, -} from "@cap/utils"; -import { useUploadingContext } from "../UploadingContext"; +import { calculateStrokeDashoffset, getProgressCircleConfig } from "@cap/utils"; +import { type UploadStatus, useUploadingContext } from "../UploadingContext"; + +const { circumference } = getProgressCircleConfig(); export const UploadPlaceholderCard = () => { - const { uploadingThumbnailUrl, uploadProgress } = useUploadingContext(); - const { circumference } = getProgressCircleConfig(); - const status = getUploadStatus(uploadProgress); + const { uploadStatus } = useUploadingContext(); const strokeDashoffset = calculateStrokeDashoffset( - uploadProgress, + uploadStatus && + (uploadStatus.status === "converting" || + uploadStatus.status === "uploadingThumbnail" || + uploadStatus.status === "uploadingVideo") + ? uploadStatus.progress + : 0, circumference, ); + if (!uploadStatus) return null; return (
- {uploadingThumbnailUrl ? ( + {uploadStatus.status === "uploadingVideo" ? ( Uploading thumbnail @@ -35,7 +37,9 @@ export const UploadPlaceholderCard = () => {
- {status} + + {getFriendlyStatus(uploadStatus.status)} + {
); }; + +function getFriendlyStatus(status: UploadStatus["status"]) { + switch (status) { + case "parsing": + return "Parsing"; + case "creating": + return "Creating"; + case "converting": + return "Converting"; + case "uploadingThumbnail": + case "uploadingVideo": + return "Uploading"; + default: + return "Processing..."; + } +} diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx index 1228636d2..90563e357 100644 --- a/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx +++ b/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx @@ -27,9 +27,13 @@ export default function FolderVideosSection({ cardType = "default", }: FolderVideosSectionProps) { const router = useRouter(); - const { isUploading, uploadingCapId } = useUploadingContext(); + const { uploadStatus } = useUploadingContext(); const { user } = useDashboardContext(); + const isUploading = uploadStatus !== undefined; + const uploadingCapId = + uploadStatus && "capId" in uploadStatus ? uploadStatus.capId : null; + const [selectedCaps, setSelectedCaps] = useState([]); const previousCountRef = useRef(0); diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/UploadCapButtonWithFolder.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/UploadCapButtonWithFolder.tsx deleted file mode 100644 index 242793660..000000000 --- a/apps/web/app/(org)/dashboard/folder/[id]/components/UploadCapButtonWithFolder.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { UploadCapButton } from "../../../caps/components/UploadCapButton"; -import { useUploadingContext } from "../../../caps/UploadingContext"; -export function UploadCapButtonWithFolder({ folderId }: { folderId: string }) { - const router = useRouter(); - const { - setIsUploading, - setUploadingCapId, - setUploadingThumbnailUrl, - setUploadProgress, - } = useUploadingContext(); - - return ( - { - setIsUploading(true); - setUploadingCapId(id); - setUploadingThumbnailUrl(thumbnail); - setUploadProgress(0); - }} - onComplete={(id) => { - // Reset all uploading state - setIsUploading(false); - setUploadingCapId(null); - setUploadingThumbnailUrl(undefined); - setUploadProgress(0); - router.refresh(); - }} - folderId={folderId} - size="sm" - /> - ); -} diff --git a/apps/web/app/(org)/dashboard/folder/[id]/page.tsx b/apps/web/app/(org)/dashboard/folder/[id]/page.tsx index 0cd730250..c72635890 100644 --- a/apps/web/app/(org)/dashboard/folder/[id]/page.tsx +++ b/apps/web/app/(org)/dashboard/folder/[id]/page.tsx @@ -5,6 +5,7 @@ import { getFolderBreadcrumb, getVideosByFolderId, } from "@/lib/folder"; +import { UploadCapButton } from "../../caps/components"; import FolderCard from "../../caps/components/Folder"; import { BreadcrumbItem, @@ -12,7 +13,6 @@ import { NewSubfolderButton, } from "./components"; import FolderVideosSection from "./components/FolderVideosSection"; -import { UploadCapButtonWithFolder } from "./components/UploadCapButtonWithFolder"; const FolderPage = async ({ params }: { params: { id: Folder.FolderId } }) => { const [childFolders, breadcrumb, videosData] = await Promise.all([ @@ -25,7 +25,7 @@ const FolderPage = async ({ params }: { params: { id: Folder.FolderId } }) => {
- +
diff --git a/apps/web/app/api/desktop/[...route]/video.ts b/apps/web/app/api/desktop/[...route]/video.ts index d21141e41..725a2282e 100644 --- a/apps/web/app/api/desktop/[...route]/video.ts +++ b/apps/web/app/api/desktop/[...route]/video.ts @@ -125,9 +125,15 @@ app.get( fps, }); - await db().insert(videoUploads).values({ - videoId: idToUse, - }); + const xCapVersion = c.req.header("X-Cap-Desktop-Version"); + const clientSupportsUploadProgress = xCapVersion + ? isAtLeastSemver(xCapVersion, 0, 3, 68) + : false; + + if (clientSupportsUploadProgress) + await db().insert(videoUploads).values({ + videoId: idToUse, + }); if (buildEnv.NEXT_PUBLIC_IS_CAP && NODE_ENV === "production") await dub().links.create({ @@ -291,14 +297,13 @@ app.post( ), ); - if (result.rowsAffected === 0) { - const result2 = await db().insert(videoUploads).values({ + if (result.rowsAffected === 0) + await db().insert(videoUploads).values({ videoId, uploaded, total, updatedAt, }); - } if (uploaded === total) await db() @@ -312,3 +317,27 @@ app.post( } }, ); + +function isAtLeastSemver( + versionString: string, + major: number, + minor: number, + patch: number, +): boolean { + const match = versionString + .replace(/^v/, "") + .match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?/); + if (!match) return false; + const [, vMajor, vMinor, vPatch, prerelease] = match; + const M = vMajor ? parseInt(vMajor, 10) || 0 : 0; + const m = vMinor ? parseInt(vMinor, 10) || 0 : 0; + const p = vPatch ? parseInt(vPatch, 10) || 0 : 0; + if (M > major) return true; + if (M < major) return false; + if (m > minor) return true; + if (m < minor) return false; + if (p > patch) return true; + if (p < patch) return false; + // Equal triplet: accept only non-prerelease + return !prerelease; +} diff --git a/apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx b/apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx index 733589602..76cbd487f 100644 --- a/apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx +++ b/apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx @@ -338,9 +338,7 @@ export function CapVideoPlayer({ "loadedmetadata", handleLoadedMetadataWithTracks, ); - if (retryTimeout.current) { - clearTimeout(retryTimeout.current); - } + if (retryTimeout.current) clearTimeout(retryTimeout.current); }; } @@ -426,7 +424,7 @@ export function CapVideoPlayer({
diff --git a/apps/web/components/VideoThumbnail.tsx b/apps/web/components/VideoThumbnail.tsx index 6a8e3fb24..664404d90 100644 --- a/apps/web/components/VideoThumbnail.tsx +++ b/apps/web/components/VideoThumbnail.tsx @@ -70,8 +70,10 @@ export const VideoThumbnail: React.FC = memo( }); const imageRef = useRef(null); - const { uploadingCapId } = useUploadingContext(); + const { uploadStatus } = useUploadingContext(); + const uploadingCapId = + uploadStatus && "capId" in uploadStatus ? uploadStatus.capId : null; useEffect(() => { imageUrl.refetch(); }, [imageUrl.refetch, uploadingCapId]); diff --git a/packages/utils/src/helpers.ts b/packages/utils/src/helpers.ts index 85f5c3eea..4ea505db7 100644 --- a/packages/utils/src/helpers.ts +++ b/packages/utils/src/helpers.ts @@ -74,13 +74,6 @@ export const calculateStrokeDashoffset = ( return circumference - (progress / 100) * circumference; }; -export const getUploadStatus = (uploadProgress?: number) => { - if (uploadProgress !== undefined) { - return "Uploading"; - } - return "Processing"; -}; - export const getDisplayProgress = ( uploadProgress?: number, processingProgress: number = 0,