From 2b51baa1981fb445b938e64bdce539e58fe70264 Mon Sep 17 00:00:00 2001
From: Runt <qingingrunt2010@qq.com>
Date: Sat, 26 Jul 2025 13:17:14 +0000
Subject: [PATCH] 小窗缩放, 相机转小窗显示 小窗旋转,相机画面旋转 关闭按钮优化

---
 LiveProject/views/VideoRendererView.swift          |    6 +
 LiveProject/controller/CameraCapture.swift         |   19 +++
 LiveProject/enum/Icons.swift                       |   38 +++---
 LiveProject/activity/stream/LiveViewModel.swift    |   26 +++--
 LiveProject/data/IconInfo.swift                    |    2 
 LiveProject/activity/stream/LiveActivity.swift     |   47 +++++++--
 LiveProject/activity/stream/views/MiniWindow.swift |  128 +++++++++++++++++++++----
 LiveProject/tool/MetalRenderer.swift               |   25 ++++
 8 files changed, 223 insertions(+), 68 deletions(-)

diff --git a/LiveProject/activity/stream/LiveActivity.swift b/LiveProject/activity/stream/LiveActivity.swift
index 045e563..d8386f6 100644
--- a/LiveProject/activity/stream/LiveActivity.swift
+++ b/LiveProject/activity/stream/LiveActivity.swift
@@ -34,12 +34,19 @@
             Color.clear
                 .ignoresSafeArea() // 填满全屏
             ZStack{
-                VideoRendererView(pixelBuffer: $mViewModel.pixelBuffer).background(Color.black).frame(width: mainSize.width,height:mainSize.height)
+                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)
-                ForEach(miniWindows, id: \.id) { miniWindow in
-                    NewMiniWindow(miniWindow: miniWindow)
-                }
-            }.border(Color.blue)
+                
+            }.border(Color.blue).clipped()
+                .overlay(
+                    ForEach(miniWindows, id: \.id) { miniWindow in
+                        NewMiniWindow(miniData: miniWindow)
+                    }
+                )
             
             VStack{
                 Spacer()
@@ -64,6 +71,7 @@
                         }
                 })
             .border(Color.red)
+            .clipped()
             .onDisappear {
                 print("onDisappear 视图消失了!")
                 
@@ -157,13 +165,29 @@
                     
                     ForEach(devices, id: \.self) { device in
                         MButton(icon: device.icon,text: device.name){
-                            mViewModel.newWindowAction(device: device){ status in
+                            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(MiniWindowData(streamType: device.type))
+                                
+                                miniWindows.append(miniData);
                             }
-                            print("\(device.name) click \(self.miniWindows.count)")
+                            print("\(device.name) \(device.type) click \(self.miniWindows.count)")
                         }
                     }
                 }
@@ -228,12 +252,13 @@
         }
     }
     
-    func NewMiniWindow(miniWindow:MiniWindowData) -> some View{
-        MiniWindow(miniData: miniWindow)
+    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 == miniWindow.id }) else { return }
+                guard let index = miniWindows.firstIndex(where: { $0.id == miniData.id }) else { return }
                 miniWindows.remove(at: index)
+                mViewModel.closeWindowAction(miniData: miniData)
             }
     }
     
