From 7b3ecfffc59d2d980d9f7628365b64c20fe015be Mon Sep 17 00:00:00 2001 From: Runt <qingingrunt2010@qq.com> Date: Sun, 27 Jul 2025 09:42:03 +0000 Subject: [PATCH] 多个小窗缩放问题修复 --- LiveProject/activity/stream/views/MiniWindow.swift | 138 ++++++++++++++++++++++++++++++++++++---------- 1 files changed, 108 insertions(+), 30 deletions(-) diff --git a/LiveProject/activity/stream/views/MiniWindow.swift b/LiveProject/activity/stream/views/MiniWindow.swift index 357e6a0..596b66d 100644 --- a/LiveProject/activity/stream/views/MiniWindow.swift +++ b/LiveProject/activity/stream/views/MiniWindow.swift @@ -26,22 +26,36 @@ } @Published var pixelBuffer: CVPixelBuffer? = nil - @Published var size:CGSize = CGSize(width:300,height:200) - @Published var position:CGSize = CGSize(width: 0, height: 0) + //流尺寸(图片和视频的分辨率) + @Published var size:CGSize = CGSize(width:16,height:9) + //视图比例(相对于 main 尺寸) + @Published var viewRate = 0.5 + //位置 + @Published var position:CGPoint = CGPoint(x: 0, y: 0) + //主选框位置 @Published var mainPosition:CGSize = CGSize(width: 0, height: 0) + //main 尺寸 @Published var mainSize:CGSize = CGSize(width:200,height:100) + //是否加入直播 @Published var videoState:LiveState = .IN_LIVE @Published var audioState:LiveState = .IN_LIVE + //是否静音 @Published var speakerState:MuteState = .UN_MUTE + //包含视频和图片 @Published var hasVideo = true; + //包含音频 @Published var hasAudio = true; + //旋转角度 @Published var rotate = 0; + //fps @Published var frameFPS = 0; + // @Published var name = " 名称"; + //相机焦距 @Published var minZoom = 0; @Published var maxZoom = 0; @Published var zoom = 1.0 - + //文本颜色 @Published var textColor = Color.white; } struct MiniWindowActions { @@ -72,11 +86,25 @@ @ObservedObject var miniData:MiniWindowData @State var lastLocation = CGSize(width: 0, height: 0) + @State var lastZoom = 0.0 @Environment(\.miniWindowActions) var actions var body: some View { - ZStack(){ - VideoRendererView(pixelBuffer: $miniData.pixelBuffer).background(Color.black) + var width = (miniData.mainSize.width * miniData.viewRate); + var height = (miniData.mainSize.width / (miniData.size.width / miniData.size.height) * miniData.viewRate); + if(miniData.rotate == 90 || miniData.rotate == 270){ + var temp = width; + width = height; + height = temp; + } + return ZStack(){ + let optionalIntBinding = Binding<Int?>( + get: { miniData.rotate }, + set: { newValue in + miniData.rotate = newValue ?? 0 + } + ) + VideoRendererView(pixelBuffer: $miniData.pixelBuffer,rotate:optionalIntBinding).background(Color.black) //名称 Text(self.miniData.name) @@ -104,44 +132,72 @@ .clipShape(RoundedCorner(radius: 20,corners: [.bottomRight])) .frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topLeading) //关闭 - IconButton(info: Icons.CLOSE,action:actions.onCloseClick).frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topTrailing) - .padding(EdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 10)) + IconButton(info: Icons.CLOSE,action:actions.onCloseClick,width: 40,height: 40).frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topTrailing) + .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) if(self.miniData.streamType != .TEXT && self.miniData.hasVideo){ //旋转 VStack{ Spacer() - IconButton(info: Icons.ROTATE_LEFT,action:actions.onRotateLeftClick).padding(EdgeInsets(top: 0, leading: 13, bottom: 0, trailing: 0)) + IconButton(info: Icons.ROTATE_LEFT,action:{ + if (self.miniData.rotate == 0) { + self.miniData.rotate = 270 + } else { + self.miniData.rotate -= 90 + } + actions.onRotateLeftClick() + },width: 40,height: 40).padding(EdgeInsets(top: 0, leading: 13, bottom: 0, trailing: 0)) Spacer() - IconButton(info: Icons.ROTATE_RIGHT,action:actions.onRotateRightClick).padding(EdgeInsets(top: 0, leading: 13, bottom: 0, trailing: 11)) + IconButton(info: Icons.ROTATE_RIGHT,action:{ + self.miniData.rotate = (self.miniData.rotate + 90) % 360 + actions.onRotateRightClick() + },width: 40,height: 40).padding(EdgeInsets(top: 0, leading: 13, bottom: 0, trailing: 11)) Spacer() }.frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .topTrailing) - .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 10)) + .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } - BottomBtns().background(Color.gray.opacity(0.4)).frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .bottom) + BottomBtns().background(Color.black.opacity(0.4)).frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .bottom) - }.background(Color.blue).frame(width: miniData.size.width,height:miniData.size.height) - .offset(self.miniData.position) - .gesture(DragGesture(minimumDistance: 0) + }.background(Color.blue).frame(width:width,height:height) + .position(self.miniData.position) + .gesture(SimultaneousGesture(DragGesture(minimumDistance: 0) .onChanged{ val in - let x = self.miniData.position.width - let y = self.miniData.position.height + let x = self.miniData.position.x + let y = self.miniData.position.y let newX = x - lastLocation.width + val.translation.width; let newY = y - lastLocation.height + val.translation.height; - self.miniData.position = CGSize(width: newX, height: newY) - print(" onChanged \(index) \(self.miniData.position) ") + self.miniData.position = CGPoint(x: newX, y: newY) + //print(" onChanged \(index) \(self.miniData.position) ") lastLocation = val.translation; }.onEnded{ val in - let x = self.miniData.position.width - let y = self.miniData.position.height + let x = self.miniData.position.x + let y = self.miniData.position.y let newX = x - lastLocation.width + val.translation.width; let newY = y - lastLocation.height + val.translation.height; - self.miniData.position = CGSize(width: newX, height: newY) - print(" onChanged \(index) \(self.miniData.position) ") + self.miniData.position = CGPoint(x: newX, y: newY) + //print(" onChanged \(index) \(self.miniData.position) ") lastLocation = CGSize(width: 0, height: 0); - print(" onEnded \(val)") - }) + //print(" onEnded \(val)") + },MagnifyGesture().onChanged{ val in + if(lastZoom > 0){ + var l = val.magnification - lastZoom; + miniData.viewRate += l; + if(miniData.viewRate < 0.4){ + miniData.viewRate = 0.4 + }else{ + } + } + lastZoom = val.magnification; + }.onEnded{ val in + var l = val.magnification - lastZoom; + miniData.viewRate += l; + if(miniData.viewRate < 0.4){ + miniData.viewRate = 0.4 + }else{ + } + lastZoom = 0; + })) } /** @@ -179,7 +235,7 @@ actions.onMicClick() } Spacer() - if(self.miniData.streamType != .MICROPHONE && self.miniData.streamType != .UAC && self.miniData.streamType != .SYSTEM ){ + if(self.miniData.streamType != .MICROPHONE && self.miniData.streamType != .UAC && self.miniData.streamType != .SYSTEM && self.miniData.streamType != .CAMERA){ IconButton(info: self.miniData.speakerState == .UN_MUTE ? Icons.SPEAKER : Icons.SPEAKER_MUTE){ if(self.miniData.speakerState == .MUTE){ self.miniData.speakerState = .UN_MUTE @@ -201,8 +257,8 @@ }.frame(maxWidth: .infinity) .padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15)) } - - func IconButton(info:IconInfo,action:@escaping ()->Void = {}, disabled:Bool = true,allow:Bool = true) -> some View{ + + func IconButton(info:IconInfo,action:@escaping ()->Void = {},width:CGFloat = 20,height:CGFloat = 20, disabled:Bool = true,allow:Bool = true) -> some View{ Button(action:{ print("IconButton ") action() @@ -214,7 +270,8 @@ .frame(width: info.size.width, height: info.size.height) .aspectRatio(contentMode: .fit) .foregroundColor(Color.white) - }.frame(width: 30,height: 30) + }.frame(width: width,height: height) + .contentShape(Rectangle()) // 明确指定可点击区域 }.buttonStyle(TextBtnStyle()) //.allowsHitTesting(allow) // ✅ 完全禁用点击(不可交互,不触发,不高亮) //.disabled(disabled) // ✅ 禁用点击事件,但按钮可能变灰(系统默认行为) @@ -286,14 +343,35 @@ struct MiniWindow_BottomBtns_Previews: PreviewProvider{ @EnvironmentObject var miniData:MiniWindowData static var previews: some View { - var miniData = MiniWindowData(streamType: StreamType.CAMERA) + var miniData = MiniWindowData(streamType: StreamType.CAMERA); var miniDataBinding = Binding<MiniWindowData> { + miniData.hasAudio = false; return miniData } set: { MiniWindowData, Transaction in } - MiniWindow(miniData:miniData).environmentObject(miniData); + MiniWindow(miniData:miniData).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) + .background( + GeometryReader { geometry in + Color.clear + .onAppear { + miniData.mainSize = geometry.size; + + print("displaySize:\(miniData.mainSize)") + } + .onChange(of: geometry.size) { newSize in + miniData.mainSize = newSize; + print("displaySize:\(miniData.mainSize)") + } + }) + .border(Color.red) + .onDisappear { + print("onDisappear 视图消失了!") + + }.onAppear { + print("onAppear 视图出现了!") + }.environmentObject(miniData); } } -- Gitblit v1.9.1