fix(iOS): move pendingExportURL to PlayerBridge — SwiftUI can now observe it and trigger save sheet

This commit is contained in:
yumoqing 2026-07-03 08:29:02 +08:00
parent 3ed5696ae0
commit 1412c234f8
3 changed files with 7 additions and 110 deletions

View File

@ -10,7 +10,6 @@ import Photos
final class HLSRecorder: ObservableObject {
@Published var isRecording = false
@Published var durationText = "00:00"
@Published var pendingExportURL: URL?
var onRecordingSaved: ((URL) -> Void)?
var onError: ((String) -> Void)?
@ -42,7 +41,6 @@ final class HLSRecorder: ObservableObject {
self.isRecording = false
self.timer?.invalidate()
self.onRecordingSaved?(outputURL)
self.showExportSheet(url: outputURL)
}
} catch is CancellationError {
NSLog("[MiniPlayer] Recording task cancelled")
@ -294,108 +292,6 @@ final class HLSRecorder: ObservableObject {
return outputMP4
}
// MARK: - /
private func showExportSheet(url: URL) {
NSLog("[MiniPlayer] showExportSheet: \(url.path)")
// 1. 使 PHPhotoLibrary
if url.pathExtension.lowercased() == "mp4" || url.pathExtension.lowercased() == "mov" {
saveToPhotoLibrary(url: url)
}
// 2. SwiftUI sheet present UIViewController
DispatchQueue.main.async { [weak self] in
self?.pendingExportURL = url
}
}
private func saveToPhotoLibrary(url: URL) {
let status = PHPhotoLibrary.authorizationStatus(for: .addOnly)
if status == .authorized || status == .limited {
performSave(url: url)
} else if status == .notDetermined {
PHPhotoLibrary.requestAuthorization(for: .addOnly) { [weak self] newStatus in
DispatchQueue.main.async {
if newStatus == .authorized || newStatus == .limited {
self?.performSave(url: url)
} else {
self?.onError?("Photo library access denied")
}
}
}
} else {
onError?("Photo library access denied. Please enable in Settings.")
}
}
private func performSave(url: URL) {
PHPhotoLibrary.shared().performChanges {
PHAssetCreationRequest.forAsset().addResource(with: .video, fileURL: url, options: nil)
} completionHandler: { [weak self] success, error in
DispatchQueue.main.async {
if success {
self?.onRecordingSaved?(url)
NSLog("[MiniPlayer] Video saved to Photos")
} else {
self?.onError?("Save to Photos failed: \(error?.localizedDescription ?? "unknown")")
}
}
}
}
private func presentActivitySheet(url: URL) {
NSLog("[MiniPlayer] presentActivitySheet called for: %@", 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 {
NSLog("[MiniPlayer] No window scene found")
return
}
// VC keyWindow fallback window
var topVC: UIViewController?
if let keyWindow = windowScene.keyWindow {
topVC = keyWindow.rootViewController
}
if topVC == nil {
for window in windowScene.windows where !window.isHidden && window.bounds.size != .zero {
topVC = window.rootViewController
if topVC != nil { break }
}
}
if topVC == nil {
topVC = windowScene.windows.first?.rootViewController
}
// 沿 presentedViewController
while let presented = topVC?.presentedViewController {
topVC = presented
}
guard let vc = topVC else {
NSLog("[MiniPlayer] No view controller found to present share sheet")
return
}
NSLog("[MiniPlayer] Presenting from VC: %@", String(describing: type(of: vc)))
let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
// iPad
if let popover = activityVC.popoverPresentationController {
popover.sourceView = vc.view
popover.sourceRect = CGRect(x: vc.view?.bounds.midX ?? 0, y: vc.view?.bounds.midY ?? 0, width: 0, height: 0)
popover.permittedArrowDirections = []
}
vc.present(activityVC, animated: true) {
NSLog("[MiniPlayer] Share sheet presented successfully")
}
}
// MARK: - Helpers
private func resolveURL(_ urlString: String, baseURL: URL) -> URL {

View File

@ -582,12 +582,12 @@ struct BricksAppView: View {
}
}
.sheet(isPresented: Binding<Bool>(
get: { bridge.hlsRecorder.pendingExportURL != nil },
set: { if !$0 { bridge.hlsRecorder.pendingExportURL = nil } }
get: { bridge.pendingExportURL != nil },
set: { if !$0 { bridge.pendingExportURL = nil } }
)) {
if let url = bridge.hlsRecorder.pendingExportURL {
if let url = bridge.pendingExportURL {
ActivityShareSheet(url: url) {
bridge.hlsRecorder.pendingExportURL = nil
bridge.pendingExportURL = nil
}
}
}

View File

@ -122,7 +122,8 @@ final class PlayerBridge: ObservableObject {
#if os(iOS)
let hlsRecorder = HLSRecorder()
let pipController = PiPController()
@Published var pendingExportURL: URL?
#endif
//
private var diagLogURL: URL?
private var diagLogHandle: FileHandle?
@ -144,7 +145,6 @@ final class PlayerBridge: ObservableObject {
try? diagLogHandle?.write(contentsOf: data)
}
}
#endif
// MARK: -
func setup() {
@ -178,6 +178,7 @@ final class PlayerBridge: ObservableObject {
#if os(iOS)
hlsRecorder.onRecordingSaved = { [weak self] url in
self?.showToast("Saved: \(url.lastPathComponent)")
self?.pendingExportURL = url
}
hlsRecorder.onError = { [weak self] msg in
self?.showToast(msg)