RTSP视频播放代码迁移至独立Swift文件问题及AVCaptureSession咨询
Hey Brian, let's tackle each of your issues step by step:
1. Fixing Objective-C Runtime Errors When Migrating to a Separate Swift File
The error pops up because the selector-based Timer API relies on the Objective-C runtime, and your standalone Swift class might not be compatible with it. Here's how to fix it:
- Ensure your standalone class inherits from
NSObject(Objective-C can only interact with classes that have this inheritance). - Keep the
updatemethod marked with@objcto expose it to the Objective-C runtime.
For a more Swift-native approach, use the block-based Timer API—this avoids Objective-C runtime dependencies entirely.
2. Updating Timer Target & Selector After Migration
When you move your RTSP logic to a standalone class, adjust the Timer reference like this:
Option 1: Selector API (Objective-C Compatible)
If you want to stick with the selector approach, your standalone class would look like this:
import UIKit class RTSPPlayerManager: NSObject { weak var imageView: UIImageView? var video: RTSPPlayer! private var updateTimer: Timer? init(imageView: UIImageView, rtspURL: String) { self.imageView = imageView super.init() setupPlayer(rtspURL: rtspURL) } private func setupPlayer(rtspURL: String) { video = RTSPPlayer(video: rtspURL, usesTcp: false) video.outputWidth = Int32(UIScreen.main.bounds.width) video.outputHeight = Int32(UIScreen.main.bounds.height) video.seekTime(0.0) // Target is now the RTSPPlayerManager instance, selector points to its update method updateTimer = Timer.scheduledTimer( timeInterval: 1.0/30, target: self, selector: #selector(update(timer:)), userInfo: nil, repeats: true ) updateTimer?.fire() } @objc func update(timer: Timer) { guard let imageView = imageView else { timer.invalidate() video.closeAudio() return } if !video.stepFrame() { timer.invalidate() video.closeAudio() } imageView.image = video.currentImage } // Cleanup to avoid memory leaks func stop() { updateTimer?.invalidate() video.closeAudio() } }
Then initialize it in your ViewController:
class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! private var playerManager: RTSPPlayerManager! override func viewDidLoad() { super.viewDidLoad() playerManager = RTSPPlayerManager( imageView: imageView, rtspURL: "rtsp://admin:admin@192.168.150.243" ) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) playerManager.stop() } }
Option 2: Block-Based Timer (Recommended for Swift)
This skips selector/Objective-C issues entirely. Replace the Timer setup in setupPlayer with:
updateTimer = Timer.scheduledTimer(withTimeInterval: 1.0/30, repeats: true) { [weak self] timer in guard let self = self, let imageView = self.imageView else { timer.invalidate() self?.video.closeAudio() return } if !self.video.stepFrame() { timer.invalidate() self.video.closeAudio() } imageView.image = self.video.currentImage }
The [weak self] prevents retain cycles—critical for avoiding memory leaks!
3. Getting UIImage from AVCaptureSession/PreviewLayer
For AVCaptureSession (Recording Workflow)
To capture a still image from your session, use AVCapturePhotoOutput:
import AVFoundation class CaptureManager: NSObject, AVCapturePhotoCaptureDelegate { private let captureSession = AVCaptureSession() private let photoOutput = AVCapturePhotoOutput() private var completion: ((UIImage?) -> Void)? func setupSession() { // Configure your session with RTSP input (add the stream as a capture input first) captureSession.sessionPreset = .high if captureSession.canAddOutput(photoOutput) { captureSession.addOutput(photoOutput) } captureSession.startRunning() } func captureStillImage(completion: @escaping (UIImage?) -> Void) { self.completion = completion let photoSettings = AVCapturePhotoSettings() photoOutput.capturePhoto(with: photoSettings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("Capture error: \(error.localizedDescription)") completion?(nil) return } if let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) { completion?(image) } else { completion?(nil) } } }
Grabbing PreviewLayer's Current Frame
If you're using AVCaptureVideoPreviewLayer, render its contents to a UIImage directly:
func imageFromPreviewLayer(_ previewLayer: AVCaptureVideoPreviewLayer) -> UIImage? { UIGraphicsBeginImageContextWithOptions(previewLayer.bounds.size, false, UIScreen.main.scale) previewLayer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image }
内容的提问来源于stack exchange,提问作者Brian Hamilton

