You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RTSP视频播放代码迁移至独立Swift文件问题及AVCaptureSession咨询

Solutions to Your RTSP Player Migration & Capture Questions

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 update method marked with @objc to 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()
    }
}

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:31:51