diff --git a/LiveProject/activity/stream/LiveViewModel.swift b/LiveProject/activity/stream/LiveViewModel.swift
index bfc3671..bb31219 100644
--- a/LiveProject/activity/stream/LiveViewModel.swift
+++ b/LiveProject/activity/stream/LiveViewModel.swift
@@ -10,15 +10,15 @@
 class LiveViewModel: ObservableObject {
     @Published var pixelBuffer: CVPixelBuffer?
     
-    let encoder = H264Encoder(width: 1080, height: 1920, fps: 30, bitrate: 1_000_000)
+    //let encoder = H264Encoder(width: 1080, height: 1920, fps: 30, bitrate: 1_000_000)
     var frameIndex: Int64 = 0
-    let encodeQueue = DispatchQueue(label: "encoder.queue")
+    //let encodeQueue = DispatchQueue(label: "encoder.queue")
 
     lazy var camera = CameraCapture()
     var timestamp = Int(Date().timeIntervalSince1970 * 1000)
     
-    func newWindowAction(device:DeviceInfo,completion: @escaping (Bool) -> Void = {b in}){
-        switch device.type{
+    func newWindowAction(minidata:MiniWindowData,completion: @escaping (Bool) -> Void = {b in}){
+        switch minidata.streamType{
         case StreamType.CAMERA:
             requestCameraPermission(mediaType: .video){ staus in
                 if(staus){
@@ -33,15 +33,18 @@
                             print("Invalid pixel buffer size: \(width)x\(height)")
                             return
                         }
-                        
+                        if(minidata.size.width != CGFloat(width) || minidata.size.height != CGFloat(height)){
+                            minidata.size = CGSize(width:width,height:height);
+                        }
                         self.frameIndex += 1
                         let ts =  Int(Date().timeIntervalSince1970 * 1000)
 
                         self.timestamp = ts;
                         let cmTime = CMTimeMake(value: Int64(CACurrentMediaTime() * 1000), timescale: 1000);
-                        self.encoder.encode(pixelBuffer: buffer, pts: cmTime)
+                        //self.encoder.encode(pixelBuffer: buffer, pts: cmTime)
                         DispatchQueue.main.async {
-                            self.pixelBuffer = buffer;
+                            minidata.pixelBuffer = buffer;
+                            
                         }
                         //print("画面更新")
                     }
@@ -49,12 +52,12 @@
                         self.camera.start()
                     }
                     print("启动相机")
-                    self.encoder.onEncoded = { (data: Data, ctime: CMTime, isKey: Bool) in
+                    /*self.encoder.onEncoded = { (data: Data, ctime: CMTime, isKey: Bool) in
                         let timestamp2 = Int(Date().timeIntervalSince1970 * 1000)
                         print("编码时间2 \(timestamp2 - self.timestamp)")
                         print("Encoded NALU size: \(data.count), key frame: \(isKey)")
 
-                    }
+                    }*/
                 }else{
                     
                 }
@@ -67,10 +70,11 @@
         }
     }
     
-    func closeWindowAction(device:DeviceInfo){
-        switch device.type{
+    func closeWindowAction(miniData:MiniWindowData){
+        switch miniData.streamType{
         case StreamType.CAMERA:
             print("关闭相机")
+            camera.stop();
             break;
         default:
             break;
diff --git a/LiveProject/activity/stream/views/MiniWindow.swift b/LiveProject/activity/stream/views/MiniWindow.swift
index 357e6a0..53fba14 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 size:CGSize = CGSize(width:16,height:9)
+    //视图比例(相对于 main 尺寸)
+    @Published var viewRate = 0.5
+    //位置
     @Published var position:CGSize = CGSize(width: 0, height: 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,33 +132,43 @@
                 .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)
+        }.background(Color.blue).frame(width:width,height:height)
             .offset(self.miniData.position)
-            .gesture(DragGesture(minimumDistance: 0)
+            .gesture(SimultaneousGesture(DragGesture(minimumDistance: 0)
                 .onChanged{ val in
                     let x = self.miniData.position.width
                     let y = self.miniData.position.height
                     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) ")
+                    //print(" onChanged \(index) \(self.miniData.position) ")
                     lastLocation = val.translation;
                 }.onEnded{ val in
                     let x = self.miniData.position.width
@@ -138,10 +176,36 @@
                     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) ")
+                    //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{
+                            var w = miniData.mainSize.width * l / 2
+                            self.miniData.position = CGSize(width: self.miniData.position.width - w , height: self.miniData.position.height - w)
+                        }
+                    }
+                    lastZoom = val.magnification;
+                }.onEnded{ val in
+                    var l = val.magnification - lastZoom;
+                    miniData.viewRate += l;
+                    if(miniData.viewRate < 0.4){
+                        miniData.viewRate = 0.4
+                    }else{
+                        var w = miniData.mainSize.width * l / 2
+                        self.miniData.position = CGSize(width: self.miniData.position.width - w , height: self.miniData.position.height - w)
+                    }
+                    lastZoom = 0;
+                }))
+            .onAppear{
+            
+                print("onAppear body width = \(width) , height = \(height)")
+            }
     }
     
     /**
@@ -179,7 +243,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
@@ -202,7 +266,7 @@
             .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) -> some View{
         Button(action:{
             print("IconButton ")
             action()
@@ -214,7 +278,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 +351,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);
     }
     
 }
diff --git a/LiveProject/controller/CameraCapture.swift b/LiveProject/controller/CameraCapture.swift
index 3345602..355b1e0 100644
--- a/LiveProject/controller/CameraCapture.swift
+++ b/LiveProject/controller/CameraCapture.swift
@@ -27,19 +27,28 @@
         if session.canAddInput(input) {
             session.addInput(input)
         }
-
+        
         let output = AVCaptureVideoDataOutput()
         output.videoSettings = [
             kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
         ]
         output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "camera.queue"))
-
         if session.canAddOutput(output) {
             session.addOutput(output)
         }
 
         self.videoOutput = output
-
+        // 在相机配置代码中
+        if let videoConnection = output.connection(with: .video) {
+            // 自动旋转(推荐)
+            videoConnection.automaticallyAdjustsVideoMirroring = false
+            videoConnection.videoOrientation = .portrait // 或根据UI方向设置
+            
+            // 对于前置摄像头启用镜像
+            /*if videoConnection.isVideoMirroringSupported {
+                videoConnection.isVideoMirrored = (cameraPosition == .front)
+            }*/
+        }
         session.commitConfiguration()
         session.startRunning()
         print("📷 相机已开启")
