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
This commit is contained in:
parent
04fb3acecc
commit
f73bc4532e
@ -440,6 +440,30 @@ struct PlayerContentView: View {
|
|||||||
// 播放状态指示(loading/buffering/error)
|
// 播放状态指示(loading/buffering/error)
|
||||||
PlayerStatusOverlay(bridge: bridge)
|
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 切换控制栏
|
// 左上角应用图标 — iOS 锁屏控制 / macOS 切换控制栏
|
||||||
MiniPlayerIcon()
|
MiniPlayerIcon()
|
||||||
.frame(width: 32, height: 32)
|
.frame(width: 32, height: 32)
|
||||||
|
|||||||
@ -124,6 +124,7 @@ final class PlayerBridge: ObservableObject {
|
|||||||
let hlsRecorder = HLSRecorder()
|
let hlsRecorder = HLSRecorder()
|
||||||
let pipController = PiPController()
|
let pipController = PiPController()
|
||||||
@Published var pendingExportURL: URL?
|
@Published var pendingExportURL: URL?
|
||||||
|
@Published var recorderErrorMessage: String? // 录制错误直接显示在界面上
|
||||||
#endif
|
#endif
|
||||||
// 诊断日志
|
// 诊断日志
|
||||||
private var diagLogURL: URL?
|
private var diagLogURL: URL?
|
||||||
@ -178,12 +179,14 @@ final class PlayerBridge: ObservableObject {
|
|||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
hlsRecorder.onRecordingSaved = { [weak self] url in
|
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)")
|
self?.showToast("Saved: \(url.lastPathComponent)")
|
||||||
// 直接用 UIKit present,不依赖 SwiftUI sheet binding
|
|
||||||
self?.presentShareSheet(for: url)
|
self?.presentShareSheet(for: url)
|
||||||
}
|
}
|
||||||
hlsRecorder.onError = { [weak self] msg in
|
hlsRecorder.onError = { [weak self] msg in
|
||||||
|
NSLog("[MiniPlayer] onError: %@", msg)
|
||||||
|
self?.recorderErrorMessage = msg
|
||||||
self?.showToast(msg)
|
self?.showToast(msg)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -192,22 +195,29 @@ final class PlayerBridge: ObservableObject {
|
|||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
/// 直接通过 UIKit present UIActivityViewController,绕过 SwiftUI sheet 可能的多 sheet 冲突
|
/// 直接通过 UIKit present UIActivityViewController,绕过 SwiftUI sheet 可能的多 sheet 冲突
|
||||||
func presentShareSheet(for url: URL) {
|
func presentShareSheet(for url: URL) {
|
||||||
|
NSLog("[MiniPlayer] presentShareSheet: %@", url.lastPathComponent)
|
||||||
guard let windowScene = UIApplication.shared.connectedScenes
|
guard let windowScene = UIApplication.shared.connectedScenes
|
||||||
.compactMap({ $0 as? UIWindowScene })
|
.compactMap({ $0 as? UIWindowScene })
|
||||||
.first(where: { $0.activationState == .foregroundActive })
|
.first(where: { $0.activationState == .foregroundActive })
|
||||||
?? UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first
|
?? UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first
|
||||||
else { return }
|
else {
|
||||||
|
recorderErrorMessage = "No window scene available"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var topVC = windowScene.keyWindow?.rootViewController
|
var topVC = windowScene.keyWindow?.rootViewController
|
||||||
?? windowScene.windows.first(where: { !$0.isHidden && $0.bounds.size != .zero })?.rootViewController
|
?? windowScene.windows.first(where: { !$0.isHidden && $0.bounds.size != .zero })?.rootViewController
|
||||||
while let presented = topVC?.presentedViewController { topVC = presented }
|
while let presented = topVC?.presentedViewController { topVC = presented }
|
||||||
|
|
||||||
let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
guard let presenter = topVC else {
|
||||||
activityVC.completionWithItemsHandler = { _, _, _, _ in
|
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
|
/// 配置 AVAudioSession,支持后台音频和 PiP
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user