diff --git a/README.md b/README.md index 691172f..8a00ee5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **安全、快速、简单的点对点文件传输解决方案 - 无需注册,即传即用** -## [在线体验](https://transfer.52python.cn) • [GitHub](https://github.com/MatrixSeven/file-transfer-go) +## [在线体验](https://transfer.52python.cn) • [GitHub](https://github.com/MatrixSeven/file-transfer-go) • [X关注我](https://x.com/_MatrixSeven)  @@ -31,6 +31,9 @@ ## 🔄 最近更新日志 +### 2025-09--1 +- ✅ **移动端桌面全屏** - 优化移动端下UI,并解决全屏问题 + ### 2025-08-28 - ✅ **完善Docker部署支持** - 优化Docker配置,支持一键部署和多环境配置 - ✅ **优化README文档** - 更新项目说明,完善部署指南和技术栈信息 diff --git a/chuan-next/src/components/DesktopViewer.tsx b/chuan-next/src/components/DesktopViewer.tsx index 04f865c..f3accf0 100644 --- a/chuan-next/src/components/DesktopViewer.tsx +++ b/chuan-next/src/components/DesktopViewer.tsx @@ -144,7 +144,33 @@ export default function DesktopViewer({ // 全屏时自动隐藏控制栏,鼠标移动时显示 setShowControls(false); } else { + // 退出全屏时显示控制栏 setShowControls(true); + + // 延迟检查视频状态,确保全屏切换完成 + setTimeout(() => { + if (videoRef.current && stream) { + console.log('[DesktopViewer] 🔄 退出全屏,检查视频状态'); + + // 确保视频流正确设置 + const currentSrcObject = videoRef.current.srcObject; + if (!currentSrcObject || currentSrcObject !== stream) { + videoRef.current.srcObject = stream; + } + + // 检查视频是否暂停 + if (videoRef.current.paused) { + console.log('[DesktopViewer] ⏸️ 退出全屏后视频已暂停,显示播放按钮'); + setIsPlaying(false); + setNeedsUserInteraction(true); + hasAttemptedAutoplayRef.current = true; // 标记已尝试过自动播放 + } else { + console.log('[DesktopViewer] ▶️ 退出全屏后视频仍在播放'); + setIsPlaying(true); + setNeedsUserInteraction(false); + } + } + }, 200); // 延迟200ms确保全屏切换完成 } }; @@ -153,7 +179,7 @@ export default function DesktopViewer({ return () => { document.removeEventListener('fullscreenchange', handleFullscreenChange); }; - }, []); + }, [stream]); // 鼠标移动处理(全屏时) const handleMouseMove = useCallback(() => { @@ -207,13 +233,43 @@ export default function DesktopViewer({ // 切换全屏 const toggleFullscreen = useCallback(async () => { - if (!containerRef.current) return; + if (!videoRef.current) return; try { if (isFullscreen) { - await document.exitFullscreen(); + // 退出全屏 + if (document.fullscreenElement) { + await document.exitFullscreen(); + } + // 退出iOS全屏模式 + if ((document as any).webkitExitFullscreen) { + await (document as any).webkitExitFullscreen(); + } + // 退出视频全屏模式 + if ((videoRef.current as any).webkitExitFullscreen) { + await (videoRef.current as any).webkitExitFullscreen(); + } + // 退出Android全屏模式 + if ((videoRef.current as any).exitFullscreen) { + await (videoRef.current as any).exitFullscreen(); + } } else { - await containerRef.current.requestFullscreen(); + // 进入标准全屏 + if (videoRef.current.requestFullscreen) { + await videoRef.current.requestFullscreen(); + } + // 进入iOS全屏模式 + else if ((videoRef.current as any).webkitRequestFullscreen) { + await (videoRef.current as any).webkitRequestFullscreen(); + } + // 进入iOS视频全屏模式 + else if ((videoRef.current as any).webkitEnterFullscreen) { + await (videoRef.current as any).webkitEnterFullscreen(); + } + // 进入Android全屏模式 + else if ((videoRef.current as any).requestFullscreen) { + await (videoRef.current as any).requestFullscreen(); + } } } catch (error) { console.error('[DesktopViewer] 全屏切换失败:', error); @@ -223,9 +279,22 @@ export default function DesktopViewer({ // 退出全屏 const exitFullscreen = useCallback(async () => { try { + // 退出标准全屏 if (document.fullscreenElement) { await document.exitFullscreen(); } + // 退出iOS全屏模式 + if ((document as any).webkitExitFullscreen) { + await (document as any).webkitExitFullscreen(); + } + // 退出视频全屏模式 + if (videoRef.current && (videoRef.current as any).webkitExitFullscreen) { + await (videoRef.current as any).webkitExitFullscreen(); + } + // 退出Android全屏模式 + if (videoRef.current && (videoRef.current as any).exitFullscreen) { + await (videoRef.current as any).exitFullscreen(); + } } catch (error) { console.error('[DesktopViewer] 退出全屏失败:', error); } @@ -301,15 +370,15 @@ export default function DesktopViewer({ }} /> - {/* 需要用户交互的播放覆盖层 - 只在自动播放尝试失败后显示 */} - {hasAttemptedAutoplayRef.current && needsUserInteraction && !isPlaying && ( + {/* 需要用户交互的播放覆盖层 - 在视频暂停时显示 */} + {((needsUserInteraction && !isPlaying) || (isConnected && !isPlaying && !needsUserInteraction && videoRef.current?.paused)) && (
浏览器需要用户交互才能开始播放媒体
+视频已暂停,点击继续播放
diff --git a/chuan-next/src/hooks/connection/useSharedWebRTCManager.ts b/chuan-next/src/hooks/connection/useSharedWebRTCManager.ts
index c83514e..c864559 100644
--- a/chuan-next/src/hooks/connection/useSharedWebRTCManager.ts
+++ b/chuan-next/src/hooks/connection/useSharedWebRTCManager.ts
@@ -239,7 +239,7 @@ export function useSharedWebRTCManager(): WebRTCConnection {
}
// 构建完整的WebSocket URL
- const wsUrl = baseWsUrl.replace('/ws/p2p', `/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`);
+ const wsUrl = `${baseWsUrl}/api/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`;
console.log('[SharedWebRTC] 🌐 连接WebSocket:', wsUrl);
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
diff --git a/chuan-next/src/hooks/webrtc/WebRTCManager.ts b/chuan-next/src/hooks/webrtc/WebRTCManager.ts
index 8c8437f..ae37e80 100644
--- a/chuan-next/src/hooks/webrtc/WebRTCManager.ts
+++ b/chuan-next/src/hooks/webrtc/WebRTCManager.ts
@@ -318,7 +318,7 @@ export class WebRTCManager extends EventEmitter {
throw new WebRTCError('WS_URL_NOT_CONFIGURED', 'WebSocket URL未配置', false);
}
- const wsUrl = baseWsUrl.replace('/ws/p2p', `/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`);
+ const wsUrl = baseWsUrl.replace('/api/ws/webrtc', `/ws/webrtc?code=${roomCode}&role=${role}&channel=shared`);
this.wsManager = new WebSocketManager({
url: wsUrl,
reconnectAttempts: 5,
diff --git a/chuan-next/src/lib/config.ts b/chuan-next/src/lib/config.ts
index 2c743fc..10d1fdb 100644
--- a/chuan-next/src/lib/config.ts
+++ b/chuan-next/src/lib/config.ts
@@ -33,12 +33,12 @@ const getCurrentWsUrl = () => {
if (isNextDevServer) {
// 开发模式:通过 Next.js 开发服务器访问,连接到后端 WebSocket
- return 'ws://localhost:8080/ws/p2p';
+ return 'ws://localhost:8080';
}
// 生产模式或通过 Go 服务器访问:使用当前域名和端口
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
- return `${protocol}//${window.location.host}/ws/p2p`;
+ return `${protocol}//${window.location.host}`;
}
// 服务器端返回空字符串,强制在客户端计算
return '';
diff --git a/cmd/main.go b/cmd/main.go
index 07061a3..3a17bf0 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -58,6 +58,7 @@ func main() {
r.Handle("/*", web.CreateFrontendHandler())
// WebRTC信令WebSocket路由
+ r.Get("/api/ws/webrtc", h.HandleWebRTCWebSocket)
r.Get("/ws/webrtc", h.HandleWebRTCWebSocket)
// WebRTC房间API