import UIKit import AVFoundation /** * 带内边距的标签 */ class PaddingLabel: UILabel { var padding: UIEdgeInsets init(padding: UIEdgeInsets) { self.padding = padding super.init(frame: .zero) } required init?(coder: NSCoder) { self.padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) super.init(coder: coder) } override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: padding)) } override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize(width: size.width + padding.left + padding.right, height: size.height + padding.top + padding.bottom) } } /** * 对话画中画视图 * * 用于在画中画模式下显示用户和AI的对话内容 */ @available(iOS 13.0, *) class DialogPipView: UIView { // 视频播放器图层(必须有,否则画中画无法工作) private var playerLayer: AVPlayerLayer? private var player: AVPlayer? // 对话显示视图 private let dialogView = UIView() // 用户消息标签 private let userMessageLabel: PaddingLabel = { let label = PaddingLabel(padding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)) label.font = UIFont.systemFont(ofSize: 14) label.textColor = UIColor.white label.numberOfLines = 0 label.backgroundColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.7) label.layer.cornerRadius = 8 label.layer.masksToBounds = true label.textAlignment = .left label.isHidden = true return label }() // AI助手消息标签 private let assistantMessageLabel: PaddingLabel = { let label = PaddingLabel(padding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)) label.font = UIFont.systemFont(ofSize: 14) label.textColor = UIColor.white label.numberOfLines = 0 label.backgroundColor = UIColor(red: 0.0, green: 0.4, blue: 0.8, alpha: 0.7) label.layer.cornerRadius = 8 label.layer.masksToBounds = true label.textAlignment = .left label.isHidden = true return label }() // 初始化 override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { super.init(coder: coder) setupView() } // 设置视图 private func setupView() { backgroundColor = UIColor.black // 添加对话视图 addSubview(dialogView) dialogView.frame = bounds dialogView.autoresizingMask = [.flexibleWidth, .flexibleHeight] // 添加用户消息标签 dialogView.addSubview(userMessageLabel) userMessageLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ userMessageLabel.leadingAnchor.constraint(equalTo: dialogView.leadingAnchor, constant: 8), userMessageLabel.trailingAnchor.constraint(equalTo: dialogView.trailingAnchor, constant: -8), userMessageLabel.topAnchor.constraint(equalTo: dialogView.topAnchor, constant: 10), ]) // 添加AI助手消息标签 dialogView.addSubview(assistantMessageLabel) assistantMessageLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ assistantMessageLabel.leadingAnchor.constraint(equalTo: dialogView.leadingAnchor, constant: 8), assistantMessageLabel.trailingAnchor.constraint(equalTo: dialogView.trailingAnchor, constant: -8), assistantMessageLabel.topAnchor.constraint(equalTo: userMessageLabel.bottomAnchor, constant: 10), assistantMessageLabel.bottomAnchor.constraint(lessThanOrEqualTo: dialogView.bottomAnchor, constant: -10) ]) } // 创建并返回视频图层 func setupDialogLayer() -> AVPlayerLayer { // 创建无声视频,用于支持画中画功能 let playerItem = createSilentVideoPlayerItem() player = AVPlayer(playerItem: playerItem) player?.actionAtItemEnd = .none player?.play() // 创建视频图层 let layer = AVPlayerLayer(player: player) layer.frame = bounds layer.videoGravity = .resizeAspectFill // 将视频图层放在最底层 self.layer.insertSublayer(layer, at: 0) playerLayer = layer // 注册通知,确保视频循环播放 NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem) return layer } // 创建无声视频用于支持画中画 private func createSilentVideoPlayerItem() -> AVPlayerItem { // 创建一个简单的黑色视频,足够支持画中画但不会占用太多资源 let composition = AVMutableComposition() // 添加视频轨道 let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) // 创建一个简单的黑色视频,持续10秒 let videoSize = CGSize(width: 640, height: 480) let duration = CMTime(seconds: 10, preferredTimescale: 600) // 创建一个黑色图像 UIGraphicsBeginImageContextWithOptions(videoSize, true, 0) UIColor.black.setFill() UIRectFill(CGRect(origin: .zero, size: videoSize)) let blackImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // 创建黑色图像的视频 let settings = [ AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: videoSize.width, AVVideoHeightKey: videoSize.height ] as [String : Any] let writerInput = AVAssetWriterInput(mediaType: .video, outputSettings: settings) let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: nil) if let pixelBuffer = createPixelBuffer(fromImage: blackImage!, size: videoSize) { let attributes: [String: Any] = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32ARGB, kCVPixelBufferWidthKey as String: videoSize.width, kCVPixelBufferHeightKey as String: videoSize.height ] // 创建一个5秒的黑色视频 let assetWriter = try! AVAssetWriter(outputURL: URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("temp.mov"), fileType: .mov) assetWriter.add(writerInput) assetWriter.startWriting() assetWriter.startSession(atSourceTime: .zero) let frameCount = 50 // 5秒,10fps let frameDuration = CMTime(value: 1, timescale: 10) writerInput.requestMediaDataWhenReady(on: DispatchQueue.global()) { for i in 0.. CVPixelBuffer? { var pixelBuffer: CVPixelBuffer? let attrs = [ kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue ] as CFDictionary let status = CVPixelBufferCreate( kCFAllocatorDefault, Int(size.width), Int(size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer ) guard status == kCVReturnSuccess, let buffer = pixelBuffer else { return nil } CVPixelBufferLockBaseAddress(buffer, []) let context = CGContext( data: CVPixelBufferGetBaseAddress(buffer), width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue ) context?.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) CVPixelBufferUnlockBaseAddress(buffer, []) return buffer } // 视频播放结束处理 @objc private func playerItemDidReachEnd(notification: Notification) { // 循环播放视频 player?.seek(to: .zero) player?.play() } // 更新对话内容 func updateDialogContent(userMessage: String, assistantMessage: String) { // 设置用户消息 if !userMessage.isEmpty { userMessageLabel.text = "用户: \(userMessage)" userMessageLabel.isHidden = false } else { userMessageLabel.isHidden = true } // 设置AI助手消息 if !assistantMessage.isEmpty { assistantMessageLabel.text = "AI助手: \(assistantMessage)" assistantMessageLabel.isHidden = false } else { assistantMessageLabel.isHidden = true } // 更新布局 setNeedsLayout() } // 布局子视图 override func layoutSubviews() { super.layoutSubviews() // 调整视频图层大小 playerLayer?.frame = bounds } // 释放资源 deinit { NotificationCenter.default.removeObserver(self) player?.pause() player = nil } }