import Foundation @available(iOS 15.0, *) final class MopidyEventStream { private var session: URLSession? private var task: URLSessionDataTask? private let onTrackChanged: () -> Void private let onVolumeChanged: (Int) -> Void private let host: String private let password: String init(host: String, password: String, onTrackChanged: @escaping () -> Void, onVolumeChanged: @escaping (Int) -> Void) { self.host = host self.password = password self.onTrackChanged = onTrackChanged self.onVolumeChanged = onVolumeChanged let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 300 config.timeoutIntervalForResource = 3600 self.session = URLSession(configuration: config) } func connect() { guard let url = URL(string: "http://\(host):6680/mopidy/events") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("text/event-stream", forHTTPHeaderField: "Accept") request.setValue("application/json", forHTTPHeaderField: "Content-Type") self.task = session?.dataTask(with: request) { [weak self] data, response, error in guard let self = self, let data = data else { return } if let text = String(data: data, encoding: .utf8) { self.parseEvents(text) } } task?.resume() } func disconnect() { task?.cancel() session?.finishTasksAndInvalidate() } private func parseEvents(_ text: String) { for line in text.components(separatedBy: "\n") { if line.hasPrefix("event:") && line.contains("tracklistchanged") { onTrackChanged() } if line.hasPrefix("event:") && line.contains("volumechanged") { // volume data would follow in subsequent data: lines } } } }