SwiftUI - MJPEG视频流未更新视图中的图像

0mkxixxg  于 2022-11-28  发布在  Swift
关注(0)|答案(2)|浏览(145)

给定以下StreamView()

struct StreamView: View {
    @StateObject var stream = MJPEGStream()

    var body: some View {
        MpegView(mjpegStream: self.stream)
            .background(.red)
            .frame(width: 200, height: 200)
    }
}

struct StreamView_Previews: PreviewProvider {
    static var previews: some View {
        StreamView()
    }
}

我有以下实现ObservableObjectMpegView()

class MJPEGStream: ObservableObject {
    @Published var stream = MJPEGStreamLib()
    
    init() {
        self.stream.play(url: URL(string: "http://192.168.1.120/mjpeg/1")!)
    }
}

struct MpegView: View {
    @ObservedObject var mjpegStream: MJPEGStream
    
    var body: some View {
        Image(uiImage: self.mjpegStream.stream.image)
            .resizable()
    }
}

基本上,以下类用MJPEG流的更新图像替换var image = UIImage()的示例:

class MJPEGStreamLib: NSObject, URLSessionDataDelegate {
    enum StreamStatus {
        case stop
        case loading
        case play
    }
    
    var receivedData: NSMutableData?
    var dataTask: URLSessionDataTask?
    var session: Foundation.URLSession!
    var status: StreamStatus = .stop
    
    var authenticationHandler: ((URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
    var didStartLoading: (() -> Void)?
    var didFinishLoading: (() -> Void)?

    var contentURL: URL?
    var image = UIImage()
    
    override init() {
        super.init()
        self.session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
    }
    
    convenience init(contentURL: URL) {
        self.init()
        self.contentURL = contentURL
        self.play()
    }
    
    deinit {
        self.dataTask?.cancel()
    }
    
    // Play function with url parameter
    func play(url: URL) {
        // Checking the status for it is already playing or not
        if self.status == .play || self.status == .loading {
            self.stop()
        }
        
        self.contentURL = url
        self.play()
    }
    
    // Play function without URL paremeter
    func play() {
        guard let url = self.contentURL, self.status == .stop else {
            return
        }
        
        self.status = .loading
        DispatchQueue.main.async {
            self.didStartLoading?()
        }
        
        self.receivedData = NSMutableData()
        
        let request = URLRequest(url: url)
        self.dataTask = self.session.dataTask(with: request)
        self.dataTask?.resume()
    }
    
    // Stop the stream function
    func stop() {
        self.status = .stop
        self.dataTask?.cancel()
    }
    
    // NSURLSessionDataDelegate
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        // Controlling the imageData is not nil
        if let imageData = self.receivedData, imageData.length > 0,
            let receivedImage = UIImage(data: imageData as Data) {
            if self.status == .loading {
                self.status = .play
                DispatchQueue.main.async {
                    self.didFinishLoading?()
                }
            }
            
            // Set the imageview as received stream
            DispatchQueue.main.async {
                self.image = receivedImage
            }
        }
        
        self.receivedData = NSMutableData()
        completionHandler(.allow)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        self.receivedData?.append(data)
    }
    
    // NSURLSessionTaskDelegate
    func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        var credential: URLCredential?
        var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
        
        // Getting the authentication if stream asks it
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            if let trust = challenge.protectionSpace.serverTrust {
                credential = URLCredential(trust: trust)
                disposition = .useCredential
            }
        } else if let onAuthentication = self.authenticationHandler {
            (disposition, credential) = onAuthentication(challenge)
        }
        
        completionHandler(disposition, credential)
    }
}

然后在我的主ContentView()中,我只需要:

struct ContentView: View {
    var body: some View {
        StreamView()
    }
}

问题是MpegView()中的Image没有用从流中接收到的帧进行更新。我不确定这是我的类库实现还是@Published@StateObject属性。
注:我可以确认流通过Web浏览器工作,如果我调试receivedImage是什么,它是来自流视频的实际帧。

u3r8eeie

u3r8eeie1#

MJPEGStream中观察到的属性stream的值是指向MJPEGStreamLib对象的指针
只有当你第一次给指针赋值的时候,也就是MpegView第一次创建的时候,这个属性才会改变,也只有当你的ObservableObject会导致MpegView更新的时候,在那之后,指向对象的指针就不会改变,即使它指向的对象正在快速生成图像,所以你的视图也不会更新。
如果你希望Swift视图在MJPEGStreamLib对象中的图像改变时更新,那么你需要将MJPEGStreamLib设为ObservableObject,并将其image属性标记为@Published

bpzcxfmw

bpzcxfmw2#

MJPEGStreamLib包含静态图像变量,因此图像视图未更新。您需要使用@State或@Published等属性 Package 绑定变量。仍不起作用,请在此处进行注解。我会帮助您

相关问题