fix: save dialog not appearing after recording on macOS/iOS
macOS: - Replace deprecated NSSavePanel.begin(completionHandler:) with async begin() API - The old API has been deprecated since macOS 12 and may silently fail on macOS 26 - Added diagnostic logging throughout recording completion flow iOS: - Added diagnostic logging to track onRecordingSaved → pendingExportURL → sheet chain - Logs file existence, size after recording completes Diagnostic logging added to help verify the save dialog flow: - macOS: file size check, showSaveDialog entry, NSSavePanel response - iOS: recordStream output verification, onRecordingSaved call, sheet binding activation
This commit is contained in:
parent
01bf7d6f4a
commit
04990674cc
@ -36,10 +36,13 @@ final class HLSRecorder: ObservableObject {
|
|||||||
recordTask = Task {
|
recordTask = Task {
|
||||||
do {
|
do {
|
||||||
let outputURL = try await recordStream(url: url)
|
let outputURL = try await recordStream(url: url)
|
||||||
NSLog("[MiniPlayer] recordStream completed: %@", outputURL.path)
|
NSLog("[MiniPlayer] recordStream completed: %@, exists: %d", outputURL.path, FileManager.default.fileExists(atPath: outputURL.path))
|
||||||
|
let attrs = try? FileManager.default.attributesOfItem(atPath: outputURL.path)
|
||||||
|
NSLog("[MiniPlayer] Output file size: %lld", (attrs?[.size] as? Int64) ?? 0)
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.isRecording = false
|
self.isRecording = false
|
||||||
self.timer?.invalidate()
|
self.timer?.invalidate()
|
||||||
|
NSLog("[MiniPlayer] Calling onRecordingSaved...")
|
||||||
self.onRecordingSaved?(outputURL)
|
self.onRecordingSaved?(outputURL)
|
||||||
}
|
}
|
||||||
} catch is CancellationError {
|
} catch is CancellationError {
|
||||||
|
|||||||
@ -585,10 +585,15 @@ struct BricksAppView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: Binding<Bool>(
|
.sheet(isPresented: Binding<Bool>(
|
||||||
get: { bridge.pendingExportURL != nil },
|
get: {
|
||||||
|
let has = bridge.pendingExportURL != nil
|
||||||
|
if has { NSLog("[MiniPlayer] Sheet binding: pendingExportURL=%@", bridge.pendingExportURL?.lastPathComponent ?? "nil") }
|
||||||
|
return has
|
||||||
|
},
|
||||||
set: { if !$0 { bridge.pendingExportURL = nil } }
|
set: { if !$0 { bridge.pendingExportURL = nil } }
|
||||||
)) {
|
)) {
|
||||||
if let url = bridge.pendingExportURL {
|
if let url = bridge.pendingExportURL {
|
||||||
|
NSLog("[MiniPlayer] Presenting ActivityShareSheet for: %@", url.lastPathComponent)
|
||||||
ActivityShareSheet(url: url) {
|
ActivityShareSheet(url: url) {
|
||||||
bridge.pendingExportURL = nil
|
bridge.pendingExportURL = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -178,8 +178,10 @@ 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, setting pendingExportURL: %@", url.lastPathComponent)
|
||||||
self?.showToast("Saved: \(url.lastPathComponent)")
|
self?.showToast("Saved: \(url.lastPathComponent)")
|
||||||
self?.pendingExportURL = url
|
self?.pendingExportURL = url
|
||||||
|
NSLog("[MiniPlayer] pendingExportURL set to: %@", url.lastPathComponent)
|
||||||
}
|
}
|
||||||
hlsRecorder.onError = { [weak self] msg in
|
hlsRecorder.onError = { [weak self] msg in
|
||||||
self?.showToast(msg)
|
self?.showToast(msg)
|
||||||
|
|||||||
@ -62,10 +62,13 @@ final class PlayerRecorder: NSObject {
|
|||||||
try await recorder.start(url: url, outputPath: tempURL.path, duration: 0)
|
try await recorder.start(url: url, outputPath: tempURL.path, duration: 0)
|
||||||
// duration=0 表示无限录制,直到 stop() 被调用
|
// duration=0 表示无限录制,直到 stop() 被调用
|
||||||
// start 在 stop() 后会正常返回
|
// start 在 stop() 后会正常返回
|
||||||
NSLog("[Recorder] ✓ Recording completed: %@", tempURL.path)
|
NSLog("[Recorder] ✓ Recording completed: %@, file exists: %d", tempURL.path, FileManager.default.fileExists(atPath: tempURL.path))
|
||||||
|
let attrs = try? FileManager.default.attributesOfItem(atPath: tempURL.path)
|
||||||
|
NSLog("[Recorder] Output file size: %lld", (attrs?[.size] as? Int64) ?? 0)
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
RecorderState.isFinalizing = false
|
RecorderState.isFinalizing = false
|
||||||
self?.stopDurationTimer()
|
self?.stopDurationTimer()
|
||||||
|
NSLog("[Recorder] Calling showSaveDialog...")
|
||||||
self?.showSaveDialog(tempURL: tempURL)
|
self?.showSaveDialog(tempURL: tempURL)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@ -129,6 +132,7 @@ final class PlayerRecorder: NSObject {
|
|||||||
// MARK: - 保存对话框
|
// MARK: - 保存对话框
|
||||||
|
|
||||||
private func showSaveDialog(tempURL: URL) {
|
private func showSaveDialog(tempURL: URL) {
|
||||||
|
NSLog("[Recorder] showSaveDialog called for: %@", tempURL.lastPathComponent)
|
||||||
let panel = NSSavePanel()
|
let panel = NSSavePanel()
|
||||||
panel.allowedContentTypes = [.mpeg4Movie]
|
panel.allowedContentTypes = [.mpeg4Movie]
|
||||||
// 默认文件名去掉 temp_ 前缀
|
// 默认文件名去掉 temp_ 前缀
|
||||||
@ -136,25 +140,28 @@ final class PlayerRecorder: NSObject {
|
|||||||
panel.nameFieldStringValue = defaultName
|
panel.nameFieldStringValue = defaultName
|
||||||
panel.directoryURL = saveDirectory
|
panel.directoryURL = saveDirectory
|
||||||
|
|
||||||
panel.begin { [weak self] response in
|
// NSSavePanel.begin(completionHandler:) 已被 macOS 12 废弃,macOS 26 上可能静默失效
|
||||||
Task { @MainActor in
|
// 使用现代 async begin() API
|
||||||
if response == .OK, let url = panel.url {
|
Task { @MainActor [weak self] in
|
||||||
do {
|
NSLog("[Recorder] NSSavePanel.begin() starting...")
|
||||||
if FileManager.default.fileExists(atPath: url.path) {
|
let response = await panel.begin()
|
||||||
try FileManager.default.removeItem(at: url)
|
NSLog("[Recorder] NSSavePanel response: %d", response.rawValue)
|
||||||
}
|
if response == .OK, let url = panel.url {
|
||||||
try FileManager.default.moveItem(at: tempURL, to: url)
|
do {
|
||||||
NSLog("[Recorder] ✓ Saved: %@", url.path)
|
if FileManager.default.fileExists(atPath: url.path) {
|
||||||
self?.onRecordingSaved?(url)
|
try FileManager.default.removeItem(at: url)
|
||||||
} catch {
|
|
||||||
NSLog("[Recorder] ✗ Save failed: %@", error.localizedDescription)
|
|
||||||
self?.onError?("Save failed: \(error.localizedDescription)")
|
|
||||||
}
|
}
|
||||||
} else {
|
try FileManager.default.moveItem(at: tempURL, to: url)
|
||||||
try? FileManager.default.removeItem(at: tempURL)
|
NSLog("[Recorder] ✓ Saved: %@", url.path)
|
||||||
|
self?.onRecordingSaved?(url)
|
||||||
|
} catch {
|
||||||
|
NSLog("[Recorder] ✗ Save failed: %@", error.localizedDescription)
|
||||||
|
self?.onError?("Save failed: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
self?.checkPendingTerminate()
|
} else {
|
||||||
|
try? FileManager.default.removeItem(at: tempURL)
|
||||||
}
|
}
|
||||||
|
self?.checkPendingTerminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user