浏览代码

优化课堂视频区域显示的布局,主体显示对方屏幕共享,对方摄像头的缩小叠加显示在右上角或右下角,增加开关控制对方摄像头的显示与否
要分开对方摄像头,和对方屏幕共享的显示容器,现在是都混在了 id="remoteVideoContainer"
remoteCameraContainer ref已正确引入ClassroomLayout, 统一命名remoteVideoContainer为remoteScreenContainer, 所有相关引用同步更新

yourname 7 月之前
父节点
当前提交
65a7b693eb

+ 35 - 11
client/mobile/components/Classroom/ClassroomLayout.tsx

@@ -19,7 +19,8 @@ export const ClassroomLayout = ({ children, role }: ClassroomLayoutProps) => {
   const [showVideo, setShowVideo] = React.useState(role !== Role.Teacher);
   const [showShareLink, setShowShareLink] = React.useState(false);
   const {
-    remoteVideoContainer,
+    remoteScreenContainer,
+    remoteCameraContainer,
     isCameraOn,
     isAudioOn,
     isScreenSharing,
@@ -33,23 +34,46 @@ export const ClassroomLayout = ({ children, role }: ClassroomLayoutProps) => {
     handUpList,
     questions,
     classStatus,
-    shareLink
+    shareLink,
+    showCameraOverlay,
+    setShowCameraOverlay
   } = useClassroomContext();
 
   return (
     <div className="flex flex-col md:flex-row h-screen bg-gray-100">
       {/* 视频区域 */}
       {showVideo && (
-        <div className="p-4 h-[300px] md:flex-1 md:h-auto">
-          <div className="bg-white rounded-lg shadow p-4 h-full">
-            <div
-              id="remoteVideoContainer"
-              ref={remoteVideoContainer}
-              className="grid grid-cols-1 md:grid-cols-2 gap-4"
-            >
-              {/* 远程视频将在这里动态添加 */}
-            </div>
+        <div className="relative h-[300px] md:flex-1 md:h-auto bg-black">
+          {/* 主屏幕共享容器 */}
+          <div
+            id="remoteScreenContainer"
+            ref={remoteScreenContainer}
+            className="w-full h-full"
+          >
+            {/* 屏幕共享视频将在这里动态添加 */}
+          </div>
+          
+          {/* 摄像头小窗容器 - 固定在右上角 */}
+          <div
+            id="remoteCameraContainer"
+            ref={remoteCameraContainer}
+            className={`absolute top-4 right-4 z-10 w-1/4 aspect-video ${
+              showCameraOverlay ? 'block' : 'hidden'
+            }`}
+          >
+            {/* 摄像头视频将在这里动态添加 */}
           </div>
+          
+          {/* 摄像头小窗开关按钮 */}
+          <button
+            onClick={() => setShowCameraOverlay(!showCameraOverlay)}
+            className={`absolute top-4 right-4 z-20 p-2 rounded-full ${
+              showCameraOverlay ? 'bg-green-500' : 'bg-gray-500'
+            } text-white`}
+            title={showCameraOverlay ? '隐藏摄像头小窗' : '显示摄像头小窗'}
+          >
+            <CameraIcon className="w-4 h-4" />
+          </button>
         </div>
       )}
 

+ 23 - 11
client/mobile/components/Classroom/useClassroom.ts

@@ -92,6 +92,7 @@ export const useClassroom = () => {
   const [questions, setQuestions] = useState<Question[]>([]);
   const [students, setStudents] = useState<Array<{id: string, name: string}>>([]);
   const [shareLink, setShareLink] = useState<string>('');
+  const [showCameraOverlay, setShowCameraOverlay] = useState<boolean>(true);
 
   // SDK实例
   const imEngine = useRef<ImEngine | null>(null);
@@ -99,7 +100,8 @@ export const useClassroom = () => {
   const imMessageManager = useRef<ImMessageManager | null>(null);
   const aliRtcEngine = useRef<AliRtcEngine | null>(null);
   const remoteVideoElMap = useRef<Record<string, HTMLVideoElement>>({});
-  const remoteVideoContainer = useRef<HTMLDivElement>(null);
+  const remoteScreenContainer = useRef<HTMLDivElement>(null); // 主屏幕共享容器(重命名)
+  const remoteCameraContainer = useRef<HTMLDivElement>(null); // 摄像头小窗容器
 
   // 辅助函数
   const showMessage = (text: string): void => {
@@ -275,7 +277,13 @@ export const useClassroom = () => {
     if (el) {
       aliRtcEngine.current!.setRemoteViewConfig(null, userId, type === 'camera' ? AliRtcVideoTrack.AliRtcVideoTrackCamera : AliRtcVideoTrack.AliRtcVideoTrackScreen);
       el.pause();
-      remoteVideoContainer.current?.removeChild(el);
+      
+      // 根据流类型从不同容器移除
+      if (type === 'camera') {
+        remoteCameraContainer.current?.removeChild(el);
+      } else {
+        remoteScreenContainer.current?.removeChild(el);
+      }
       delete remoteVideoElMap.current[vid];
     }
   };
@@ -321,13 +329,12 @@ export const useClassroom = () => {
             video.playsInline = true;
             video.className = 'w-80 h-45 mr-2 mb-2 bg-black';
             
-            if (!remoteVideoContainer.current) {
-              console.error('远程视频容器未找到');
+            if (!remoteCameraContainer.current) {
+              console.error('摄像头视频容器未找到');
               return;
             }
             
-            remoteVideoContainer.current.style.display = 'block';
-            remoteVideoContainer.current.appendChild(video);
+            remoteCameraContainer.current.appendChild(video);
             remoteVideoElMap.current[`camera_${userId}`] = video;
             
             aliRtcEngine.current!.setRemoteViewConfig(
@@ -346,6 +353,7 @@ export const useClassroom = () => {
           
         case 1: // 取消订阅
           console.log(`取消订阅用户 ${userId} 的视频流`);
+          showMessage(`取消订阅用户 ${userId} 的视频流`);
           removeRemoteVideo(userId, 'camera');
           break;
           
@@ -380,14 +388,14 @@ export const useClassroom = () => {
             const video = document.createElement('video');
             video.autoplay = true;
             video.playsInline = true;
-            video.className = 'w-80 h-45 mr-2 mb-2 bg-black';
+            video.className = 'w-full h-full bg-black';
             
-            if (!remoteVideoContainer.current) {
-              console.error('远程视频容器未找到');
+            if (!remoteScreenContainer.current) {
+              console.error('屏幕共享容器未找到');
               return;
             }
             
-            remoteVideoContainer.current.appendChild(video);
+            remoteScreenContainer.current.appendChild(video);
             remoteVideoElMap.current[`screen_${userId}`] = video;
             
             aliRtcEngine.current!.setRemoteViewConfig(
@@ -406,6 +414,7 @@ export const useClassroom = () => {
           
         case 1: // 取消订阅
           console.log(`取消订阅用户 ${userId} 的屏幕分享流`);
+          showMessage(`取消订阅用户 ${userId} 的屏幕分享流`);
           removeRemoteVideo(userId, 'screen');
           break;
           
@@ -917,7 +926,10 @@ export const useClassroom = () => {
     questions,
     students,
     shareLink,
-    remoteVideoContainer,
+    remoteScreenContainer, // 重命名为remoteScreenContainer
+    remoteCameraContainer, // 导出摄像头容器ref
+    showCameraOverlay,
+    setShowCameraOverlay,
 
     // 方法
     login,

+ 4 - 1
版本迭代需求.md

@@ -1,3 +1,6 @@
 2025.05.13 0.1.0
 在移动端加入课堂入口
-排查课堂选择角色后,一直停留在自动登录的原因
+排查课堂选择角色后,一直停留在自动登录的原因
+优化课堂视频区域显示的布局,主体显示对方屏幕共享,对方摄像头的缩小叠加显示在右上角或右下角,增加开关控制对方摄像头的显示与否
+要分开对方摄像头,和对方屏幕共享的显示容器,现在是都混在了 id="remoteVideoContainer"
+remoteCameraContainer ref已正确引入ClassroomLayout, 统一命名remoteVideoContainer为remoteScreenContainer, 所有相关引用同步更新