Fix regia basket e stabilità diretta iOS.
La regia remota non va più in 500 su basket/timed; iOS gestisce chiusura set, riconnessione RTMP e avanzamento quarti in modo affidabile. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
731b43ea88
commit
270bf680a4
@@ -42,6 +42,8 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
private var broadcastGeneration = 0
|
||||
private var suppressDisconnectError = false
|
||||
private var orientationTransitionUntil: Date = .distantPast
|
||||
private var reconnectAttempts = 0
|
||||
private var reconnectTask: Task<Void, Never>?
|
||||
private var lastAppliedCaptureOrientation: AVCaptureVideoOrientation?
|
||||
private var orientationDebounceTask: Task<Void, Never>?
|
||||
private var rtmpSession: (any Session)?
|
||||
@@ -121,6 +123,9 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
self.suppressDisconnectError = suppressDisconnectError
|
||||
defer { self.suppressDisconnectError = false }
|
||||
broadcastGeneration += 1
|
||||
reconnectTask?.cancel()
|
||||
reconnectTask = nil
|
||||
reconnectAttempts = 0
|
||||
publishPending = false
|
||||
publishTask?.cancel()
|
||||
publishTask = nil
|
||||
@@ -130,6 +135,9 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
|
||||
func resumeBroadcast(config: BroadcastConfig) async throws {
|
||||
self.config = config
|
||||
reconnectAttempts = 0
|
||||
reconnectTask?.cancel()
|
||||
reconnectTask = nil
|
||||
publishPending = true
|
||||
if !pipelineConfigured {
|
||||
try await configurePipeline(config)
|
||||
@@ -144,6 +152,9 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
self.suppressDisconnectError = suppressDisconnectError
|
||||
defer { self.suppressDisconnectError = false }
|
||||
broadcastGeneration += 1
|
||||
reconnectTask?.cancel()
|
||||
reconnectTask = nil
|
||||
reconnectAttempts = 0
|
||||
publishTask?.cancel()
|
||||
publishTask = nil
|
||||
publishPending = false
|
||||
@@ -254,10 +265,15 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
try await publish(config: config, generation: generation)
|
||||
if generation == broadcastGeneration {
|
||||
publishPending = false
|
||||
reconnectAttempts = 0
|
||||
}
|
||||
} catch {
|
||||
guard generation == broadcastGeneration else { return }
|
||||
publishPending = false
|
||||
if phase == .reconnecting || reconnectAttempts > 0 {
|
||||
scheduleReconnect()
|
||||
return
|
||||
}
|
||||
let message = UserFacingError.message(for: error) ?? "Connessione RTMP fallita"
|
||||
setPhase(.error, error: message)
|
||||
}
|
||||
@@ -300,16 +316,16 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
try await session.connect { [weak self] in
|
||||
Task { @MainActor in
|
||||
guard let self,
|
||||
!self.shouldSuppressDisconnectError,
|
||||
self.broadcastGeneration == generation,
|
||||
self.phase == .live else { return }
|
||||
self.setPhase(.error, error: "Connessione RTMP interrotta")
|
||||
self.phase == .live || self.phase == .reconnecting else { return }
|
||||
self.scheduleReconnect()
|
||||
}
|
||||
}
|
||||
guard generation == broadcastGeneration else {
|
||||
await teardownRTMP(keepPreview: true)
|
||||
return
|
||||
}
|
||||
reconnectAttempts = 0
|
||||
setPhase(.live)
|
||||
}
|
||||
|
||||
@@ -341,9 +357,8 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
switch state {
|
||||
case .open:
|
||||
setPhase(.live)
|
||||
case .closed where phase == .live:
|
||||
guard !shouldSuppressDisconnectError else { return }
|
||||
setPhase(.error, error: "Connessione RTMP interrotta")
|
||||
case .closed where phase == .live || phase == .reconnecting:
|
||||
scheduleReconnect()
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -369,6 +384,33 @@ final class LiveBroadcastEngine: ObservableObject {
|
||||
suppressDisconnectError || Date() < orientationTransitionUntil
|
||||
}
|
||||
|
||||
/// Riconnessione automatica RTMP (come Android): evita falsi errori su iPad.
|
||||
private func scheduleReconnect() {
|
||||
guard !shouldSuppressDisconnectError else { return }
|
||||
guard phase == .live || phase == .reconnecting else { return }
|
||||
guard let config else { return }
|
||||
|
||||
reconnectAttempts += 1
|
||||
if reconnectAttempts > config.maxReconnectAttempts {
|
||||
reconnectTask = nil
|
||||
setPhase(.error, error: "Connessione RTMP interrotta")
|
||||
return
|
||||
}
|
||||
|
||||
setPhase(.reconnecting)
|
||||
reconnectTask?.cancel()
|
||||
let generation = broadcastGeneration
|
||||
let delayMs = config.reconnectDelayMs
|
||||
reconnectTask = Task {
|
||||
try? await Task.sleep(nanoseconds: UInt64(delayMs) * 1_000_000)
|
||||
guard !Task.isCancelled, generation == broadcastGeneration else { return }
|
||||
guard phase == .reconnecting else { return }
|
||||
publishPending = true
|
||||
publishInFlight = false
|
||||
await startPendingPublish()
|
||||
}
|
||||
}
|
||||
|
||||
private func applyVideoOrientationIfNeeded(force: Bool = false) async {
|
||||
let orientation = BroadcastVideoOrientation.captureOrientation()
|
||||
let landscapeLocked = AppOrientation.mode == .landscape
|
||||
|
||||
Reference in New Issue
Block a user