From 1412c234f811dda3eaaa3e1d25d60a746fbb6ee9 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 3 Jul 2026 08:29:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(iOS):=20move=20pendingExportURL=20to=20Play?= =?UTF-8?q?erBridge=20=E2=80=94=20SwiftUI=20can=20now=20observe=20it=20and?= =?UTF-8?q?=20trigger=20save=20sheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/HLSRecorder.swift | 104 ------------------------------------ Sources/MiniPlayerApp.swift | 8 +-- Sources/PlayerBridge.swift | 5 +- 3 files changed, 7 insertions(+), 110 deletions(-) diff --git a/Sources/HLSRecorder.swift b/Sources/HLSRecorder.swift index 34bab47..0631a87 100644 --- a/Sources/HLSRecorder.swift +++ b/Sources/HLSRecorder.swift @@ -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 { diff --git a/Sources/MiniPlayerApp.swift b/Sources/MiniPlayerApp.swift index 1b81167..23ea9df 100644 --- a/Sources/MiniPlayerApp.swift +++ b/Sources/MiniPlayerApp.swift @@ -582,12 +582,12 @@ struct BricksAppView: View { } } .sheet(isPresented: Binding( - 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 } } } diff --git a/Sources/PlayerBridge.swift b/Sources/PlayerBridge.swift index fc6e73d..a8427bd 100644 --- a/Sources/PlayerBridge.swift +++ b/Sources/PlayerBridge.swift @@ -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)