From f73bc4532e3d994981cac8c348f04c7bda14c12b Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 17 Jul 2026 21:28:15 +0800 Subject: [PATCH] fix(iOS): show recorder errors as full-screen overlay instead of fleeting toast - Added recorderErrorMessage @Published var for persistent error display - Recording errors now shown as overlay with error details + Dismiss button - presentShareSheet now logs errors and sets recorderErrorMessage on failure - Errors like 'No window scene' or 'No view controller' visible on screen --- Sources/MiniPlayerApp.swift | 24 ++++++++++++++++++++++++ Sources/PlayerBridge.swift | 26 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Sources/MiniPlayerApp.swift b/Sources/MiniPlayerApp.swift index c47af3f..f19e55a 100644 --- a/Sources/MiniPlayerApp.swift +++ b/Sources/MiniPlayerApp.swift @@ -440,6 +440,30 @@ struct PlayerContentView: View { // 播放状态指示(loading/buffering/error) PlayerStatusOverlay(bridge: bridge) + // 录制错误覆盖层(显示在界面上不会一闪而过) + #if os(iOS) + if let errMsg = bridge.recorderErrorMessage { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 36)) + .foregroundColor(.orange) + Text("Recording Error") + .font(.headline).foregroundColor(.white) + Text(errMsg) + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.8)) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + Button("Dismiss") { + bridge.recorderErrorMessage = nil + } + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.black.opacity(0.75)) + } + #endif + // 左上角应用图标 — iOS 锁屏控制 / macOS 切换控制栏 MiniPlayerIcon() .frame(width: 32, height: 32) diff --git a/Sources/PlayerBridge.swift b/Sources/PlayerBridge.swift index 94396bb..7ed14a4 100644 --- a/Sources/PlayerBridge.swift +++ b/Sources/PlayerBridge.swift @@ -124,6 +124,7 @@ final class PlayerBridge: ObservableObject { let hlsRecorder = HLSRecorder() let pipController = PiPController() @Published var pendingExportURL: URL? + @Published var recorderErrorMessage: String? // 录制错误直接显示在界面上 #endif // 诊断日志 private var diagLogURL: URL? @@ -178,12 +179,14 @@ final class PlayerBridge: ObservableObject { #if os(iOS) hlsRecorder.onRecordingSaved = { [weak self] url in - NSLog("[MiniPlayer] onRecordingSaved called: %@", url.lastPathComponent) + NSLog("[MiniPlayer] onRecordingSaved: %@", url.lastPathComponent) + self?.recorderErrorMessage = nil self?.showToast("Saved: \(url.lastPathComponent)") - // 直接用 UIKit present,不依赖 SwiftUI sheet binding self?.presentShareSheet(for: url) } hlsRecorder.onError = { [weak self] msg in + NSLog("[MiniPlayer] onError: %@", msg) + self?.recorderErrorMessage = msg self?.showToast(msg) } #endif @@ -192,22 +195,29 @@ final class PlayerBridge: ObservableObject { #if os(iOS) /// 直接通过 UIKit present UIActivityViewController,绕过 SwiftUI sheet 可能的多 sheet 冲突 func presentShareSheet(for url: URL) { + NSLog("[MiniPlayer] presentShareSheet: %@", url.lastPathComponent) guard let windowScene = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first(where: { $0.activationState == .foregroundActive }) ?? UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first - else { return } + else { + recorderErrorMessage = "No window scene available" + return + } var topVC = windowScene.keyWindow?.rootViewController ?? windowScene.windows.first(where: { !$0.isHidden && $0.bounds.size != .zero })?.rootViewController while let presented = topVC?.presentedViewController { topVC = presented } - let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil) - activityVC.completionWithItemsHandler = { _, _, _, _ in - // 录制完成后可以删掉临时文件?不,用户可能需要再次分享 + guard let presenter = topVC else { + recorderErrorMessage = "No view controller to present share sheet" + return } - topVC?.present(activityVC, animated: true) - NSLog("[MiniPlayer] presentShareSheet: presented for %@", url.lastPathComponent) + + let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil) + activityVC.completionWithItemsHandler = { _, _, _, _ in } + presenter.present(activityVC, animated: true) + NSLog("[MiniPlayer] presentShareSheet: presented successfully") } /// 配置 AVAudioSession,支持后台音频和 PiP