Resolve various connection issues
This commit is contained in:
@@ -54,10 +54,12 @@ struct NowPlayingInfo: Codable
|
||||
let volume: Int
|
||||
}
|
||||
|
||||
struct API
|
||||
actor API
|
||||
{
|
||||
let baseURL: URL
|
||||
|
||||
private var pingTask: Task<(), any Swift.Error>? = nil
|
||||
|
||||
init(baseURL: URL) {
|
||||
self.baseURL = baseURL
|
||||
}
|
||||
@@ -168,17 +170,20 @@ struct API
|
||||
}
|
||||
|
||||
public func events() async throws -> AsyncStream<StreamEvent> {
|
||||
let requestBuilder: () -> RequestBuilder = request
|
||||
|
||||
return AsyncStream { continuation in
|
||||
var websocketTask: URLSessionWebSocketTask = spawnWebsocketTask(with: continuation)
|
||||
let websocketTask: URLSessionWebSocketTask = API.spawnWebsocketTask(requestBuilder: requestBuilder, with: continuation)
|
||||
|
||||
Task {
|
||||
while true {
|
||||
var pingLoopEnabled = true
|
||||
while pingLoopEnabled {
|
||||
try await Task.sleep(for: .seconds(5))
|
||||
|
||||
websocketTask.sendPing { error in
|
||||
if let error {
|
||||
notifyError(error, continuation: continuation)
|
||||
websocketTask = spawnWebsocketTask(with: continuation)
|
||||
API.notifyError(error, continuation: continuation)
|
||||
pingLoopEnabled = false
|
||||
} else {
|
||||
continuation.yield(.event(Event(type: .receivedWebsocketPong)))
|
||||
}
|
||||
@@ -188,11 +193,12 @@ struct API
|
||||
}
|
||||
}
|
||||
|
||||
private func spawnWebsocketTask(
|
||||
private static func spawnWebsocketTask(
|
||||
requestBuilder: () -> RequestBuilder,
|
||||
with continuation: AsyncStream<StreamEvent>.Continuation
|
||||
) -> URLSessionWebSocketTask
|
||||
{
|
||||
let url = request()
|
||||
let url = requestBuilder()
|
||||
.path("/events")
|
||||
.websocket()
|
||||
|
||||
@@ -225,8 +231,9 @@ struct API
|
||||
return websocketTask
|
||||
}
|
||||
|
||||
private func notifyError(_ error: any Swift.Error, continuation: AsyncStream<StreamEvent>.Continuation) {
|
||||
private static func notifyError(_ error: any Swift.Error, continuation: AsyncStream<StreamEvent>.Continuation) {
|
||||
print("Websocket Error: \(error)")
|
||||
|
||||
var shouldNotifyObservers = true
|
||||
let nsError = error as NSError
|
||||
if nsError.code == 53 {
|
||||
|
||||
@@ -98,10 +98,17 @@ extension ContentView
|
||||
for await streamEvent in try await api.events() {
|
||||
switch streamEvent {
|
||||
case .event(let event):
|
||||
model.connectionError = nil
|
||||
await clearConnectionErrorIfNecessary()
|
||||
await handle(event: event)
|
||||
case .error(let error):
|
||||
model.connectionError = error
|
||||
|
||||
Task { @MainActor in
|
||||
try await Task.sleep(for: .seconds(1.0))
|
||||
websocketRestartTrigger += 1
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -128,10 +135,14 @@ extension ContentView
|
||||
|
||||
case .receivedWebsocketPong:
|
||||
// This means we're online.
|
||||
if model.connectionError != nil {
|
||||
model.connectionError = nil
|
||||
await refresh([.playlist, .nowPlaying, .favorites])
|
||||
}
|
||||
await clearConnectionErrorIfNecessary()
|
||||
}
|
||||
}
|
||||
|
||||
private func clearConnectionErrorIfNecessary() async {
|
||||
if model.connectionError != nil {
|
||||
model.connectionError = nil
|
||||
await refresh([.playlist, .nowPlaying, .favorites])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,10 +149,14 @@ class MainViewModel
|
||||
withObservationTracking {
|
||||
_ = nowPlayingViewModel.volume
|
||||
} onChange: { [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
let isRefreshing = isRefreshingFromAPI
|
||||
Task {
|
||||
guard let self else { return }
|
||||
await self.withModificationsViaAPI { api in
|
||||
try await api.setVolume(self.nowPlayingViewModel.volume)
|
||||
if !isRefreshing {
|
||||
await self.withModificationsViaAPI { api in
|
||||
try await api.setVolume(self.nowPlayingViewModel.volume)
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run { self.observeNowPlayingModel() }
|
||||
|
||||
@@ -12,7 +12,11 @@ struct NowPlayingMiniView: View {
|
||||
let onTap: () -> Void
|
||||
|
||||
@GestureState private var tapGestureState = false
|
||||
private var nothingQueued: Bool { model.title == nil && model.subtitle == nil }
|
||||
|
||||
private var nothingQueued: Bool {
|
||||
guard let title = model.title, let subtitle = model.subtitle else { return true }
|
||||
return title.isEmpty && subtitle.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let playPauseImageName = model.isPlaying ? "pause.fill" : "play.fill"
|
||||
@@ -26,14 +30,14 @@ struct NowPlayingMiniView: View {
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
if let title = model.title {
|
||||
if let title = model.title, !title.isEmpty {
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
.lineLimit(1)
|
||||
.bold()
|
||||
}
|
||||
|
||||
if let subtitle = model.subtitle {
|
||||
if let subtitle = model.subtitle, !subtitle.isEmpty {
|
||||
Text(subtitle)
|
||||
.lineLimit(1)
|
||||
.font(.caption)
|
||||
|
||||
@@ -175,7 +175,10 @@ struct AddServerView: View
|
||||
_ = try await api.fetchNowPlayingInfo()
|
||||
|
||||
self.validationState = .valid
|
||||
self.serverURL = self.validationURL
|
||||
|
||||
if validationURL != serverURL {
|
||||
self.serverURL = self.validationURL
|
||||
}
|
||||
} catch {
|
||||
print("Validation failed: \(error)")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user