| | |
| | | import MetalKit |
| | | |
| | | struct LiveActivity: View { |
| | | @State private var pixelBuffer: CVPixelBuffer? |
| | | |
| | | |
| | | @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 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())] |
| | | @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> = [] |
| | | |
| | | private let mViewModel = LiveViewModel() |
| | | @StateObject private var mViewModel = LiveViewModel() |
| | | |
| | | var body: some View { |
| | | ZStack{ |
| | | Color.clear |
| | | .ignoresSafeArea() // 填满全屏 |
| | | VStack{ |
| | | VideoRendererView(renderer:mViewModel.renderer).background(Color.black).frame(width: mainSize.width,height:mainSize.height) |
| | | Spacer() |
| | | }.border(Color.blue) |
| | | 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 showDeviceDialog { |
| | | DialogDevices() |
| | | if showInputDialog{ |
| | | DialogInput() |
| | | } |
| | | }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) |
| | | .background( |
| | |
| | | } |
| | | }) |
| | | .border(Color.red) |
| | | .clipped() |
| | | .onDisappear { |
| | | print("onDisappear 视图消失了!") |
| | | |
| | |
| | | print("updateWindow:\(mainSize)") |
| | | } |
| | | |
| | | func DialogDevices() -> some View{ |
| | | func DialogInput(onCancel:() ->Void = {},onConfirm:() -> Void = {}) -> some View{ |
| | | ZStack{ |
| | | Color.black.opacity(0.4) |
| | | .edgesIgnoringSafeArea(.all) |
| | | .onTapGesture { |
| | | withAnimation { |
| | | showDeviceDialog = false |
| | | showInputDialog = false |
| | | } |
| | | } |
| | | VStack { |
| | | Spacer() |
| | | VStack(spacing: 20) { |
| | | Spacer().frame(height:40) |
| | | FlowLayout(devices){ device in |
| | | MButton(icon: device.icon,text: device.name){ |
| | | mViewModel.newWindowAction(device: device){ status in |
| | | withAnimation{ |
| | | showDeviceDialog = false; |
| | | } |
| | | } |
| | | print("\(device.name) click") |
| | | 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() |
| | | } |
| | | .frame(maxWidth: .infinity) |
| | | .padding() |
| | | .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; |
| | | 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{ |
| | |
| | | |
| | | HStack(){ |
| | | //横竖屏控制 |
| | | MButton(icon:streamRate == (9/16.0) ? IconPortrait() : IconLandscape() ){ |
| | | MButton(icon:streamRate == (9/16.0) ? Icons.PORTRAIT : Icons.LANDSCAPE ){ |
| | | streamRate = streamRate == (9/16.0) ? (16/9.0) : (9/16.0) |
| | | updateWindowSize() |
| | | } |
| | |
| | | HStack{ |
| | | LButton(text: "设备"){ |
| | | print("Click 设备 button") |
| | | withAnimation{ |
| | | showDeviceDialog.toggle() |
| | | showDeviceDialog.toggle() |
| | | }.sheet(isPresented:$showDeviceDialog, content: { |
| | | VStack { |
| | | ScrollView { |
| | | DialogDevices() |
| | | } |
| | | } |
| | | } |
| | | .presentationDetents([.height(200),.medium]) |
| | | }) |
| | | LButton(text: "RTMP"){ |
| | | |
| | | print("Click RTMP button") |
| | | withAnimation{ |
| | | showInputDialog.toggle() |
| | | } |
| | | } |
| | | /*flLButton(text: "文件"){ |
| | | |
| | | }*/ |
| | | LButton(text: "文本"){ |
| | | |
| | | print("Click 文本 button") |
| | | withAnimation{ |
| | | showInputDialog.toggle() |
| | | } |
| | | } |
| | | } |
| | | HStack{ |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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) |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | |