//
|
// LiveActivity.swift
|
// LiveProject
|
//
|
// Created by 倪路朋 on 6/25/25.
|
//
|
|
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 {
|
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)")
|
}
|
|
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;
|
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:streamRate == (9/16.0) ? Icons.PORTRAIT : Icons.LANDSCAPE ){
|
streamRate = streamRate == (9/16.0) ? (16/9.0) : (9/16.0)
|
updateWindowSize()
|
}
|
// 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()
|
}
|
}
|
/*flLButton(text: "文件"){
|
|
}*/
|
LButton(text: "文本"){
|
print("Click 文本 button")
|
withAnimation{
|
showInputDialog.toggle()
|
}
|
}
|
}
|
HStack{
|
Text("编码正常").font(Font.system(size: 14)).foregroundColor(Color.init("ColorGreen"))
|
Text("推流正常").font(Font.system(size: 14)).foregroundColor(Color.init("ColorGreen"))
|
Text("内存正常").font(Font.system(size: 14)).foregroundColor(Color.init("ColorGreen"))
|
}
|
}
|
}
|
|
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_BottomBtns_Previews: PreviewProvider{
|
static var previews: some View {
|
LiveActivity();
|
}
|
|
}
|