SwiftUI - MJPEG 视频流不更新视图中的图像
SwiftUI - MJPEG Video stream not updating Image in View
给出以下 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()
}
}
基本上以下 class 将 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) -> (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
未使用从流中接收到的帧进行更新。我不确定这是我对 class 库或 @Published
或 @StateObject
属性的实现。
注意:我可以确认流通过网络浏览器工作,而且如果我调试 receivedImage
是来自流视频的实际帧。
您在 MJPEGStream
中观察到的 属性 stream
的值是指向 MJPEGStreamLib
对象的 指针 。
唯一一次 属性 更改,也是唯一一次您的 ObservableObject
会导致 MpegView
更新,是您第一次为指针赋值时 - 当MpegView
首先创建。之后,对象的 pointer 永远不会改变,即使它指向的对象正在快速生成图像。所以您的视图永远不会更新。
如果您希望 Swift 视图在 MJPEGStreamLib
对象中的图像发生变化时更新,那么您需要将 MJPEGStreamLib
设为 ObservableObject
并标记其 image
属性 作为 @Published
.
MJPEGStreamLib 包含静态图像变量,因此图像视图不会更新。您需要使用像@State 或@Published 这样的属性 包装器来绑定变量。仍然无法正常工作,请在此处发表评论。我来帮你
给出以下 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()
}
}
基本上以下 class 将 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) -> (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
未使用从流中接收到的帧进行更新。我不确定这是我对 class 库或 @Published
或 @StateObject
属性的实现。
注意:我可以确认流通过网络浏览器工作,而且如果我调试 receivedImage
是来自流视频的实际帧。
您在 MJPEGStream
中观察到的 属性 stream
的值是指向 MJPEGStreamLib
对象的 指针 。
唯一一次 属性 更改,也是唯一一次您的 ObservableObject
会导致 MpegView
更新,是您第一次为指针赋值时 - 当MpegView
首先创建。之后,对象的 pointer 永远不会改变,即使它指向的对象正在快速生成图像。所以您的视图永远不会更新。
如果您希望 Swift 视图在 MJPEGStreamLib
对象中的图像发生变化时更新,那么您需要将 MJPEGStreamLib
设为 ObservableObject
并标记其 image
属性 作为 @Published
.
MJPEGStreamLib 包含静态图像变量,因此图像视图不会更新。您需要使用像@State 或@Published 这样的属性 包装器来绑定变量。仍然无法正常工作,请在此处发表评论。我来帮你