| | |
| | | // Created by 倪路朋 on 6/25/25. |
| | | // |
| | | |
| | | import Foundation |
| | | import UIKit |
| | | import AVFoundation |
| | | import SwiftUI |
| | | import WrappingHStack |
| | | import MetalKit |
| | | |
| | | struct LiveActivity: View { |
| | | @State private var pixelBuffer: CVPixelBuffer? |
| | |
| | | @State private var showDeviceDialog = 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 devices = [DeviceInfo(name: "相机", type: .CAMERA(), deviceId: "相机"), |
| | | DeviceInfo(name: "话筒", type: .MICROPHONE(),deviceId: "话筒"), |
| | | DeviceInfo(name: "系统", type: .SYSTEM(),deviceId : "系统")] |
| | | @State private var displaySize : CGSize = .zero; |
| | | |
| | | @State private var devices = [DeviceInfo(name: "相机", type: .CAMERA, deviceId: UUID().uuidString,icon: IconCamera()), |
| | | DeviceInfo(name: "话筒", type: .MICROPHONE,deviceId: UUID().uuidString,icon: IconMic()), |
| | | DeviceInfo(name: "系统", type: .SYSTEM,deviceId : UUID().uuidString,icon: IconPortrait())] |
| | | |
| | | |
| | | private let mViewModel = LiveViewModel() |
| | | |
| | | var body: some View { |
| | | ZStack{ |
| | | Color.clear |
| | | .ignoresSafeArea() // 填满全屏 |
| | | VStack{ |
| | | VideoRendererView(renderer: MetalRenderer()).background(Color.black).frame(width: mainSize.width,height:mainSize.height) |
| | | VideoRendererView(renderer:mViewModel.renderer).background(Color.black).frame(width: mainSize.width,height:mainSize.height) |
| | | Spacer() |
| | | }.border(Color.blue) |
| | | VStack{ |
| | |
| | | GeometryReader { geometry in |
| | | Color.clear |
| | | .onAppear { |
| | | updateWindowSize(width: Int(geometry.size.width), height: Int(geometry.size.height)) |
| | | displaySize = geometry.size; |
| | | updateWindowSize() |
| | | print("displaySize:\(displaySize)") |
| | | } |
| | | .onChange(of: geometry.size) { newSize in |
| | | updateWindowSize(width: Int(newSize.width), height: Int(newSize.height)) |
| | | displaySize = newSize; |
| | | updateWindowSize() |
| | | print("displaySize:\(displaySize)") |
| | | } |
| | | }) |
| | | .border(Color.red) |
| | |
| | | } |
| | | } |
| | | |
| | | func updateWindowSize(width:Int,height:Int){ |
| | | var rate : Float = Float(width)/Float(height); |
| | | func updateWindowSize(){ |
| | | var rate : Float = Float(displaySize.width)/Float(displaySize.height); |
| | | if(rate != streamRate) { |
| | | var mainWidth = 0; |
| | | var mainHeight = 0; |
| | | var mainWidth = 0.0; |
| | | var mainHeight = 0.0; |
| | | if(rate < streamRate){ |
| | | mainWidth = width; |
| | | mainWidth = displaySize.width; |
| | | if(9.0/16 == streamRate){ |
| | | mainHeight = (mainWidth / 9 * 16); |
| | | mainHeight = (mainWidth / 9.0 * 16); |
| | | }else{ |
| | | mainHeight = (mainWidth / 16 * 9); |
| | | mainHeight = (mainWidth / 16.0 * 9); |
| | | } |
| | | }else{ |
| | | mainHeight = height; |
| | | mainHeight = displaySize.height; |
| | | if(9.0 / 16 == streamRate){ |
| | | mainWidth = (mainHeight / 16 * 9); |
| | | mainWidth = (mainHeight / 16.0 * 9); |
| | | }else{ |
| | | mainWidth = (mainHeight / 9 * 16); |
| | | mainWidth = (mainHeight / 9.0 * 16); |
| | | } |
| | | } |
| | | if(mainSize.width != CGFloat(mainWidth) || mainSize.height != CGFloat(mainHeight)){ |
| | | mainSize = .init(width: mainWidth, height: mainHeight); |
| | | } |
| | | }else{ |
| | | if(mainSize.width != CGFloat(width) || mainSize.height != CGFloat(height)){ |
| | | mainSize = .init(width: width, height: height); |
| | | if(mainSize.width != displaySize.width || mainSize.height != displaySize.height){ |
| | | mainSize = .init(width: displaySize.width, height: displaySize.height); |
| | | } |
| | | } |
| | | //Log.w(TAG , "onSizeChanged: ${mainWindowSize.value}" , ) |
| | | print("updateWindow:\(mainSize)") |
| | | } |
| | | |
| | | func DialogDevices() -> some View{ |
| | |
| | | VStack(spacing: 20) { |
| | | Spacer().frame(height:40) |
| | | FlowLayout(devices){ device in |
| | | MButton(text:device.name){ |
| | | |
| | | MButton(icon: device.icon,text: device.name){ |
| | | mViewModel.newWindowAction(device: device){ status in |
| | | withAnimation{ |
| | | showDeviceDialog = false; |
| | | } |
| | | } |
| | | print("\(device.name) click") |
| | | } |
| | | } |
| | | .padding() |
| | | .animation(.default, value: devices) |
| | | } |
| | | .frame(maxWidth: .infinity) |
| | | .padding() |
| | |
| | | .transition(.move(edge: .bottom)) |
| | | } |
| | | .zIndex(1) |
| | | .animation(.default, value: devices) |
| | | } |
| | | } |
| | | |
| | |
| | | VStack{ |
| | | |
| | | HStack(){ |
| | | MButton(icon: IconPortrait()){ |
| | | |
| | | //横竖屏控制 |
| | | MButton(icon:streamRate == (9/16.0) ? IconPortrait() : IconLandscape() ){ |
| | | 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: "+"){ |
| | | |
| | | } |