From 3b7521a47ae731f0bf0a922822e4417493489539 Mon Sep 17 00:00:00 2001
From: Runt <qingingrunt2010@qq.com>
Date: Wed, 09 Jul 2025 10:42:02 +0000
Subject: [PATCH] 小窗布局,小窗移动

---
 LiveProject/activity/stream/LiveActivity.swift |  222 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 203 insertions(+), 19 deletions(-)

diff --git a/LiveProject/activity/stream/LiveActivity.swift b/LiveProject/activity/stream/LiveActivity.swift
index d6faae2..045e563 100644
--- a/LiveProject/activity/stream/LiveActivity.swift
+++ b/LiveProject/activity/stream/LiveActivity.swift
@@ -5,46 +5,219 @@
 //  Created by 倪路朋 on 6/25/25.
 //
 
-import Foundation
+import UIKit
+import AVFoundation
 import SwiftUI
+import MetalKit
 
 struct LiveActivity: View {
+    
+    @State private var showDeviceDialog = false
+    @State private var showInputDialog = false
+    
+    @State private var streamRate = Float(9/16.0);
+    @State private var fpsState = 30;
+    @State private var mainSize: CGSize = .init(width: 100, height: 100)
+    
+    @State private var displaySize : CGSize = .zero;
+    
+    @State private var devices = [DeviceInfo(name: "相机", type: .CAMERA, deviceId: UUID().uuidString,icon: Icons.CAMERA),
+                                  DeviceInfo(name: "话筒", type: .MICROPHONE,deviceId: UUID().uuidString,icon: Icons.MIC),
+                                  DeviceInfo(name: "系统", type: .SYSTEM,deviceId : UUID().uuidString,icon: Icons.PORTRAIT)]
+
+    @State private var miniWindows:Array<MiniWindowData> = []
+    
+    @StateObject private var mViewModel = LiveViewModel()
+    
     var body: some View {
-        NavigationView{
+        ZStack{
+            Color.clear
+                .ignoresSafeArea() // 填满全屏
             ZStack{
+                VideoRendererView(pixelBuffer: $mViewModel.pixelBuffer).background(Color.black).frame(width: mainSize.width,height:mainSize.height)
+                    .frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topTrailing)
+                ForEach(miniWindows, id: \.id) { miniWindow in
+                    NewMiniWindow(miniWindow: miniWindow)
+                }
+            }.border(Color.blue)
+            
+            VStack{
+                Spacer()
+                BottomBtns().frame(alignment: .bottom).border(Color.green)
+            }
+            if showInputDialog{
+                DialogInput()
+            }
+        }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
+            .background(
+                GeometryReader { geometry in
+                    Color.clear
+                        .onAppear {
+                            displaySize = geometry.size;
+                            updateWindowSize()
+                            print("displaySize:\(displaySize)")
+                        }
+                        .onChange(of: geometry.size) { newSize in
+                            displaySize = newSize;
+                            updateWindowSize()
+                            print("displaySize:\(displaySize)")
+                        }
+                })
+            .border(Color.red)
+            .onDisappear {
+                print("onDisappear 视图消失了!")
                 
+            }.onAppear {
+                print("onAppear 视图出现了!")
+            }
+    }
+    
+    func updateWindowSize(){
+        var rate : Float = Float(displaySize.width)/Float(displaySize.height);
+        if(rate != streamRate) {
+            var mainWidth = 0.0;
+            var mainHeight = 0.0;
+            if(rate < streamRate){
+                mainWidth = displaySize.width;
+                if(9.0/16 == streamRate){
+                    mainHeight = (mainWidth / 9.0 * 16);
+                }else{
+                    mainHeight = (mainWidth / 16.0 * 9);
+                }
+            }else{
+                mainHeight = displaySize.height;
+                if(9.0 / 16 == streamRate){
+                    mainWidth = (mainHeight / 16.0 * 9);
+                }else{
+                    mainWidth = (mainHeight / 9.0 * 16);
+                }
+            }
+            if(mainSize.width != CGFloat(mainWidth) || mainSize.height != CGFloat(mainHeight)){
+                mainSize = .init(width: mainWidth, height: mainHeight);
+            }
+        }else{
+            if(mainSize.width != displaySize.width || mainSize.height != displaySize.height){
+                mainSize = .init(width: displaySize.width, height: displaySize.height);
             }
         }
+        print("updateWindow:\(mainSize)")
     }
-}
-
-struct Bottombtns:View {
-    var body: some View {
+    
+    func DialogInput(onCancel:() ->Void = {},onConfirm:() -> Void = {}) -> some View{
+        ZStack{
+            Color.black.opacity(0.4)
+                .edgesIgnoringSafeArea(.all)
+                .onTapGesture {
+                    withAnimation {
+                        showInputDialog = false
+                    }
+                }
+            VStack {
+                VStack(alignment: .leading, spacing: 40) {
+                    Text("请输入直播地址")
+                        .font(Font.system(size: 20))
+                    LTextField().environmentObject(LText())
+                    HStack{
+                     Spacer()
+                        Button(action:{
+                            showInputDialog.toggle();
+                        }){
+                            Text("取消")
+                                .font(Font.system(size: 20))
+                                .foregroundColor(Color.gray)
+                        }
+                        
+                        Spacer().frame(width: 30)
+                        Button(action:{
+                            showInputDialog.toggle();
+                        }){
+                            Text("确认")
+                                .font(Font.system(size: 20))
+                                .foregroundColor(Color.colorTextLink)
+                        }
+                        
+                    }
+                }
+                .padding(30)
+                .background(Color.white)
+                .cornerRadius(20)
+                .transition(.move(edge: .bottom))
+            }
+            .padding(60)
+            .zIndex(1)
+            .animation(.default, value: devices)
+        }
+    }
+    
+    func DialogDevices() -> some View{
         VStack{
-            
+            VStack(spacing: 20) {
+                Spacer().frame(height:20)
+                FlowLayout(){
+                    
+                    ForEach(devices, id: \.self) { device in
+                        MButton(icon: device.icon,text: device.name){
+                            mViewModel.newWindowAction(device: device){ status in
+                                withAnimation{
+                                    showDeviceDialog = false;
+                                }
+                                miniWindows.append(MiniWindowData(streamType: device.type))
+                            }
+                            print("\(device.name) click \(self.miniWindows.count)")
+                        }
+                    }
+                }
+            }
+            .frame(maxWidth: .infinity,alignment:.leading)
+            .padding()
+        }
+        .frame(maxHeight: .infinity,alignment:.topLeading)
+    }
+    
+    func BottomBtns() -> some View{
+        VStack{
+        
             HStack(){
-                MButton(icon: IconPortrait()){
-                    
+                //横竖屏控制
+                MButton(icon:streamRate == (9/16.0) ? Icons.PORTRAIT : Icons.LANDSCAPE ){
+                    streamRate = streamRate == (9/16.0) ? (16/9.0) : (9/16.0)
+                    updateWindowSize()
                 }
-                MButton(text: "30帧"){
-                    
+                // fps 控制
+                MButton(text: "\(fpsState)帧"){
+                    fpsState = fpsState == 30 ? 60 : 30;
                 }
+                //添加推流地址
                 MButton(valid: .INVALID,text: "+"){
                     
                 }
             }
             HStack{
                 LButton(text: "设备"){
-                    
-                }
+                    print("Click 设备 button")
+                    showDeviceDialog.toggle()
+                }.sheet(isPresented:$showDeviceDialog, content: {
+                    VStack {
+                        ScrollView {
+                            DialogDevices()
+                        }
+                    }
+                    .presentationDetents([.height(200),.medium])
+                })
                 LButton(text: "RTMP"){
-                    
+                    print("Click RTMP button")
+                    withAnimation{
+                        showInputDialog.toggle()
+                    }
                 }
-                LButton(text: "文件"){
+                /*flLButton(text: "文件"){
                     
-                }
+                }*/
                 LButton(text: "文本"){
-                    
+                    print("Click 文本 button")
+                    withAnimation{
+                        showInputDialog.toggle()
+                    }
                 }
             }
             HStack{
@@ -54,11 +227,22 @@
             }
         }
     }
+    
+    func NewMiniWindow(miniWindow:MiniWindowData) -> some View{
+        MiniWindow(miniData: miniWindow)
+            .frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topLeading)
+            .onCloseClick {
+                guard let index = miniWindows.firstIndex(where: { $0.id == miniWindow.id }) else { return }
+                miniWindows.remove(at: index)
+            }
+    }
+    
 }
 
-struct LiveActivity_Previews: PreviewProvider{
+
+struct LiveActivity_BottomBtns_Previews: PreviewProvider{
     static var previews: some View {
-        Bottombtns();
+        LiveActivity();
     }
     
 }

--
Gitblit v1.9.1