Show upload size during media finalization

这个提交包含在:
cryptocommuniums-afk
2026-03-15 02:16:32 +08:00
父节点 815f96d4e8
当前提交 f4f425de42
修改 3 个文件,包含 73 行新增16 行删除

查看文件

@@ -123,6 +123,25 @@ function getArchiveProgress(session: MediaSession | null) {
}
}
function getArchivePhaseLabel(mode: RecorderMode, session: MediaSession | null, taskProgress?: number | null) {
if (mode === "finalizing" && !taskProgress) {
return "正在提交归档任务";
}
if (session?.archiveStatus === "completed") {
return "回放已生成";
}
if (session?.archiveStatus === "failed") {
return "归档失败";
}
if (session?.archiveStatus === "processing") {
return "正在生成回放";
}
if (session?.archiveStatus === "queued") {
return "正在合并分段";
}
return "等待归档";
}
function formatFileSize(bytes: number) {
if (!bytes) return "0 MB";
return `${(bytes / 1024 / 1024).toFixed(bytes > 20 * 1024 * 1024 ? 1 : 2)} MB`;
@@ -174,6 +193,7 @@ export default function Recorder() {
const [isOnline, setIsOnline] = useState(() => navigator.onLine);
const [reconnectAttempts, setReconnectAttempts] = useState(0);
const [queuedSegments, setQueuedSegments] = useState(0);
const [queuedBytes, setQueuedBytes] = useState(0);
const [uploadedSegments, setUploadedSegments] = useState(0);
const [uploadBytes, setUploadBytes] = useState(0);
const [cameraError, setCameraError] = useState("");
@@ -187,8 +207,10 @@ export default function Recorder() {
const mobile = useMemo(() => isMobileDevice(), []);
const mimeType = useMemo(() => pickRecorderMimeType(), []);
const currentPlaybackUrl = mediaSession?.playback.mp4Url || mediaSession?.playback.webmUrl || "";
const archiveProgress = getArchiveProgress(mediaSession);
const archiveTaskQuery = useBackgroundTask(archiveTaskId);
const archiveProgress = archiveTaskQuery.data?.progress ?? getArchiveProgress(mediaSession);
const archivePhaseLabel = getArchivePhaseLabel(mode, mediaSession, archiveTaskQuery.data?.progress);
const totalUploadBytes = uploadBytes + queuedBytes;
const syncSessionState = useCallback((session: MediaSession | null) => {
currentSessionRef.current = session;
@@ -198,6 +220,11 @@ export default function Recorder() {
setUploadBytes(session?.uploadedBytes ?? 0);
}, []);
const syncQueuedUploadState = useCallback(() => {
setQueuedSegments(pendingUploadsRef.current.length);
setQueuedBytes(pendingUploadsRef.current.reduce((total, item) => total + item.blob.size, 0));
}, []);
useEffect(() => {
modeRef.current = mode;
}, [mode]);
@@ -328,7 +355,7 @@ export default function Recorder() {
const processUploadQueue = useCallback(async () => {
if (uploadInFlightRef.current || pendingUploadsRef.current.length === 0 || !currentSessionRef.current?.id || !navigator.onLine) {
setQueuedSegments(pendingUploadsRef.current.length);
syncQueuedUploadState();
return;
}
@@ -343,7 +370,7 @@ export default function Recorder() {
nextSegment.blob
);
pendingUploadsRef.current.shift();
setQueuedSegments(pendingUploadsRef.current.length);
syncQueuedUploadState();
syncSessionState(response.session);
}
} catch (error: any) {
@@ -351,7 +378,7 @@ export default function Recorder() {
} finally {
uploadInFlightRef.current = false;
}
}, [syncSessionState]);
}, [syncQueuedUploadState, syncSessionState]);
const enqueueSegment = useCallback(async (blob: Blob, durationForSegmentMs: number) => {
if (!blob.size) return;
@@ -360,9 +387,9 @@ export default function Recorder() {
durationMs: Math.max(1, durationForSegmentMs),
blob,
});
setQueuedSegments(pendingUploadsRef.current.length);
syncQueuedUploadState();
await processUploadQueue();
}, [processUploadQueue]);
}, [processUploadQueue, syncQueuedUploadState]);
const flushPendingSegments = useCallback(async () => {
while (pendingUploadsRef.current.length > 0 || uploadInFlightRef.current) {
@@ -588,6 +615,7 @@ export default function Recorder() {
setUploadedSegments(0);
setUploadBytes(0);
setQueuedSegments(0);
setQueuedBytes(0);
setReconnectAttempts(0);
setArchiveTaskId(null);
segmentSequenceRef.current = 0;
@@ -664,6 +692,7 @@ export default function Recorder() {
setMarkers([]);
setDurationMs(0);
setQueuedSegments(0);
setQueuedBytes(0);
setUploadedSegments(0);
setUploadBytes(0);
setReconnectAttempts(0);
@@ -976,8 +1005,8 @@ export default function Recorder() {
<div className="mt-2 text-lg font-semibold text-white">{uploadedSegments}</div>
</div>
<div className="rounded-xl bg-black/15 px-3 py-3">
<div className="text-[11px] uppercase tracking-[0.18em] text-white/45"></div>
<div className="mt-2 text-lg font-semibold text-white">{queuedSegments}</div>
<div className="text-[11px] uppercase tracking-[0.18em] text-white/45"></div>
<div className="mt-2 text-lg font-semibold text-white">{formatFileSize(uploadBytes)}</div>
</div>
</div>
</div>
@@ -989,6 +1018,8 @@ export default function Recorder() {
<AlertTitle></AlertTitle>
<AlertDescription>
{archiveTaskQuery.data?.message || "录制文件正在后台整理、转码并登记到视频库。"}
{formatFileSize(uploadBytes)}
{queuedBytes > 0 ? `,待上传 ${formatFileSize(queuedBytes)}` : ""}
</AlertDescription>
</Alert>
@@ -1144,12 +1175,20 @@ export default function Recorder() {
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span></span>
<span></span>
<span className="font-medium">{formatFileSize(uploadBytes)}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
<span className="font-medium">{queuedSegments}</span>
<span></span>
<span className="font-medium">{queuedSegments} · {formatFileSize(queuedBytes)}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
<span className="font-medium">{formatFileSize(totalUploadBytes)}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
<span className="font-medium">{uploadedSegments + queuedSegments}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span></span>
@@ -1160,16 +1199,32 @@ export default function Recorder() {
{(mode === "finalizing" || mode === "archived" || mediaSession?.archiveStatus === "failed") && (
<div className="space-y-2 rounded-2xl border border-border/60 bg-muted/25 p-4">
<div className="flex items-center justify-between text-sm">
<span></span>
<span>{archivePhaseLabel}</span>
<span className="font-medium">{archiveProgress}%</span>
</div>
<Progress value={archiveProgress} className="h-2" />
<div className="grid gap-2 rounded-2xl bg-background/70 p-3 text-xs text-muted-foreground sm:grid-cols-3">
<div>
<div className="text-[10px] uppercase tracking-[0.14em]"></div>
<div className="mt-1 text-sm font-medium text-foreground">{formatFileSize(uploadBytes)}</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-[0.14em]"></div>
<div className="mt-1 text-sm font-medium text-foreground">{formatFileSize(queuedBytes)}</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-[0.14em]"></div>
<div className="mt-1 text-sm font-medium text-foreground">{uploadedSegments + queuedSegments} </div>
</div>
</div>
<p className="text-xs leading-5 text-muted-foreground">
{mediaSession?.archiveStatus === "completed"
{archiveTaskQuery.data?.message
? `${archiveTaskQuery.data.message},当前已上传 ${formatFileSize(uploadBytes)}`
: mediaSession?.archiveStatus === "completed"
? "归档完成,已生成可回放文件并同步到视频库。"
: mediaSession?.archiveStatus === "failed"
? mediaSession.lastError || "归档失败,请检查媒体服务日志。"
: "Worker 正在合并分段并生成归档文件。"}
: `Worker 正在合并分段并生成归档文件,当前已上传 ${formatFileSize(uploadBytes)}`}
</p>
</div>
)}