#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. Все еще не работает, пожалуйста, прокомментируйте здесь. Я помогу тебе