Fix connessione RTMP in dev: riscrivi host Docker mediamtx per iOS.

Il simulatore non risolve «mediamtx»; l'app riscrive l'ingest verso 127.0.0.1 e docker-compose espone MEDIAMTX_RTMP_URL client-safe con messaggi d'errore RTMP più chiari.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Emiliano Frascaro
2026-06-15 07:30:06 +02:00
co-authored by Cursor
parent 5de3a1a1cf
commit 80813b390b
6 changed files with 101 additions and 2 deletions
@@ -9,3 +9,39 @@ enum MediaUrl {
return "\(base)/\(path)"
}
}
/// Normalizza l'URL RTMP restituito dall'API per il dispositivo corrente.
enum RtmpIngestUrl {
/// Host interni Docker non raggiungibili da simulatore / telefono localhost o LAN.
private static let dockerInternalHosts: Set<String> = ["mediamtx", "host.docker.internal"]
static func resolveForDevice(_ urlString: String) -> String {
guard var components = URLComponents(string: urlString),
let host = components.host?.lowercased() else {
return urlString
}
guard dockerInternalHosts.contains(host) || shouldRewriteForLocalApi else {
return urlString
}
components.host = localIngestHost
if components.port == nil {
components.port = 1935
}
return components.url?.absoluteString ?? urlString
}
private static var shouldRewriteForLocalApi: Bool {
guard let host = URL(string: AppConfig.apiBaseUrl)?.host?.lowercased() else { return false }
return host == "localhost" || host == "127.0.0.1" || host.hasSuffix(".local")
}
/// Host RTMP per dev locale: simulatore iOS 127.0.0.1; device fisico stesso host dell'API.
private static var localIngestHost: String {
guard let apiHost = URL(string: AppConfig.apiBaseUrl)?.host else { return "127.0.0.1" }
let lower = apiHost.lowercased()
if lower == "localhost" || lower == "127.0.0.1" {
return "127.0.0.1"
}
return apiHost
}
}
@@ -10,10 +10,30 @@ enum UserFacingError {
static func message(for error: Error, fallback: String = "Operazione non riuscita") -> String? {
if isCancellation(error) { return nil }
if let rtmp = rtmpMessage(for: error) { return rtmp }
if let api = error as? LocalizedError, let description = api.errorDescription, !description.isEmpty {
return description
}
let text = error.localizedDescription
if text.contains("RTMPConnection.Error") || text.contains("RTMPHaishinKit") {
return "Connessione RTMP fallita. Verifica che MediaMTX sia raggiungibile (in dev: porta 1935 su localhost)."
}
return text.isEmpty ? fallback : text
}
private static func rtmpMessage(for error: Error) -> String? {
let text = String(describing: error)
let localized = error.localizedDescription
guard text.contains("RTMPConnection") || localized.contains("RTMPConnection") else { return nil }
if localized.contains("unsupportedCommand") || text.contains("unsupportedCommand") {
return "URL RTMP non valido. Controlla l'indirizzo di ingest dal server."
}
if localized.contains("connectionTimedOut") || text.contains("connectionTimedOut") {
return "Timeout connessione RTMP. Verifica rete e server MediaMTX (porta 1935)."
}
if localized.contains("socketError") || text.contains("socketError") {
return "Server RTMP non raggiungibile. In dev locale usa localhost:1935 (non l'host Docker «mediamtx»)."
}
return "Connessione RTMP fallita. Verifica rete e server streaming."
}
}
@@ -426,7 +426,7 @@ struct BroadcastScreen: View {
private func broadcastConfig(for loaded: StreamSession, rtmpUrl: String) -> BroadcastConfig {
BroadcastConfig(
rtmpUrl: rtmpUrl,
rtmpUrl: RtmpIngestUrl.resolveForDevice(rtmpUrl),
width: 1280,
height: 720,
videoBitrate: loaded.targetBitrate,