简体   繁体   English

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

[英]SwiftUI - MJPEG Video stream not updating Image in View

Given the following StreamView() :鉴于以下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()
    }
}

I have the following MpegView() that implements ObservableObject :我有以下实现ObservableObject MpegView()

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()
    }
}

Basically the following class replaces an instance of var image = UIImage() with an updated image of the MJPEG stream:基本上,以下类用 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)
    }
}

Then in my main ContentView() I simply have:然后在我的主要ContentView()我只有:

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

The problem is that the Image in the MpegView() is not getting updated with the received frames from the stream.问题是MpegView()中的Image没有使用从流中接收到的帧进行更新。 I'm not sure if it's my implementation for the class library or the @Published or @StateObject properties.我不确定这是否是我对类库或@Published@StateObject属性的实现。

NOTE: I can confirm that the stream works via the web browser and also if I debug what the receivedImage is it's the actual frame from the streamed video.注意:我可以通过网络浏览器确认流是否正常工作,并且如果我调试receivedImage是来自流视频的实际帧。

The value of your observed property stream in MJPEGStream is a pointer to an MJPEGStreamLib object. MJPEGStream观察到的属性streamMJPEGStream指向MJPEGStreamLib对象的指针

The only time that property changes, and the only time your ObservableObject will cause the MpegView to be updated, is when you first assign a value to the pointer - when the MpegView is first created.属性更改的唯一时间,也是您的ObservableObject将导致MpegView更新的唯一时间,是您第一次为指针赋值时 - 首次创建MpegView时。 After that, the pointer to the object never changes, even if the object it points to is quickly generating images.之后,指向对象的指针永远不会改变,即使它指向的对象正在快速生成图像。 So your view never updates.所以你的视图永远不会更新。

If you want your Swift view to update whenever the image in your MJPEGStreamLib object changes, then you need to make MJPEGStreamLib the ObservableObject and mark its image property as @Published .如果您希望您的 Swift 视图在MJPEGStreamLib对象中的图像发生更改时更新,那么您需要将MJPEGStreamLib ObservableObject并将其image属性标记为@Published

MJPEGStreamLib contains static image variable so image view is not updating. MJPEGStreamLib 包含静态图像变量,因此图像视图不会更新。 You need to bind variables using a property wrapper like @State or @Published.您需要使用@State 或@Published 之类的属性包装器来绑定变量。 Still not work please comment here.仍然无法正常工作,请在此处发表评论。 I will help you我会帮你

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM