From bf9e4680eb466bcb7c9cb1bef567252bb1f2bb7d Mon Sep 17 00:00:00 2001 From: Runt <qingingrunt2010@qq.com> Date: Fri, 27 Jun 2025 05:32:55 +0000 Subject: [PATCH] 1 --- LiveProject/views/VideoRendererView.swift | 58 +---- LiveProject/styles/TextBtnStyle.swift | 2 LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 15 + LiveProject.xcodeproj/project.pbxproj | 28 ++ LiveProject/activity/stream/LiveActivity.swift | 128 +++++++++++- LiveProject/enum/StreamType.swift | 23 ++ LiveProject/tool/MetalRenderer.swift | 78 +----- LiveProject/data/DeviceInfo.swift | 21 ++ LiveProject/views/FlowLayout.swift | 193 +++++++++++++++++++ LiveProject/views/LButton.swift | 1 LiveProject/views/MButton.swift | 1 LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist | 6 LiveProject/shape/IconBack.swift | 30 ++ LiveProject/views/TitleBarView.swift | 9 14 files changed, 466 insertions(+), 127 deletions(-) diff --git a/LiveProject.xcodeproj/project.pbxproj b/LiveProject.xcodeproj/project.pbxproj index a56e128..f659319 100644 --- a/LiveProject.xcodeproj/project.pbxproj +++ b/LiveProject.xcodeproj/project.pbxproj @@ -6,6 +6,10 @@ objectVersion = 77; objects = { +/* Begin PBXBuildFile section */ + A1615DB82E0D9A6E00D73CE3 /* WrappingHStack in Frameworks */ = {isa = PBXBuildFile; productRef = A1615DB72E0D9A6E00D73CE3 /* WrappingHStack */; }; +/* End PBXBuildFile section */ + /* Begin PBXContainerItemProxy section */ A1615D372E0C27F700D73CE3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -42,6 +46,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A1615DB82E0D9A6E00D73CE3 /* WrappingHStack in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -100,6 +105,7 @@ ); name = LiveProject; packageProductDependencies = ( + A1615DB72E0D9A6E00D73CE3 /* WrappingHStack */, ); productName = LiveProject; productReference = A1615D292E0C27F500D73CE3 /* LiveProject.app */; @@ -177,6 +183,9 @@ ); mainGroup = A1615D202E0C27F500D73CE3; minimizedProjectReferenceProxies = 1; + packageReferences = ( + A1615DB62E0D996500D73CE3 /* XCRemoteSwiftPackageReference "WrappingHStack" */, + ); preferredProjectObjectVersion = 77; productRefGroup = A1615D2A2E0C27F500D73CE3 /* Products */; projectDirPath = ""; @@ -532,6 +541,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + A1615DB62E0D996500D73CE3 /* XCRemoteSwiftPackageReference "WrappingHStack" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/dkk/WrappingHStack"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.2.11; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + A1615DB72E0D9A6E00D73CE3 /* WrappingHStack */ = { + isa = XCSwiftPackageProductDependency; + package = A1615DB62E0D996500D73CE3 /* XCRemoteSwiftPackageReference "WrappingHStack" */; + productName = WrappingHStack; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = A1615D212E0C27F500D73CE3 /* Project object */; } diff --git a/LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..5ac2f71 --- /dev/null +++ b/LiveProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "50578bb08258a739890c5d3d0a495ba4213b27b4dbbbc844c23e93aa2d80550b", + "pins" : [ + { + "identity" : "wrappinghstack", + "kind" : "remoteSourceControl", + "location" : "https://github.com/dkk/WrappingHStack", + "state" : { + "revision" : "425d9488ba55f58f0b34498c64c054c77fc2a44b", + "version" : "2.2.11" + } + } + ], + "version" : 3 +} diff --git a/LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..6bd2c4d --- /dev/null +++ b/LiveProject.xcodeproj/xcuserdata/nilupeng.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Bucket + uuid = "B6E33E15-95B1-4036-89E0-5C9B0FA33668" + type = "1" + version = "2.0"> +</Bucket> diff --git a/LiveProject/activity/stream/LiveActivity.swift b/LiveProject/activity/stream/LiveActivity.swift index d6faae2..8f16308 100644 --- a/LiveProject/activity/stream/LiveActivity.swift +++ b/LiveProject/activity/stream/LiveActivity.swift @@ -7,21 +7,121 @@ import Foundation import SwiftUI +import WrappingHStack struct LiveActivity: View { + @State private var pixelBuffer: CVPixelBuffer? + + + @State private var showDeviceDialog = false + + @State private var streamRate = Float(9/16.0); + @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 : "系统")] + var body: some View { - NavigationView{ - ZStack{ + ZStack{ + Color.clear + .ignoresSafeArea() // 填满全屏 + VStack{ + VideoRendererView(renderer: MetalRenderer()).background(Color.black).frame(width: mainSize.width,height:mainSize.height) + Spacer() + }.border(Color.blue) + VStack{ + Spacer() + BottomBtns().frame(alignment: .bottom).border(Color.green) + } + if showDeviceDialog { + DialogDevices() + } + }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) + .background( + GeometryReader { geometry in + Color.clear + .onAppear { + updateWindowSize(width: Int(geometry.size.width), height: Int(geometry.size.height)) + } + .onChange(of: geometry.size) { newSize in + updateWindowSize(width: Int(newSize.width), height: Int(newSize.height)) + } + }) + .border(Color.red) + .onDisappear { + print("onDisappear 视图消失了!") + }.onAppear { + print("onAppear 视图出现了!") + } + } + + func updateWindowSize(width:Int,height:Int){ + var rate : Float = Float(width)/Float(height); + if(rate != streamRate) { + var mainWidth = 0; + var mainHeight = 0; + if(rate < streamRate){ + mainWidth = width; + if(9.0/16 == streamRate){ + mainHeight = (mainWidth / 9 * 16); + }else{ + mainHeight = (mainWidth / 16 * 9); + } + }else{ + mainHeight = height; + if(9.0 / 16 == streamRate){ + mainWidth = (mainHeight / 16 * 9); + }else{ + mainWidth = (mainHeight / 9 * 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); } } + //Log.w(TAG , "onSizeChanged: ${mainWindowSize.value}" , ) } -} - -struct Bottombtns:View { - var body: some View { + + func DialogDevices() -> some View{ + ZStack{ + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + .onTapGesture { + withAnimation { + showDeviceDialog = false + } + } + VStack { + Spacer() + VStack(spacing: 20) { + Spacer().frame(height:40) + FlowLayout(devices){ device in + MButton(text:device.name){ + + } + } + .padding() + .animation(.default, value: devices) + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.white) + .cornerRadius(20) + .transition(.move(edge: .bottom)) + } + .zIndex(1) + } + } + + func BottomBtns() -> some View{ VStack{ - + HStack(){ MButton(icon: IconPortrait()){ @@ -35,14 +135,17 @@ } HStack{ LButton(text: "设备"){ - + print("Click 设备 button") + withAnimation{ + showDeviceDialog.toggle() + } } LButton(text: "RTMP"){ } - LButton(text: "文件"){ + /*flLButton(text: "文件"){ - } + }*/ LButton(text: "文本"){ } @@ -56,9 +159,10 @@ } } -struct LiveActivity_Previews: PreviewProvider{ + +struct LiveActivity_BottomBtns_Previews: PreviewProvider{ static var previews: some View { - Bottombtns(); + LiveActivity(); } } diff --git a/LiveProject/data/DeviceInfo.swift b/LiveProject/data/DeviceInfo.swift index e69de29..e3313f4 100644 --- a/LiveProject/data/DeviceInfo.swift +++ b/LiveProject/data/DeviceInfo.swift @@ -0,0 +1,21 @@ +// +// DeviceInfo.swift +// LiveProject +// +// Created by 倪路朋 on 6/26/25. +// + + +struct DeviceInfo : Hashable { + let name:String + let type:StreamType + let deviceId:String; + + func hash(into hasher: inout Hasher){ + hasher.combine(deviceId) + } + + static func == (lhs: DeviceInfo, rhs: DeviceInfo) -> Bool { + lhs.deviceId == rhs.deviceId + } +} diff --git a/LiveProject/enum/StreamType.swift b/LiveProject/enum/StreamType.swift index e69de29..c2c1254 100644 --- a/LiveProject/enum/StreamType.swift +++ b/LiveProject/enum/StreamType.swift @@ -0,0 +1,23 @@ +// +// StreamType.swift +// LiveProject +// +// Created by 倪路朋 on 6/26/25. +// + + +enum StreamType{ + case + //系统 + SYSTEM(val:Int = 0), + //本地类型 + FILE(val:Int = 1), + //图像 + CAMERA(val:Int = 100),VIDEO(val:Int = 102),PICTURE(val:Int = 104),TEXT(val:Int = 106), + //音频 + MICROPHONE(val:Int = 101),AUDIO(val:Int = 103), + //外置设备 + UVC(val:Int = 301),UAC(val:Int = 302), + //远程 + RTMP(val:Int = 200) +} diff --git a/LiveProject/shape/IconBack.swift b/LiveProject/shape/IconBack.swift index 0856c3b..5d760f8 100644 --- a/LiveProject/shape/IconBack.swift +++ b/LiveProject/shape/IconBack.swift @@ -5,4 +5,32 @@ // Created by 倪路朋 on 6/26/25. // -import Foundation +import SwiftUI + +struct IconBack: Shape { + + func path(in rect: CGRect) -> Path { + var path = Path() + + let arrowHeadLength = rect.width * 0.4 + let shaftY = rect.midY + let headHeight = rect.height * 0.5 + + // 箭头头部(三角形) + path.move(to: CGPoint(x: arrowHeadLength, y: rect.minY)) + path.addLine(to: CGPoint(x: 0, y: shaftY)) + path.addLine(to: CGPoint(x: arrowHeadLength, y: rect.maxY)) + + // 箭身(横线) + path.move(to: CGPoint(x: 0, y: shaftY)) + path.addLine(to: CGPoint(x: rect.maxX, y: shaftY)) + return path + } +} + +struct IconBack_Previews : PreviewProvider{ + static var previews: some View { + IconBack() + .stroke(Color.primary, lineWidth: 3).frame(width: 30,height: 25) + } +} diff --git a/LiveProject/styles/TextBtnStyle.swift b/LiveProject/styles/TextBtnStyle.swift index 7c22fda..85e0369 100644 --- a/LiveProject/styles/TextBtnStyle.swift +++ b/LiveProject/styles/TextBtnStyle.swift @@ -11,5 +11,7 @@ struct TextBtnStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label + .scaleEffect(configuration.isPressed ? 0.95 : 1.0) + .opacity(configuration.isPressed ? 0.8 : 1.0) } } diff --git a/LiveProject/tool/MetalRenderer.swift b/LiveProject/tool/MetalRenderer.swift index 3e88871..791dfc8 100644 --- a/LiveProject/tool/MetalRenderer.swift +++ b/LiveProject/tool/MetalRenderer.swift @@ -4,75 +4,35 @@ // 渲染工具 // Created by 倪路朋 on 6/26/25. // +import CoreVideo import Metal import MetalKit -class Renderer: NSObject, MTKViewDelegate { - static var shared: Renderer? +class MetalRenderer: NSObject, MTKViewDelegate { + var device: MTLDevice! + var commandQueue: MTLCommandQueue! - private var device: MTLDevice! - private var commandQueue: MTLCommandQueue! - private var pipelineState: MTLRenderPipelineState! - private var texture: MTLTexture? - private var textureDescriptor: MTLTextureDescriptor! - private var currentData: [UInt8]? - private var textureWidth = 0 - private var textureHeight = 0 - - weak var mtkView: MTKView? { - didSet { - guard let view = mtkView else { return } - device = view.device - commandQueue = device.makeCommandQueue() - - let library = device.makeDefaultLibrary() - let pipelineDesc = MTLRenderPipelineDescriptor() - pipelineDesc.vertexFunction = library?.makeFunction(name: "vertexShader") - pipelineDesc.fragmentFunction = library?.makeFunction(name: "fragmentShader") - pipelineDesc.colorAttachments[0].pixelFormat = view.colorPixelFormat - - pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDesc) - - Renderer.shared = self - } + func setup(view: MTKView) { + self.device = view.device + self.commandQueue = device.makeCommandQueue() + // 初始化 texture、pipeline 等 } - func updateFrame(data: [UInt8], width: Int, height: Int) { - currentData = data - textureWidth = width - textureHeight = height + // ✅ 必须实现的方法 1:窗口大小改变时调用 + func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { + // 可以留空或更新视图缩放、渲染区域等 } + // ✅ 必须实现的方法 2:每一帧绘制时调用 func draw(in view: MTKView) { guard let drawable = view.currentDrawable, - let descriptor = view.currentRenderPassDescriptor, - let data = currentData else { return } + let descriptor = view.currentRenderPassDescriptor else { return } - if texture == nil || texture?.width != textureWidth || texture?.height != textureHeight { - textureDescriptor = MTLTextureDescriptor.texture2DDescriptor( - pixelFormat: .rgba8Unorm, - width: textureWidth, - height: textureHeight, - mipmapped: false - ) - textureDescriptor.usage = [.shaderRead, .shaderWrite] - texture = device.makeTexture(descriptor: textureDescriptor) - } - - let region = MTLRegionMake2D(0, 0, textureWidth, textureHeight) - data.withUnsafeBytes { ptr in - texture?.replace(region: region, mipmapLevel: 0, withBytes: ptr.baseAddress!, bytesPerRow: textureWidth * 4) - } - - let commandBuffer = commandQueue.makeCommandBuffer() - let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: descriptor) - encoder?.setRenderPipelineState(pipelineState) - encoder?.setFragmentTexture(texture, index: 0) - encoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) - encoder?.endEncoding() - commandBuffer?.present(drawable) - commandBuffer?.commit() + let commandBuffer = commandQueue.makeCommandBuffer()! + let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)! + // 渲染逻辑,如绘制纹理 + encoder.endEncoding() + commandBuffer.present(drawable) + commandBuffer.commit() } - - func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} } diff --git a/LiveProject/views/FlowLayout.swift b/LiveProject/views/FlowLayout.swift index f3ee86a..151cd68 100644 --- a/LiveProject/views/FlowLayout.swift +++ b/LiveProject/views/FlowLayout.swift @@ -4,4 +4,197 @@ // // Created by 倪路朋 on 6/26/25. // +import SwiftUI +import SwiftUI +/// 完全自定义的自动换行布局容器 +struct FlowLayout<Data: RandomAccessCollection, Content: View>: View where Data.Element: Hashable { + // MARK: - 属性 + + /// 要显示的数据集合 + let data: Data + /// 水平间距 + let horizontalSpacing: CGFloat + /// 垂直间距 + let verticalSpacing: CGFloat + /// 对齐方式 + let alignment: HorizontalAlignment + /// 内容构建闭包 + let content: (Data.Element) -> Content + /// 总高度状态 + @State private var totalHeight: CGFloat = 0 + + // MARK: - 初始化 + + /// 初始化FlowLayout + /// - Parameters: + /// - data: 要显示的数据集合 + /// - horizontalSpacing: 水平间距,默认为8 + /// - verticalSpacing: 垂直间距,默认为8 + /// - alignment: 对齐方式,默认为.leading + /// - content: 内容构建闭包 + init( + _ data: Data, + horizontalSpacing: CGFloat = 8, + verticalSpacing: CGFloat = 8, + alignment: HorizontalAlignment = .leading, + @ViewBuilder content: @escaping (Data.Element) -> Content + ) { + self.data = data + self.horizontalSpacing = horizontalSpacing + self.verticalSpacing = verticalSpacing + self.alignment = alignment + self.content = content + } + + // MARK: - 主体视图 + + var body: some View { + GeometryReader { geometry in + self.contentView(in: geometry) + .background( + HeightReader(height: $totalHeight) + ) + } + .frame(height: totalHeight) + } + + // MARK: - 私有方法 + + /// 构建内容视图 + private func contentView(in geometry: GeometryProxy) -> some View { + var width: CGFloat = 0 + var height: CGFloat = 0 + var lastHeight: CGFloat = 0 + + return ZStack(alignment: Alignment(horizontal: alignment, vertical: .top)) { + ForEach(data.map { $0 }, id: \.self) { item in + content(item) + .padding(.trailing, horizontalSpacing) + .padding(.bottom, verticalSpacing) + .alignmentGuide(.leading) { dimensions in + // 检查是否需要换行 + if abs(width - dimensions.width) > geometry.size.width { + width = 0 + height += lastHeight + verticalSpacing + } + + let result = width + + // 更新宽度计算 + if item == data.last { + width = 0 // 重置为0,最后一项 + } else { + width -= dimensions.width + horizontalSpacing + } + + // 记录当前行高度 + lastHeight = dimensions.height + return result + } + .alignmentGuide(.top) { dimensions in + let result = height + + // 如果是最后一项,更新总高度 + if item == data.last { + height += lastHeight + verticalSpacing + } + + return result + } + } + } + } +} + +// MARK: - 高度读取器 + +/// 用于读取视图高度的辅助视图 +private struct HeightReader: View { + @Binding var height: CGFloat + + var body: some View { + GeometryReader { geometry in + Color.clear + .preference( + key: HeightPreferenceKey.self, + value: geometry.size.height + ) + } + .onPreferenceChange(HeightPreferenceKey.self) { newHeight in + DispatchQueue.main.async { + self.height = newHeight + } + } + } +} + +// MARK: - 高度偏好键 + +/// 用于传递高度值的PreferenceKey +private struct HeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + +// MARK: - 使用示例 + +struct FlowLayoutExample: View { + let tags = [ + "Swift", "SwiftUI", "UIKit", "Combine", "Core Data", + "Xcode", "Interface Builder", "Core Animation", "ARKit", + "Metal", "Core ML", "Vision", "MapKit", "CloudKit" + ] + + @State private var newTag = "" + @State private var customTags = ["自定义标签1", "自定义标签2"] + + var body: some View { + VStack { + FlowLayout(customTags + tags, horizontalSpacing: 10, verticalSpacing: 10) { tag in + MButton(text:tag){ + + } + } + .padding() + .animation(.default, value: customTags) + } + } + + private func addTag() { + withAnimation { + customTags.append(newTag) + newTag = "" + } + } +} + +/// 标签视图 +struct TagView: View { + let text: String + + var body: some View { + Text(text) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(Color.blue.opacity(0.2)) + .foregroundColor(.blue) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.blue, lineWidth: 1) + ) + .fixedSize(horizontal: true, vertical: false) // 确保文本不被截断 + } +} + +// MARK: - 预览 + +struct FlowLayout_Previews: PreviewProvider { + static var previews: some View { + FlowLayoutExample() + } +} diff --git a/LiveProject/views/LButton.swift b/LiveProject/views/LButton.swift index 932ff21..6f430e2 100644 --- a/LiveProject/views/LButton.swift +++ b/LiveProject/views/LButton.swift @@ -21,7 +21,6 @@ } var body: some View { - //登录按钮 Button(action: { if(valid == .INVALID){ return diff --git a/LiveProject/views/MButton.swift b/LiveProject/views/MButton.swift index 06da285..ce8b915 100644 --- a/LiveProject/views/MButton.swift +++ b/LiveProject/views/MButton.swift @@ -21,7 +21,6 @@ } var body: some View { - //登录按钮 Button(action: { if(valid == .INVALID){ return diff --git a/LiveProject/views/TitleBarView.swift b/LiveProject/views/TitleBarView.swift index a58fd40..7cfce53 100644 --- a/LiveProject/views/TitleBarView.swift +++ b/LiveProject/views/TitleBarView.swift @@ -12,9 +12,9 @@ @Environment(\.presentationMode) var presentationMode var title = "" - var imgLeft = "IconBack"; + var iconBack = IconBack(); var imgRight = ""; - var titleColor = "ColorText" + var titleColor = Color.colorText var body: some View { @@ -24,11 +24,10 @@ print("Click back button") self.presentationMode.wrappedValue.dismiss() }) { - Image(imgLeft) - .frame(width: 28,height: 28) + iconBack.stroke(Color.primary, lineWidth: 2.5).frame(width: 18,height: 14) } Spacer() - Text(title).foregroundColor(Color.init(titleColor)) + Text(title).foregroundColor(titleColor) .bold().font(Font.system(size: 16)) Spacer() Image(imgRight).frame(width: 28,height: 28) diff --git a/LiveProject/views/VideoRendererView.swift b/LiveProject/views/VideoRendererView.swift index 960aa78..f3b0341 100644 --- a/LiveProject/views/VideoRendererView.swift +++ b/LiveProject/views/VideoRendererView.swift @@ -7,57 +7,19 @@ import SwiftUI import MetalKit -struct MetalVideoRenderView: UIViewRepresentable { - // 统一接口,接收各种视频源的帧数据 - var frameData: VideoFrameData? - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - +struct VideoRendererView: UIViewRepresentable { + let renderer: MetalRenderer // 自定义 Metal 渲染器,支持传入 RGBA/YUV 数据帧 + func makeUIView(context: Context) -> MTKView { let mtkView = MTKView() - mtkView.device = context.coordinator.device - mtkView.delegate = context.coordinator + mtkView.device = MTLCreateSystemDefaultDevice() mtkView.framebufferOnly = false - mtkView.colorPixelFormat = .bgra8Unorm - mtkView.autoResizeDrawable = true + mtkView.enableSetNeedsDisplay = false + mtkView.isPaused = true + mtkView.delegate = renderer + renderer.setup(view: mtkView) return mtkView } - - func updateUIView(_ uiView: MTKView, context: Context) { - // 更新帧数据 - context.coordinator.updateFrame(frameData) - } - - class Coordinator: NSObject, MTKViewDelegate { - var parent: MetalVideoRenderView - var device: MTLDevice - var commandQueue: MTLCommandQueue - var pipelineState: MTLRenderPipelineState - var textureCache: CVMetalTextureCache? - - // YUV转RGB的着色器 - var yuvConversionPipeline: MTLComputePipelineState? - - init(_ parent: MetalVideoRenderView) { - self.parent = parent - self.device = MTLCreateSystemDefaultDevice()! - self.commandQueue = device.makeCommandQueue()! - - // 初始化Metal资源 - // (这里需要设置渲染管线和纹理缓存) - } - - func updateFrame(_ frameData: VideoFrameData?) { - // 根据不同的帧数据格式(YUV/RGBA)进行处理 - // 将数据转换为Metal纹理 - } - - func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} - - func draw(in view: MTKView) { - // 执行Metal绘制命令 - } - } + + func updateUIView(_ uiView: MTKView, context: Context) {} } -- Gitblit v1.9.1