Show upload size during media finalization
这个提交包含在:
@@ -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>
|
||||
)}
|
||||
|
||||
在新工单中引用
屏蔽一个用户