@@ -51,7 +60,9 @@
         guard let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
         let width = CVPixelBufferGetWidth(buffer)
         let height = CVPixelBufferGetHeight(buffer)
-        //print("📷 当前帧尺寸: \(width)x\(height)")
+        //print("Buffer Size: \(CVPixelBufferGetWidth(buffer))x\(CVPixelBufferGetHeight(buffer))")
+        //print("Connection orientation: \(connection.videoOrientation.rawValue)")
+        //print("Rotation angle: \(connection.videoRotationAngle)")
         onFrame?(buffer)
     }
     
diff --git a/LiveProject/data/IconInfo.swift b/LiveProject/data/IconInfo.swift
index 9a07bbd..a2f1f24 100644
--- a/LiveProject/data/IconInfo.swift
+++ b/LiveProject/data/IconInfo.swift
@@ -8,5 +8,5 @@
 
 struct IconInfo{
     var name:String
-    var size:CGSize = CGSize(width: 20, height: 20)
+    var size:CGSize = CGSize(width: 15, height: 20)
 }
diff --git a/LiveProject/enum/Icons.swift b/LiveProject/enum/Icons.swift
index 56b2ca6..386e318 100644
--- a/LiveProject/enum/Icons.swift
+++ b/LiveProject/enum/Icons.swift
@@ -2,28 +2,28 @@
 //  Icons.swift
 //  LiveProject
 //
-//  Created by 倪路朋 on 7/4/25.
+//  Created by 倪路朋 on 7/4/22.
 //
 import SwiftUI
 
 struct Icons{
-    static let CAMERA = IconInfo(name: "camera",size: CGSize(width: 25, height: 20))
-    static let MIC = IconInfo(name: "mic",size: CGSize(width: 15, height: 23))
-    static let MIC_MUTE = IconInfo(name: "mic.slash",size: CGSize(width: 18, height: 20))
-    static let PORTRAIT = IconInfo(name: "ipad",size: CGSize(width: 18, height: 23))
-    static let LANDSCAPE = IconInfo(name: "ipad.landscape",size: CGSize(width: 25, height: 20))
-    static let BACK = IconInfo(name: "arrow.left",size: CGSize(width: 25, height: 20))
-    static let CLOSE = IconInfo(name: "xmark",size: CGSize(width: 22, height: 22))
+    static let CAMERA = IconInfo(name: "camera",size: CGSize(width: 22, height: 17))
+    static let MIC = IconInfo(name: "mic",size: CGSize(width: 13, height: 20))
+    static let MIC_MUTE = IconInfo(name: "mic.slash",size: CGSize(width: 15, height: 17))
+    static let PORTRAIT = IconInfo(name: "ipad",size: CGSize(width: 15, height: 20))
+    static let LANDSCAPE = IconInfo(name: "ipad.landscape",size: CGSize(width: 22, height: 17))
+    static let BACK = IconInfo(name: "arrow.left",size: CGSize(width: 22, height: 17))
+    static let CLOSE = IconInfo(name: "xmark",size: CGSize(width: 19, height: 19))
     
-    static let SPEAKER = IconInfo(name: "speaker",size: CGSize(width: 18, height: 23))
-    static let SPEAKER_MUTE = IconInfo(name: "speaker.slash",size: CGSize(width: 18, height: 23))
-    static let IMAGE = IconInfo(name: "video",size: CGSize(width: 28, height: 19))
-    static let IMAGE_MUTE = IconInfo(name: "video.slash",size: CGSize(width: 25, height: 20))
-    static let ROTATE_LEFT = IconInfo(name: "rotate.left",size: CGSize(width: 23, height: 25))
-    static let ROTATE_RIGHT = IconInfo(name: "rotate.right",size: CGSize(width: 23, height: 25))
-    static let INFO = IconInfo(name: "info.circle",size: CGSize(width: 25, height: 25))
-    static let PAINT = IconInfo(name: "paintpalette",size: CGSize(width: 25, height: 25))
-    static let HOME = IconInfo(name: "house",size: CGSize(width: 25, height: 23))
-    static let ROTATE = IconInfo(name: "camera.rotate",size: CGSize(width: 25, height: 20))
-    static let MORE = IconInfo(name: "ellipsis",size: CGSize(width: 25, height: 5))
+    static let SPEAKER = IconInfo(name: "speaker",size: CGSize(width: 15, height: 20))
+    static let SPEAKER_MUTE = IconInfo(name: "speaker.slash",size: CGSize(width: 15, height: 20))
+    static let IMAGE = IconInfo(name: "video",size: CGSize(width: 23, height: 16))
+    static let IMAGE_MUTE = IconInfo(name: "video.slash",size: CGSize(width: 22, height: 17))
+    static let ROTATE_LEFT = IconInfo(name: "rotate.left",size: CGSize(width: 20, height: 22))
+    static let ROTATE_RIGHT = IconInfo(name: "rotate.right",size: CGSize(width: 20, height: 22))
+    static let INFO = IconInfo(name: "info.circle",size: CGSize(width: 22, height: 22))
+    static let PAINT = IconInfo(name: "paintpalette",size: CGSize(width: 22, height: 22))
+    static let HOME = IconInfo(name: "house",size: CGSize(width: 22, height: 20))
+    static let ROTATE = IconInfo(name: "camera.rotate",size: CGSize(width: 22, height: 17))
+    static let MORE = IconInfo(name: "ellipsis",size: CGSize(width: 18, height: 3))
 }
