wake-ios/wake/View/Components/Upload/MediaPickerWithLogger.swift
2025-08-21 19:39:36 +08:00

168 lines
7.2 KiB
Swift

import SwiftUI
import PhotosUI
import os.log
import AVKit
struct MediaPickerWithLogging: UIViewControllerRepresentable {
@Binding var selectedMedia: [MediaType]
let selectionLimit: Int
let onDismiss: (() -> Void)?
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = .any(of: [.videos, .images])
configuration.selectionLimit = selectionLimit
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: MediaPickerWithLogging
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "MediaPickerWithLogging")
init(_ parent: MediaPickerWithLogging) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard !results.isEmpty else {
parent.onDismiss?()
return
}
var processedMedia: [MediaType] = []
let group = DispatchGroup()
for result in results {
let itemProvider = result.itemProvider
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
group.enter()
processImage(itemProvider: itemProvider) { media in
if let media = media {
processedMedia.append(media)
}
group.leave()
}
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
group.enter()
processVideo(itemProvider: itemProvider) { media in
if let media = media {
processedMedia.append(media)
}
group.leave()
}
}
}
group.notify(queue: .main) {
self.parent.selectedMedia = processedMedia
self.printMediaInfo(media: processedMedia)
self.parent.onDismiss?()
}
}
private func processImage(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) {
itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in
if let image = object as? UIImage {
completion(.image(image))
} else {
self.logger.error("Failed to load image: \(error?.localizedDescription ?? "Unknown error")")
completion(nil)
}
}
}
private func processVideo(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) {
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
guard let videoURL = url, error == nil else {
self.logger.error("Failed to load video: \(error?.localizedDescription ?? "Unknown error")")
completion(nil)
return
}
let tempDirectory = FileManager.default.temporaryDirectory
let targetURL = tempDirectory.appendingPathComponent("\(UUID().uuidString).\(videoURL.pathExtension)")
do {
if FileManager.default.fileExists(atPath: targetURL.path) {
try FileManager.default.removeItem(at: targetURL)
}
try FileManager.default.copyItem(at: videoURL, to: targetURL)
MediaUtils.extractFirstFrame(from: targetURL) { result in
switch result {
case .success(let thumbnail):
completion(.video(targetURL, thumbnail))
case .failure(let error):
self.logger.error("Failed to extract video thumbnail: \(error.localizedDescription)")
completion(.video(targetURL, nil))
}
}
} catch {
self.logger.error("Failed to copy video file: \(error.localizedDescription)")
completion(nil)
}
}
}
private func printMediaInfo(media: [MediaType]) {
print("=== Selected Media Information ===")
for (index, media) in media.enumerated() {
print("\nItem \(index + 1):")
switch media {
case .image(let image):
print("Type: Image")
print("Dimensions: \(Int(image.size.width))x\(Int(image.size.height))")
if let data = image.jpegData(compressionQuality: 1.0) {
print("File Size: \(formatFileSize(Int64(data.count)))")
}
case .video(let url, _):
print("Type: Video")
print("File Name: \(url.lastPathComponent)")
print("File Path: \(url.path)")
if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
let fileSize = attributes[.size] as? Int64 {
print("File Size: \(formatFileSize(fileSize))")
}
let asset = AVURLAsset(url: url)
let duration = asset.duration.seconds
print("Duration: \(formatTimeInterval(duration))")
if let track = asset.tracks(withMediaType: .video).first {
let size = track.naturalSize
print("Video Dimensions: \(Int(size.width))x\(Int(size.height))")
}
}
}
print("================================\n")
}
private func formatFileSize(_ bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB]
formatter.countStyle = .file
return formatter.string(fromByteCount: bytes)
}
private func formatTimeInterval(_ interval: TimeInterval) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: interval) ?? "00:00"
}
}
}