mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-05-22 22:33:05 +08:00
feat: 添加文件传输速度和剩余时间计算,优化文件传输进度显示
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback, useRef } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Download, FileText, Image, Video, Music, Archive } from 'lucide-react';
|
import { Download, FileText, Image, Video, Music, Archive, Clock, Zap } from 'lucide-react';
|
||||||
import { useToast } from '@/components/ui/toast-simple';
|
import { useToast } from '@/components/ui/toast-simple';
|
||||||
import { ConnectionStatus } from '@/components/ConnectionStatus';
|
import { ConnectionStatus } from '@/components/ConnectionStatus';
|
||||||
|
import { TransferProgressTracker, formatTransferSpeed, formatTime } from '@/lib/transfer-utils';
|
||||||
|
|
||||||
interface FileInfo {
|
interface FileInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -14,6 +15,8 @@ interface FileInfo {
|
|||||||
type: string;
|
type: string;
|
||||||
status: 'ready' | 'downloading' | 'completed';
|
status: 'ready' | 'downloading' | 'completed';
|
||||||
progress: number;
|
progress: number;
|
||||||
|
transferSpeed?: number; // bytes per second
|
||||||
|
startTime?: number; // 传输开始时间
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileIcon = (mimeType: string) => {
|
const getFileIcon = (mimeType: string) => {
|
||||||
@@ -60,6 +63,9 @@ export function WebRTCFileReceive({
|
|||||||
const [pickupCode, setPickupCode] = useState('');
|
const [pickupCode, setPickupCode] = useState('');
|
||||||
const [isValidating, setIsValidating] = useState(false);
|
const [isValidating, setIsValidating] = useState(false);
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
|
|
||||||
|
// 用于跟踪传输进度的trackers
|
||||||
|
const transferTrackers = useRef<Map<string, TransferProgressTracker>>(new Map());
|
||||||
|
|
||||||
// 使用传入的取件码或本地状态的取件码
|
// 使用传入的取件码或本地状态的取件码
|
||||||
const displayPickupCode = propPickupCode || pickupCode;
|
const displayPickupCode = propPickupCode || pickupCode;
|
||||||
@@ -242,16 +248,44 @@ export function WebRTCFileReceive({
|
|||||||
const isDownloading = file.status === 'downloading';
|
const isDownloading = file.status === 'downloading';
|
||||||
const isCompleted = file.status === 'completed';
|
const isCompleted = file.status === 'completed';
|
||||||
const hasDownloadedFile = downloadedFiles?.has(file.id);
|
const hasDownloadedFile = downloadedFiles?.has(file.id);
|
||||||
const currentProgress = file.progress;
|
|
||||||
|
|
||||||
console.log('文件状态:', {
|
console.log('文件状态:', {
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
status: file.status,
|
status: file.status,
|
||||||
progress: file.progress,
|
progress: file.progress,
|
||||||
isDownloading,
|
isDownloading
|
||||||
currentProgress
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算传输进度信息
|
||||||
|
let transferInfo = null;
|
||||||
|
let currentProgress = 0; // 使用稳定的进度值
|
||||||
|
|
||||||
|
if (isDownloading && file) {
|
||||||
|
const fileKey = `${file.name}-${file.size}`;
|
||||||
|
let tracker = transferTrackers.current.get(fileKey);
|
||||||
|
|
||||||
|
// 如果tracker不存在,创建一个新的
|
||||||
|
if (!tracker) {
|
||||||
|
tracker = new TransferProgressTracker(file.size);
|
||||||
|
transferTrackers.current.set(fileKey, tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新传输进度
|
||||||
|
const transferredBytes = (file.progress / 100) * file.size;
|
||||||
|
const progressInfo = tracker.update(transferredBytes);
|
||||||
|
transferInfo = progressInfo;
|
||||||
|
currentProgress = progressInfo.percentage; // 使用稳定的百分比
|
||||||
|
} else {
|
||||||
|
// 如果不在传输中,使用原始进度值
|
||||||
|
currentProgress = file.progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理已完成的tracker
|
||||||
|
if (file.status === 'completed') {
|
||||||
|
const fileKey = `${file.name}-${file.size}`;
|
||||||
|
transferTrackers.current.delete(fileKey);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={file.id} className="bg-gradient-to-r from-slate-50 to-blue-50 border border-slate-200 rounded-xl p-3 sm:p-4 hover:shadow-md transition-all duration-200">
|
<div key={file.id} className="bg-gradient-to-r from-slate-50 to-blue-50 border border-slate-200 rounded-xl p-3 sm:p-4 hover:shadow-md transition-all duration-200">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-3 gap-3">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-3 gap-3">
|
||||||
@@ -266,7 +300,27 @@ export function WebRTCFileReceive({
|
|||||||
<p className="text-xs text-emerald-600 font-medium">✅ 传输完成,点击保存</p>
|
<p className="text-xs text-emerald-600 font-medium">✅ 传输完成,点击保存</p>
|
||||||
)}
|
)}
|
||||||
{isDownloading && (
|
{isDownloading && (
|
||||||
<p className="text-xs text-blue-600 font-medium">⏳ 传输中...{currentProgress.toFixed(1)}%</p>
|
<div className="space-y-1">
|
||||||
|
{/* 传输速度和剩余时间信息 */}
|
||||||
|
{transferInfo && (
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="flex items-center gap-1 text-xs text-blue-600">
|
||||||
|
<Zap className="w-3 h-3 flex-shrink-0" />
|
||||||
|
<span className="w-3 font-mono text-right">{transferInfo.speed.displaySpeed}</span>
|
||||||
|
<span className='w-2'/>
|
||||||
|
<span className="w-3">{transferInfo.speed.unit}</span>
|
||||||
|
<span className='w-3'/>
|
||||||
|
</div>
|
||||||
|
{transferInfo.remainingTime.seconds < Infinity && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-slate-600">
|
||||||
|
<Clock className="w-3 h-3 flex-shrink-0" />
|
||||||
|
<span>剩余</span>
|
||||||
|
<span className="w-3 font-mono text-right">{transferInfo.remainingTime.display}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,7 +343,9 @@ export function WebRTCFileReceive({
|
|||||||
{(isDownloading || isCompleted) && currentProgress > 0 && (
|
{(isDownloading || isCompleted) && currentProgress > 0 && (
|
||||||
<div className="mt-3 space-y-2">
|
<div className="mt-3 space-y-2">
|
||||||
<div className="flex justify-between text-sm text-slate-600">
|
<div className="flex justify-between text-sm text-slate-600">
|
||||||
<span>{hasDownloadedFile ? '传输完成' : '正在传输...'}</span>
|
<span>
|
||||||
|
{hasDownloadedFile ? '传输完成' : '正在传输...'}
|
||||||
|
</span>
|
||||||
<span className="font-medium">{currentProgress.toFixed(1)}%</span>
|
<span className="font-medium">{currentProgress.toFixed(1)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-slate-200 rounded-full h-2">
|
<div className="w-full bg-slate-200 rounded-full h-2">
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useRef, useCallback } from 'react';
|
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Upload, FileText, Image, Video, Music, Archive, X } from 'lucide-react';
|
import { Upload, FileText, Image, Video, Music, Archive, X, Clock, Zap } from 'lucide-react';
|
||||||
import RoomInfoDisplay from '@/components/RoomInfoDisplay';
|
import RoomInfoDisplay from '@/components/RoomInfoDisplay';
|
||||||
import { ConnectionStatus } from '@/components/ConnectionStatus';
|
import { ConnectionStatus } from '@/components/ConnectionStatus';
|
||||||
|
import { TransferProgressTracker, formatTransferSpeed, formatTime } from '@/lib/transfer-utils';
|
||||||
|
|
||||||
|
|
||||||
interface FileInfo {
|
interface FileInfo {
|
||||||
@@ -14,6 +15,8 @@ interface FileInfo {
|
|||||||
type: string;
|
type: string;
|
||||||
status: 'ready' | 'downloading' | 'completed';
|
status: 'ready' | 'downloading' | 'completed';
|
||||||
progress: number;
|
progress: number;
|
||||||
|
transferSpeed?: number; // bytes per second
|
||||||
|
startTime?: number; // 传输开始时间
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileIcon = (mimeType: string) => {
|
const getFileIcon = (mimeType: string) => {
|
||||||
@@ -65,6 +68,9 @@ export function WebRTCFileUpload({
|
|||||||
}: WebRTCFileUploadProps) {
|
}: WebRTCFileUploadProps) {
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
// 用于跟踪传输进度的trackers
|
||||||
|
const transferTrackers = useRef<Map<string, TransferProgressTracker>>(new Map());
|
||||||
|
|
||||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -197,9 +203,38 @@ export function WebRTCFileUpload({
|
|||||||
// 查找对应的文件信息(包含状态和进度)
|
// 查找对应的文件信息(包含状态和进度)
|
||||||
const fileInfo = fileList.find(f => f.name === file.name && f.size === file.size);
|
const fileInfo = fileList.find(f => f.name === file.name && f.size === file.size);
|
||||||
const isTransferringThisFile = fileInfo?.status === 'downloading';
|
const isTransferringThisFile = fileInfo?.status === 'downloading';
|
||||||
const currentProgress = fileInfo?.progress || 0;
|
|
||||||
const fileStatus = fileInfo?.status || 'ready';
|
const fileStatus = fileInfo?.status || 'ready';
|
||||||
|
|
||||||
|
// 计算传输进度信息
|
||||||
|
let transferInfo = null;
|
||||||
|
let currentProgress = 0; // 使用稳定的进度值
|
||||||
|
|
||||||
|
if (isTransferringThisFile && fileInfo) {
|
||||||
|
const fileKey = `${file.name}-${file.size}`;
|
||||||
|
let tracker = transferTrackers.current.get(fileKey);
|
||||||
|
|
||||||
|
// 如果tracker不存在,创建一个新的
|
||||||
|
if (!tracker) {
|
||||||
|
tracker = new TransferProgressTracker(file.size);
|
||||||
|
transferTrackers.current.set(fileKey, tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新传输进度
|
||||||
|
const transferredBytes = (fileInfo.progress / 100) * file.size;
|
||||||
|
const progressInfo = tracker.update(transferredBytes);
|
||||||
|
transferInfo = progressInfo;
|
||||||
|
currentProgress = progressInfo.percentage; // 使用稳定的百分比
|
||||||
|
} else {
|
||||||
|
// 如果不在传输中,使用原始进度值
|
||||||
|
currentProgress = fileInfo?.progress || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理已完成的tracker
|
||||||
|
if (fileStatus === 'completed') {
|
||||||
|
const fileKey = `${file.name}-${file.size}`;
|
||||||
|
transferTrackers.current.delete(fileKey);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${file.name}-${file.size}-${index}`}
|
key={`${file.name}-${file.size}-${index}`}
|
||||||
@@ -227,6 +262,26 @@ export function WebRTCFileUpload({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 传输速度和剩余时间信息 */}
|
||||||
|
{transferInfo && (
|
||||||
|
<div className="flex items-center space-x-3 mt-1">
|
||||||
|
<div className="flex items-center gap-1 text-xs text-blue-600">
|
||||||
|
<Zap className="w-3 h-3 flex-shrink-0" />
|
||||||
|
<span className="w-3 font-mono text-right">{transferInfo.speed.displaySpeed}</span>
|
||||||
|
<span className='w-2'/>
|
||||||
|
<span className="w-3">{transferInfo.speed.unit}</span>
|
||||||
|
<span className='w-3'/>
|
||||||
|
</div>
|
||||||
|
{transferInfo.remainingTime.seconds < Infinity && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-slate-600">
|
||||||
|
<Clock className="w-3 h-3 flex-shrink-0" />
|
||||||
|
<span>剩余</span>
|
||||||
|
<span className="w-3 font-mono text-right">{transferInfo.remainingTime.display}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -245,7 +300,9 @@ export function WebRTCFileUpload({
|
|||||||
<div className="px-3 sm:px-4 pb-3 sm:pb-4">
|
<div className="px-3 sm:px-4 pb-3 sm:pb-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-xs text-slate-600">
|
<div className="flex justify-between text-xs text-slate-600">
|
||||||
<span>{fileStatus === 'downloading' ? '正在发送...' : '发送完成'}</span>
|
<span>
|
||||||
|
{fileStatus === 'downloading' ? '正在发送...' : '发送完成'}
|
||||||
|
</span>
|
||||||
<span className="font-medium">{currentProgress.toFixed(1)}%</span>
|
<span className="font-medium">{currentProgress.toFixed(1)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-slate-200 rounded-full h-2">
|
<div className="w-full bg-slate-200 rounded-full h-2">
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ interface FileInfo {
|
|||||||
type: string;
|
type: string;
|
||||||
status: 'ready' | 'downloading' | 'completed';
|
status: 'ready' | 'downloading' | 'completed';
|
||||||
progress: number;
|
progress: number;
|
||||||
|
transferSpeed?: number; // bytes per second
|
||||||
|
startTime?: number; // 传输开始时间
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseFileStateManagerProps {
|
interface UseFileStateManagerProps {
|
||||||
@@ -72,21 +74,33 @@ export const useFileStateManager = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 更新文件状态
|
// 更新文件状态
|
||||||
const updateFileStatus = useCallback((fileId: string, status: FileInfo['status'], progress?: number) => {
|
const updateFileStatus = useCallback((fileId: string, status: FileInfo['status'], progress?: number, transferSpeed?: number) => {
|
||||||
setFileList(prev => prev.map(item =>
|
setFileList(prev => prev.map(item =>
|
||||||
item.id === fileId
|
item.id === fileId
|
||||||
? { ...item, status, progress: progress ?? item.progress }
|
? {
|
||||||
|
...item,
|
||||||
|
status,
|
||||||
|
progress: progress ?? item.progress,
|
||||||
|
transferSpeed: transferSpeed ?? item.transferSpeed,
|
||||||
|
startTime: status === 'downloading' && !item.startTime ? Date.now() : item.startTime
|
||||||
|
}
|
||||||
: item
|
: item
|
||||||
));
|
));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 更新文件进度
|
// 更新文件进度
|
||||||
const updateFileProgress = useCallback((fileId: string, fileName: string, progress: number) => {
|
const updateFileProgress = useCallback((fileId: string, fileName: string, progress: number, transferSpeed?: number) => {
|
||||||
const newStatus = progress >= 100 ? 'completed' as const : 'downloading' as const;
|
const newStatus = progress >= 100 ? 'completed' as const : 'downloading' as const;
|
||||||
setFileList(prev => prev.map(item => {
|
setFileList(prev => prev.map(item => {
|
||||||
if (item.id === fileId || item.name === fileName) {
|
if (item.id === fileId || item.name === fileName) {
|
||||||
console.log(`更新文件 ${item.name} 进度: ${item.progress} -> ${progress}`);
|
console.log(`更新文件 ${item.name} 进度: ${item.progress} -> ${progress}${transferSpeed ? `, 速度: ${transferSpeed} B/s` : ''}`);
|
||||||
return { ...item, progress, status: newStatus };
|
return {
|
||||||
|
...item,
|
||||||
|
progress,
|
||||||
|
status: newStatus,
|
||||||
|
transferSpeed: transferSpeed ?? item.transferSpeed,
|
||||||
|
startTime: newStatus === 'downloading' && !item.startTime ? Date.now() : item.startTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}));
|
}));
|
||||||
|
|||||||
273
chuan-next/src/lib/transfer-utils.ts
Normal file
273
chuan-next/src/lib/transfer-utils.ts
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
/**
|
||||||
|
* 传输速度和时间计算工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface TransferSpeed {
|
||||||
|
bytesPerSecond: number;
|
||||||
|
displaySpeed: string;
|
||||||
|
unit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransferProgress {
|
||||||
|
totalBytes: number;
|
||||||
|
transferredBytes: number;
|
||||||
|
percentage: number;
|
||||||
|
speed: TransferSpeed;
|
||||||
|
remainingTime: {
|
||||||
|
seconds: number;
|
||||||
|
display: string;
|
||||||
|
};
|
||||||
|
elapsedTime: {
|
||||||
|
seconds: number;
|
||||||
|
display: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化传输速度显示
|
||||||
|
* @param bytesPerSecond 每秒传输的字节数
|
||||||
|
* @returns 格式化的速度显示
|
||||||
|
*/
|
||||||
|
export function formatTransferSpeed(bytesPerSecond: number): TransferSpeed {
|
||||||
|
if (bytesPerSecond < 1024) {
|
||||||
|
return {
|
||||||
|
bytesPerSecond,
|
||||||
|
displaySpeed: `${bytesPerSecond.toFixed(0)}`,
|
||||||
|
unit: 'B/s'
|
||||||
|
};
|
||||||
|
} else if (bytesPerSecond < 1024 * 1024) {
|
||||||
|
const kbps = bytesPerSecond / 1024;
|
||||||
|
return {
|
||||||
|
bytesPerSecond,
|
||||||
|
displaySpeed: `${kbps.toFixed(1)}`,
|
||||||
|
unit: 'KB/s'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const mbps = bytesPerSecond / (1024 * 1024);
|
||||||
|
return {
|
||||||
|
bytesPerSecond,
|
||||||
|
displaySpeed: `${mbps.toFixed(1)}`,
|
||||||
|
unit: 'MB/s'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间显示
|
||||||
|
* @param seconds 秒数
|
||||||
|
* @returns 格式化的时间显示
|
||||||
|
*/
|
||||||
|
export function formatTime(seconds: number): string {
|
||||||
|
if (!isFinite(seconds) || seconds < 0) {
|
||||||
|
return '--:--';
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
} else {
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输进度跟踪器
|
||||||
|
*/
|
||||||
|
export class TransferProgressTracker {
|
||||||
|
private startTime: number;
|
||||||
|
private lastUpdateTime: number;
|
||||||
|
private lastSpeedUpdateTime: number;
|
||||||
|
private lastProgressUpdateTime: number;
|
||||||
|
private lastTransferredBytes: number;
|
||||||
|
private speedHistory: number[] = [];
|
||||||
|
private readonly maxHistorySize = 10; // 保持最近10个速度样本
|
||||||
|
private readonly speedUpdateInterval = 300; // 速度更新间隔:0.3秒
|
||||||
|
private readonly progressUpdateInterval = 50; // 进度更新间隔:0.3秒
|
||||||
|
private cachedProgress: TransferProgress | null = null;
|
||||||
|
private lastDisplayedSpeed: TransferSpeed;
|
||||||
|
private lastDisplayedPercentage: number = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private totalBytes: number,
|
||||||
|
private initialTransferredBytes: number = 0
|
||||||
|
) {
|
||||||
|
this.startTime = Date.now();
|
||||||
|
this.lastUpdateTime = this.startTime;
|
||||||
|
this.lastSpeedUpdateTime = this.startTime;
|
||||||
|
this.lastProgressUpdateTime = this.startTime;
|
||||||
|
this.lastTransferredBytes = initialTransferredBytes;
|
||||||
|
this.lastDisplayedSpeed = formatTransferSpeed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新传输进度
|
||||||
|
* @param transferredBytes 已传输的字节数
|
||||||
|
* @returns 传输进度信息
|
||||||
|
*/
|
||||||
|
update(transferredBytes: number): TransferProgress {
|
||||||
|
const now = Date.now();
|
||||||
|
const elapsedTimeMs = now - this.startTime;
|
||||||
|
const timeSinceLastUpdate = now - this.lastUpdateTime;
|
||||||
|
const timeSinceLastSpeedUpdate = now - this.lastSpeedUpdateTime;
|
||||||
|
const timeSinceLastProgressUpdate = now - this.lastProgressUpdateTime;
|
||||||
|
|
||||||
|
// 计算即时速度(基于最近的更新)
|
||||||
|
let instantSpeed = 0;
|
||||||
|
if (timeSinceLastUpdate > 0) {
|
||||||
|
const bytesDiff = transferredBytes - this.lastTransferredBytes;
|
||||||
|
instantSpeed = (bytesDiff * 1000) / timeSinceLastUpdate; // bytes per second
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有当距离上次速度更新超过指定间隔时才更新速度显示
|
||||||
|
let shouldUpdateSpeed = timeSinceLastSpeedUpdate >= this.speedUpdateInterval;
|
||||||
|
|
||||||
|
// 只有当距离上次进度更新超过指定间隔时才更新进度显示
|
||||||
|
let shouldUpdateProgress = timeSinceLastProgressUpdate >= this.progressUpdateInterval;
|
||||||
|
|
||||||
|
// 如果是第一次更新或者传输完成,立即更新速度和进度
|
||||||
|
if (this.cachedProgress === null || transferredBytes >= this.totalBytes) {
|
||||||
|
shouldUpdateSpeed = true;
|
||||||
|
shouldUpdateProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdateSpeed) {
|
||||||
|
// 更新速度历史
|
||||||
|
if (instantSpeed > 0) {
|
||||||
|
this.speedHistory.push(instantSpeed);
|
||||||
|
if (this.speedHistory.length > this.maxHistorySize) {
|
||||||
|
this.speedHistory.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算平均速度
|
||||||
|
let averageSpeed = 0;
|
||||||
|
if (this.speedHistory.length > 0) {
|
||||||
|
averageSpeed = this.speedHistory.reduce((sum, speed) => sum + speed, 0) / this.speedHistory.length;
|
||||||
|
} else if (elapsedTimeMs > 0) {
|
||||||
|
// 如果没有即时速度历史,使用总体平均速度
|
||||||
|
averageSpeed = (transferredBytes * 1000) / elapsedTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新显示的速度
|
||||||
|
this.lastDisplayedSpeed = formatTransferSpeed(averageSpeed);
|
||||||
|
this.lastSpeedUpdateTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新显示的进度百分比
|
||||||
|
if (shouldUpdateProgress) {
|
||||||
|
const currentPercentage = this.totalBytes > 0 ? (transferredBytes / this.totalBytes) * 100 : 0;
|
||||||
|
this.lastDisplayedPercentage = Math.min(currentPercentage, 100);
|
||||||
|
this.lastProgressUpdateTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算剩余时间(使用当前显示的速度)
|
||||||
|
const remainingBytes = this.totalBytes - transferredBytes;
|
||||||
|
const remainingTimeSeconds = this.lastDisplayedSpeed.bytesPerSecond > 0
|
||||||
|
? remainingBytes / this.lastDisplayedSpeed.bytesPerSecond
|
||||||
|
: Infinity;
|
||||||
|
|
||||||
|
// 更新跟踪状态
|
||||||
|
this.lastUpdateTime = now;
|
||||||
|
this.lastTransferredBytes = transferredBytes;
|
||||||
|
|
||||||
|
// 创建进度对象(使用稳定的进度值)
|
||||||
|
const progress: TransferProgress = {
|
||||||
|
totalBytes: this.totalBytes,
|
||||||
|
transferredBytes,
|
||||||
|
percentage: this.lastDisplayedPercentage,
|
||||||
|
speed: this.lastDisplayedSpeed,
|
||||||
|
remainingTime: {
|
||||||
|
seconds: remainingTimeSeconds,
|
||||||
|
display: formatTime(remainingTimeSeconds)
|
||||||
|
},
|
||||||
|
elapsedTime: {
|
||||||
|
seconds: elapsedTimeMs / 1000,
|
||||||
|
display: formatTime(elapsedTimeMs / 1000)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 缓存进度信息
|
||||||
|
this.cachedProgress = progress;
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置跟踪器
|
||||||
|
*/
|
||||||
|
reset(totalBytes?: number, initialTransferredBytes: number = 0) {
|
||||||
|
if (totalBytes !== undefined) {
|
||||||
|
this.totalBytes = totalBytes;
|
||||||
|
}
|
||||||
|
this.startTime = Date.now();
|
||||||
|
this.lastUpdateTime = this.startTime;
|
||||||
|
this.lastSpeedUpdateTime = this.startTime;
|
||||||
|
this.lastProgressUpdateTime = this.startTime;
|
||||||
|
this.lastTransferredBytes = initialTransferredBytes;
|
||||||
|
this.speedHistory = [];
|
||||||
|
this.cachedProgress = null;
|
||||||
|
this.lastDisplayedSpeed = formatTransferSpeed(0);
|
||||||
|
this.lastDisplayedPercentage = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取总字节数
|
||||||
|
*/
|
||||||
|
getTotalBytes(): number {
|
||||||
|
return this.totalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取平均速度(整个传输过程)
|
||||||
|
*/
|
||||||
|
getOverallAverageSpeed(): number {
|
||||||
|
const elapsedTimeMs = Date.now() - this.startTime;
|
||||||
|
if (elapsedTimeMs > 0) {
|
||||||
|
return (this.lastTransferredBytes * 1000) / elapsedTimeMs;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建传输进度跟踪器
|
||||||
|
* @param totalBytes 总字节数
|
||||||
|
* @param initialTransferredBytes 初始已传输字节数
|
||||||
|
* @returns 传输进度跟踪器实例
|
||||||
|
*/
|
||||||
|
export function createTransferTracker(totalBytes: number, initialTransferredBytes: number = 0): TransferProgressTracker {
|
||||||
|
return new TransferProgressTracker(totalBytes, initialTransferredBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的传输速度计算(无状态)
|
||||||
|
* @param transferredBytes 已传输字节数
|
||||||
|
* @param elapsedTimeMs 经过的时间(毫秒)
|
||||||
|
* @returns 格式化的速度
|
||||||
|
*/
|
||||||
|
export function calculateSpeed(transferredBytes: number, elapsedTimeMs: number): TransferSpeed {
|
||||||
|
if (elapsedTimeMs <= 0) {
|
||||||
|
return formatTransferSpeed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytesPerSecond = (transferredBytes * 1000) / elapsedTimeMs;
|
||||||
|
return formatTransferSpeed(bytesPerSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算剩余时间
|
||||||
|
* @param remainingBytes 剩余字节数
|
||||||
|
* @param bytesPerSecond 每秒传输字节数
|
||||||
|
* @returns 格式化的剩余时间
|
||||||
|
*/
|
||||||
|
export function calculateRemainingTime(remainingBytes: number, bytesPerSecond: number): string {
|
||||||
|
if (bytesPerSecond <= 0 || remainingBytes <= 0) {
|
||||||
|
return '--:--';
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingSeconds = remainingBytes / bytesPerSecond;
|
||||||
|
return formatTime(remainingSeconds);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user