Runt
2025-07-27 7b3ecfffc59d2d980d9f7628365b64c20fe015be
LiveProject/activity/stream/LiveActivity.swift
@@ -5,46 +5,252 @@
//  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{
                let optionalIntBinding = Binding<Int?>(
                    get: { 0 },
                    set: { newValue in }
                )
                VideoRendererView(pixelBuffer: $mViewModel.pixelBuffer,rotate: optionalIntBinding).background(Color.black).frame(width: mainSize.width,height:mainSize.height)
                    .frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topTrailing)
                
            }.border(Color.blue).clipped()
                .overlay(
                    ForEach(miniWindows, id: \.id) { miniWindow in
                        NewMiniWindow(miniData: miniWindow)
                    }
                )
            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)
            .clipped()
            .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){
                            var miniData = MiniWindowData(streamType: device.type);
                            switch device.type{
                            case .CAMERA:
                                miniData.hasAudio = false;
                                miniData.hasVideo = true;
                                break;
                            case .MICROPHONE:
                                miniData.hasAudio = true;
                                miniData.hasVideo = false;
                                break;
                            case .VIDEO:
                                miniData.hasAudio = true;
                                miniData.hasVideo = true;
                                miniData.speakerState = .MUTE;
                                break;
                            case .PICTURE:
                                miniData.hasVideo = true;
                                miniData.hasAudio = false;
                                break;
                            default:
                                break
                            }
                            miniData.mainSize = mainSize
                            miniData.name = device.name;
                            mViewModel.newWindowAction(minidata: miniData){ status in
                                withAnimation{
                                    showDeviceDialog = false;
                                }
                                miniWindows.append(miniData);
                            }
                            print("\(device.name) \(device.type) 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 +260,23 @@
            }
        }
    }
    func NewMiniWindow(miniData:MiniWindowData) -> some View{
        return MiniWindow(miniData: miniData)
            .frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topLeading)
            .onCloseClick {
                guard let index = miniWindows.firstIndex(where: { $0.id == miniData.id }) else { return }
                miniWindows.remove(at: index)
                mViewModel.closeWindowAction(miniData: miniData)
            }
    }
}
struct LiveActivity_Previews: PreviewProvider{
struct LiveActivity_BottomBtns_Previews: PreviewProvider{
    static var previews: some View {
        Bottombtns();
        LiveActivity();
    }
    
}