Видеопоток SwiftUI — MJPEG не обновляет изображение в поле зрения

#ios #swift #swiftui #streaming #mjpeg

Вопрос:

Учитывая следующее 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()  } }  

У меня есть следующее MpegView() , что реализует ObservableObject :

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

В основном следующий класс заменяет экземпляр var image = UIImage() обновленным изображением потока MJPEG:

 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) -gt; (URLSession.AuthChallengeDisposition, URLCredential?))?  var didStartLoading: (() -gt; Void)?  var didFinishLoading: (() -gt; 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) -gt; Void) {  // Controlling the imageData is not nil  if let imageData = self.receivedData, imageData.length gt; 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?) -gt; 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()  } }  

Проблема в том, что Image в MpegView() не обновляется с полученными кадрами из потока. Я не уверен, является ли это моей реализацией для библиотеки классов @Published или @StateObject свойств или.

ПРИМЕЧАНИЕ: Я могу подтвердить, что поток работает через веб-браузер, а также, если я отлажу, что receivedImage это за фактический кадр из потокового видео.

Ответ №1:

Значение вашего наблюдаемого свойства stream in MJPEGStream является указателем на MJPEGStreamLib объект.

Единственный раз, когда это свойство изменяется, и единственный раз, когда вы ObservableObject заставите MpegView его обновляться, — это когда вы впервые присваиваете значение указателю-когда MpegView он впервые создается. После этого указатель на объект никогда не меняется, даже если объект, на который он указывает, быстро генерирует изображения. Так что ваш взгляд никогда не обновляется.

Если вы хотите, чтобы ваше представление Swift обновлялось всякий раз, когда изображение в вашем MJPEGStreamLib объекте изменяется, вам необходимо создать MJPEGStreamLib ObservableObject и пометить его image свойство как @Published .

Комментарии:

1. Потрясающе! Я кое-чему научился, и это прекрасно работает. Спасибо.

Ответ №2:

MJPEGStreamLib содержит статическую переменную изображения, поэтому представление изображения не обновляется. Вам нужно связать переменные с помощью оболочки свойств, такой как @State или @Published. Все еще не работает, пожалуйста, прокомментируйте здесь. Я помогу тебе