// 文件传输相关功能 // 设置数据通道 function setupDataChannel(channel) { dataChannel = channel; let pendingChunkMeta = null; channel.onopen = () => { console.log('数据通道已打开'); isP2PConnected = true; updateP2PStatus(true); // 清除连接超时定时器 if (connectionTimeout) { clearTimeout(connectionTimeout); connectionTimeout = null; } }; channel.onmessage = (event) => { // 检查是否是二进制数据 if (event.data instanceof ArrayBuffer) { // 处理二进制数据块 if (pendingChunkMeta && currentRole === 'receiver') { receiveFileChunk(pendingChunkMeta, event.data); pendingChunkMeta = null; } } else { // 处理JSON消息 try { const message = JSON.parse(event.data); if (message.type === 'file-chunk-meta') { pendingChunkMeta = message; } else { handleDataChannelMessage(event.data); } } catch (error) { console.error('解析数据通道消息失败:', error); } } }; channel.onerror = (error) => { console.error('数据通道错误:', error); isP2PConnected = false; updateP2PStatus(false); }; channel.onclose = () => { console.log('数据通道已关闭'); isP2PConnected = false; updateP2PStatus(false); }; } // 更新P2P连接状态 function updateP2PStatus(connected) { const receiverStatus = document.getElementById('receiverStatus'); const downloadButtons = document.querySelectorAll('button[onclick^="downloadFile"]'); if (currentRole === 'receiver' && receiverStatus) { if (connected) { receiverStatus.innerHTML = `
P2P连接已建立,可以下载文件
`; // 启用下载按钮 downloadButtons.forEach(btn => { btn.disabled = false; btn.classList.remove('opacity-50', 'cursor-not-allowed'); btn.classList.add('hover:bg-blue-600'); }); } else { receiverStatus.innerHTML = `
正在建立P2P连接...
`; // 禁用下载按钮 downloadButtons.forEach(btn => { btn.disabled = true; btn.classList.add('opacity-50', 'cursor-not-allowed'); btn.classList.remove('hover:bg-blue-600'); }); } } } // 下载文件 function downloadFile(fileId) { if (!isP2PConnected || !dataChannel || dataChannel.readyState !== 'open') { alert('P2P连接未建立,请等待连接建立后重试'); return; } // 发送文件请求 const request = { type: 'file-request', fileId: fileId }; dataChannel.send(JSON.stringify(request)); showTransferProgress(fileId, 'downloading'); } // 处理数据通道消息 function handleDataChannelMessage(data) { try { const message = JSON.parse(data); switch (message.type) { case 'file-request': if (currentRole === 'sender') { sendFileData(message.fileId); } break; case 'file-info': if (currentRole === 'receiver') { // 存储文件信息用于下载 if (!fileTransfers.has(message.fileId)) { fileTransfers.set(message.fileId, { chunks: [], totalSize: message.size, receivedSize: 0, fileName: message.name, mimeType: message.mimeType }); } } break; case 'file-data': // 旧的file-data类型已被file-chunk-meta + 二进制数据替代 // 这里保留是为了向后兼容 if (currentRole === 'receiver') { receiveFileDataLegacy(message); } break; case 'file-complete': if (currentRole === 'receiver') { completeFileDownload(message.fileId); } break; } } catch (error) { console.error('处理数据通道消息失败:', error); } } // 发送文件数据 function sendFileData(fileId) { const fileIndex = parseInt(fileId.split('_')[1]); const file = selectedFiles[fileIndex]; if (!file) return; // 首先发送文件元信息 const fileInfo = { type: 'file-info', fileId: fileId, name: file.name, size: file.size, mimeType: file.type, lastModified: file.lastModified }; dataChannel.send(JSON.stringify(fileInfo)); const reader = new FileReader(); const chunkSize = 65536; // 增加到64KB chunks以提高速度 let offset = 0; const sendChunk = () => { const slice = file.slice(offset, offset + chunkSize); reader.readAsArrayBuffer(slice); }; reader.onload = (e) => { const chunk = e.target.result; // 使用更高效的方式传输二进制数据 if (dataChannel.readyState === 'open') { // 先发送元数据 const metadata = { type: 'file-chunk-meta', fileId: fileId, offset: offset, size: chunk.byteLength, total: file.size, isLast: offset + chunk.byteLength >= file.size }; dataChannel.send(JSON.stringify(metadata)); // 再发送二进制数据 dataChannel.send(chunk); } offset += chunk.byteLength; if (offset < file.size) { // 减少延迟以提高传输速度 setTimeout(sendChunk, 1); } else { dataChannel.send(JSON.stringify({ type: 'file-complete', fileId: fileId })); } }; sendChunk(); } // 接收文件块(二进制数据) function receiveFileChunk(meta, chunkData) { if (!fileTransfers.has(meta.fileId)) { // 如果没有文件信息,创建默认的 fileTransfers.set(meta.fileId, { chunks: [], totalSize: meta.total, receivedSize: 0, fileName: `unknown_file_${meta.fileId}`, mimeType: 'application/octet-stream' }); } const transfer = fileTransfers.get(meta.fileId); transfer.chunks.push(new Uint8Array(chunkData)); transfer.receivedSize += chunkData.byteLength; // 更新总大小(以防文件信息还没收到) if (transfer.totalSize !== meta.total) { transfer.totalSize = meta.total; } // 更新进度 updateTransferProgress(meta.fileId, transfer.receivedSize, transfer.totalSize); if (meta.isLast) { completeFileDownload(meta.fileId); } } // 接收文件数据(向后兼容的旧版本) function receiveFileDataLegacy(message) { if (!fileTransfers.has(message.fileId)) { // 如果没有文件信息,创建默认的 fileTransfers.set(message.fileId, { chunks: [], totalSize: message.total, receivedSize: 0, fileName: `unknown_file_${message.fileId}`, mimeType: 'application/octet-stream' }); } const transfer = fileTransfers.get(message.fileId); transfer.chunks.push(new Uint8Array(message.chunk)); transfer.receivedSize += message.chunk.length; // 更新总大小(以防文件信息还没收到) if (transfer.totalSize !== message.total) { transfer.totalSize = message.total; } // 更新进度 updateTransferProgress(message.fileId, transfer.receivedSize, transfer.totalSize); if (message.isLast) { completeFileDownload(message.fileId); } } // 完成文件下载 function completeFileDownload(fileId) { const transfer = fileTransfers.get(fileId); if (!transfer) return; // 合并所有chunks,使用正确的MIME类型 const blob = new Blob(transfer.chunks, { type: transfer.mimeType }); // 使用正确的文件名 const fileName = transfer.fileName || `downloaded_file_${fileId}`; // 创建下载链接 const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log(`文件下载完成: ${fileName}, 大小: ${formatFileSize(transfer.totalSize)}`); // 清理 fileTransfers.delete(fileId); hideTransferProgress(fileId); } // 显示传输进度 function showTransferProgress(fileId, type) { const progressContainer = document.getElementById('transferProgress'); const progressList = document.getElementById('progressList'); progressContainer.classList.remove('hidden'); // 获取文件名 let fileName = fileId; if (currentRole === 'receiver') { // 从接收方文件列表中获取文件名 const fileIndex = parseInt(fileId.split('_')[1]); const receiverFilesList = document.getElementById('receiverFilesList'); const fileItems = receiverFilesList.querySelectorAll('.font-medium'); if (fileItems[fileIndex]) { fileName = fileItems[fileIndex].textContent; } } else if (currentRole === 'sender') { // 从发送方文件列表中获取文件名 const fileIndex = parseInt(fileId.split('_')[1]); if (selectedFiles[fileIndex]) { fileName = selectedFiles[fileIndex].name; } } const progressItem = document.createElement('div'); progressItem.id = `progress_${fileId}`; progressItem.className = 'bg-gray-50 p-3 rounded-lg'; progressItem.innerHTML = `
${type === 'downloading' ? '📥 下载' : '📤 上传'}: ${fileName} 0%
`; progressList.appendChild(progressItem); } // 更新传输进度 function updateTransferProgress(fileId, received, total) { const progressItem = document.getElementById(`progress_${fileId}`); if (!progressItem) return; const percentage = Math.round((received / total) * 100); const progressBar = progressItem.querySelector('.bg-blue-600'); const percentageText = progressItem.querySelector('.text-gray-500'); progressBar.style.width = percentage + '%'; percentageText.textContent = percentage + '%'; } // 隐藏传输进度 function hideTransferProgress(fileId) { const progressItem = document.getElementById(`progress_${fileId}`); if (progressItem) { progressItem.remove(); } // 如果没有进度项了,隐藏整个进度容器 const progressList = document.getElementById('progressList'); if (progressList.children.length === 0) { document.getElementById('transferProgress').classList.add('hidden'); } }