diff --git a/LiveProject/tool/MetalRenderer.swift b/LiveProject/tool/MetalRenderer.swift
index 61bd769..92b2fe4 100644
--- a/LiveProject/tool/MetalRenderer.swift
+++ b/LiveProject/tool/MetalRenderer.swift
@@ -16,6 +16,7 @@
 
     private var currentPixelBuffer: CVPixelBuffer?
     private let textureCache: CVMetalTextureCache
+    private var rotate:Int = 0;
 
     init(mtkView: MTKView) {
         guard let device = MTLCreateSystemDefaultDevice(),
@@ -63,7 +64,24 @@
         guard drawableSize.width > 0, drawableSize.height > 0 else { return }
 
         // 加方向修正:顺时针旋转90度
-        var ciImage = CIImage(cvPixelBuffer: pixelBuffer).oriented(.right)
+        var orien:CGImagePropertyOrientation = .up;
+        switch self.rotate{
+        case 0:
+            break;
+        case 90:
+            orien = .right;
+            break;
+        case 180:
+            orien = .down;
+            break;
+        case 270:
+            orien = .left;
+            break;
+        default:
+            orien = .up;
+        }
+        print(" roate = \(rotate)")
+        var ciImage = CIImage(cvPixelBuffer: pixelBuffer).oriented(orien)
 
         // 等比缩放后居中
         let sourceExtent = ciImage.extent
@@ -99,4 +117,9 @@
         //刷新
         
     }
+    
+    func updateRotate(angle:Int){
+        rotate = angle;
+    }
+    
 }
diff --git a/LiveProject/views/VideoRendererView.swift b/LiveProject/views/VideoRendererView.swift
index d157f06..79a63a1 100644
--- a/LiveProject/views/VideoRendererView.swift
+++ b/LiveProject/views/VideoRendererView.swift
@@ -9,6 +9,7 @@
 
 struct VideoRendererView: UIViewRepresentable {
     @Binding var pixelBuffer: CVPixelBuffer?
+    @Binding var rotate:Int?;
 
     //用 Coordinator 缓存实例
     func makeCoordinator() -> Coordinator {
@@ -24,7 +25,12 @@
             //print("updateUIView")
             context.coordinator.renderer.display(pixelBuffer: buffer)
         }
+        if let angle = rotate{
+            //print("updateUIView rotate \(angle)")
+            context.coordinator.renderer.updateRotate(angle: angle)
+        }
     }
+    
 
     class Coordinator {
         let mtkView: MTKView

--
Gitblit v1.9.1