I'm trying to observe an audio element on a webpage. How do I best keep it's "JS" data in sync with SwiftUI?
Here's the start of my view:
the body:
and a the webViewTimer method:
As you can see, I'm using Timer, but surely there must be a better way no?
Here's the start of my view:
Code Block struct ContentView: View { // @State private var webAudioElementFound = false @State private var paused = true @State private var currentTime = 0.0 @State private var duration = 0.0 private let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() @StateObject private var webViewStore: WebViewStore private let userContentController = WKUserContentController() private let configuration = WKWebViewConfiguration() private let userScript = WKUserScript( source: """ function elementReady(selector) { return new Promise((resolve, reject) => { let el = document.querySelector(selector) if (el) { resolve(el) } new MutationObserver((mutationRecords, observer) => { Array.from(document.querySelectorAll(selector)).forEach((element) => { resolve(element) observer.disconnect() }) }).observe(document.documentElement, { childList: true, subtree: true, }) }) } window.webAudioPromise = elementReady("audio").then((webAudioElement) => { window.webAudioElement = webAudioElement }) """, injectionTime: .atDocumentStart, forMainFrameOnly: false, in: .defaultClient ) init() { userContentController.addUserScript(userScript) configuration.userContentController = userContentController configuration.mediaTypesRequiringUserActionForPlayback = [] let webView = WKWebView(frame: .zero, configuration: configuration) webView.customUserAgent = "Mozilla" _webViewStore = StateObject(wrappedValue: WebViewStore(webView: webView)) }
the body:
Code Block WebView(webView: webViewStore.webView) .onReceive(timer, perform: webViewTimer)
and a the webViewTimer method:
Code Block func webViewTimer(time: Date) { checkPlayback() if !webViewStore.isLoading && !paused { webViewStore.webView.evaluateJavaScript("[webAudioElement.duration, webAudioElement.currentTime]", in: nil, in: .defaultClient, completionHandler: { result in switch result { case .success(let value): guard let array = value as? [Double] else { return } duration = array[0] currentTime = array[1] case .failure(let failure): print("timer failure: \(failure.localizedDescription)") break } }) } }
As you can see, I'm using Timer, but surely there must be a better way no?