Fix HLS browser in prod e stabilizza broadcast RTMP su iOS.

Il proxy Rails gestisce il redirect MediaMTX su /live/match_* e riscrive le playlist; edge nginx reindirizza /live/ sotto /hls/. Su iOS, SessionBuilder HaishinKit, pipeline mixer corretta e refresh token.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Emiliano Frascaro
2026-06-19 18:48:29 +02:00
co-authored by Cursor
parent ed0f913138
commit a303a94671
16 changed files with 1092 additions and 319 deletions
@@ -79,7 +79,12 @@ struct BroadcastScreen: View {
.onChange(of: scoreController.lastActionError) { message in
if let message { snackbarMessage = message }
}
.onChange(of: broadcastCoordinator.metrics.phase) { _ in updateOverlay() }
.onChange(of: broadcastCoordinator.metrics.phase) { phase in
updateOverlay()
if phase == .error, let message = broadcastCoordinator.metrics.lastError {
self.error = message
}
}
.alert("Errore", isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) {
Button("OK") { onFinished() }
} message: {
@@ -287,18 +292,19 @@ struct BroadcastScreen: View {
match = loadedMatch
container.scoreController.bind(sessionId: sessionId, initial: loaded.score)
wireCable()
await preloadLogos(for: loadedMatch)
loading = false
updateOverlay()
try? await Task.sleep(nanoseconds: 100_000_000)
if let url = loaded.rtmpIngestUrl, !url.isEmpty {
let config = broadcastConfig(for: loaded, rtmpUrl: url)
if loaded.isPaused {
try await container.broadcastCoordinator.engine.preparePreview(config: config)
} else {
try await container.broadcastCoordinator.startBroadcast(config: config)
try await container.broadcastCoordinator.engine.prepareForBroadcast(config: config)
}
}
await preloadLogos(for: loadedMatch)
startPolling()
loading = false
updateOverlay()
} catch {
if let message = UserFacingError.message(for: error) {
self.error = message
@@ -58,13 +58,13 @@ struct LoginScreen: View {
_ = try await container.authRepository.login(email: email.trimmingCharacters(in: .whitespaces), password: password)
onLoggedIn()
} catch {
let message = error.localizedDescription
if message.contains("401") {
if let apiError = error as? APIError, case .unauthorized = apiError {
self.error = "Email o password non corretti"
} else if message.localizedCaseInsensitiveContains("timeout") {
} else if let message = UserFacingError.message(for: error),
message.localizedCaseInsensitiveContains("timeout") {
self.error = "Server non raggiungibile. Verifica la connessione."
} else {
self.error = message
self.error = UserFacingError.message(for: error) ?? "Accesso non riuscito"
}
}
loading = false
@@ -1,3 +1,5 @@
import HaishinKit
import RTMPHaishinKit
import SwiftUI
import UIKit
@@ -77,6 +79,9 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
AppOrientation.lockPortrait()
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
}
return true
}