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

322 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import UIKit
///
public class ImageUploadService {
// MARK: - Shared Instance
public static let shared = ImageUploadService()
// MARK: - Properties
private let uploader: ImageUploaderGetID
// MARK: - Initialization
public init(uploader: ImageUploaderGetID = ImageUploaderGetID()) {
self.uploader = uploader
}
// MARK: - Public Methods
///
/// - Parameters:
/// - image:
/// - progressHandler: (0.0 1.0)
/// - completion:
public func uploadImage(
_ image: UIImage,
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
completion: @escaping (Result<ImageUploaderGetID.UploadResult, Error>) -> Void
) {
uploader.uploadImage(
image,
progress: { progress in
let progressInfo = ImageUploadService.UploadProgress(
current: Int(progress * 100),
total: 100,
progress: progress,
isOriginal: true
)
DispatchQueue.main.async {
progressHandler(progressInfo)
}
},
completion: { result in
DispatchQueue.main.async {
completion(result)
}
}
)
}
///
/// - Parameters:
/// - image:
/// - compressionQuality: (0.0 1.0)
/// - progressHandler: (0.0 1.0)
/// - completion:
public func uploadCompressedImage(
_ image: UIImage,
compressionQuality: CGFloat = 0.5,
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
completion: @escaping (Result<ImageUploaderGetID.UploadResult, Error>) -> Void
) {
guard let compressedImage = image.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
completion(.failure(NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片压缩失败"])))
return
}
uploadImage(
compressedImage,
progress: progressHandler,
completion: completion
)
}
///
/// - Parameters:
/// - image:
/// - compressionQuality: (0.0 1.0)
/// - progressHandler:
/// - completion:
public func uploadOriginalAndCompressedImage(
_ image: UIImage,
compressionQuality: CGFloat = 0.5,
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
completion: @escaping (Result<ImageUploadService.UploadResults, Error>) -> Void
) {
//
uploadImage(image, progress: { progress in
let originalProgress = ImageUploadService.UploadProgress(
current: Int(progress.progress * 100),
total: 200, // 200100 + 100
progress: progress.progress * 0.5, // 50%
isOriginal: true
)
progressHandler(originalProgress)
}) { [weak self] originalResult in
guard let self = self else { return }
switch originalResult {
case .success(let originalUploadResult):
//
self.uploadCompressedImage(
image,
compressionQuality: compressionQuality,
progress: { progress in
let compressedProgress = ImageUploadService.UploadProgress(
current: 100 + Int(progress.progress * 100), // 100
total: 200, // 200100 + 100
progress: 0.5 + (progress.progress * 0.5), // 50%
isOriginal: false
)
progressHandler(compressedProgress)
},
completion: { compressedResult in
switch compressedResult {
case .success(let compressedUploadResult):
let results = ImageUploadService.UploadResults(
original: originalUploadResult,
compressed: compressedUploadResult
)
completion(.success(results))
case .failure(let error):
completion(.failure(error))
}
}
)
case .failure(let error):
completion(.failure(error))
}
}
}
// MARK: - Unified Media Upload
///
/// - Parameters:
/// - media:
/// - compressionQuality: / (0.0 1.0)
/// - progressHandler: (0.0 1.0)
/// - completion:
public func uploadMedia(
_ media: MediaType,
compressionQuality: CGFloat = 0.7,
progress progressHandler: @escaping (UploadProgress) -> Void,
completion: @escaping (Result<MediaUploadResult, Error>) -> Void
) {
switch media {
case .image(let image):
//
uploadCompressedImage(
image,
compressionQuality: compressionQuality,
progress: progressHandler,
completion: { result in
let mediaResult = result.map { MediaUploadResult.file($0) }
completion(mediaResult)
}
)
case .video(let videoURL, let thumbnail):
//
uploadVideoWithThumbnail(
videoURL: videoURL,
existingThumbnail: thumbnail,
compressionQuality: compressionQuality,
progress: progressHandler,
completion: completion
)
}
}
///
private func uploadVideoWithThumbnail(
videoURL: URL,
existingThumbnail: UIImage?,
compressionQuality: CGFloat,
progress progressHandler: @escaping (UploadProgress) -> Void,
completion: @escaping (Result<MediaUploadResult, Error>) -> Void
) {
// 1.
func processThumbnail(_ thumbnail: UIImage) {
// 2.
guard let compressedThumbnail = thumbnail.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
let error = NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "缩略图压缩失败"])
completion(.failure(error))
return
}
// 3.
let videoProgress = Progress(totalUnitCount: 100)
let thumbnailProgress = Progress(totalUnitCount: 100)
//
let totalProgress = Progress(totalUnitCount: 200) // 100 + 100
totalProgress.addChild(videoProgress, withPendingUnitCount: 100)
totalProgress.addChild(thumbnailProgress, withPendingUnitCount: 100)
//
self.uploader.uploadVideo(
videoURL,
progress: { progress in
videoProgress.completedUnitCount = Int64(progress * 100)
let currentProgress = Double(totalProgress.completedUnitCount) / 200.0
progressHandler(UploadProgress(
current: Int(progress * 100),
total: 100,
progress: currentProgress,
isOriginal: true
))
},
completion: { videoResult in
switch videoResult {
case .success(let videoUploadResult):
// 4.
self.uploadCompressedImage(
compressedThumbnail,
compressionQuality: 1.0, //
progress: { progress in
thumbnailProgress.completedUnitCount = Int64(progress.progress * 100)
let currentProgress = Double(totalProgress.completedUnitCount) / 200.0
progressHandler(UploadProgress(
current: 100 + Int(progress.progress * 100),
total: 200,
progress: currentProgress,
isOriginal: false
))
},
completion: { thumbnailResult in
switch thumbnailResult {
case .success(let thumbnailUploadResult):
let result = MediaUploadResult.video(
video: videoUploadResult,
thumbnail: thumbnailUploadResult
)
completion(.success(result))
case .failure(let error):
completion(.failure(error))
}
}
)
case .failure(let error):
completion(.failure(error))
}
}
)
}
// 使
if let thumbnail = existingThumbnail {
processThumbnail(thumbnail)
} else {
//
MediaUtils.extractFirstFrame(from: videoURL) { result in
switch result {
case .success(let thumbnail):
processThumbnail(thumbnail)
case .failure(let error):
completion(.failure(error))
}
}
}
}
// MARK: - Supporting Types
///
public enum MediaType {
case image(UIImage)
case video(URL, UIImage?)
}
///
public enum MediaUploadResult {
case file(ImageUploaderGetID.UploadResult)
case video(video: ImageUploaderGetID.UploadResult, thumbnail: ImageUploaderGetID.UploadResult)
/// IDID
public var fileId: String {
switch self {
case .file(let result):
return result.fileId
case .video(let videoResult, _):
return videoResult.fileId
}
}
}
///
public struct UploadProgress {
public let current: Int
public let total: Int
public let progress: Double
public let isOriginal: Bool
public init(current: Int, total: Int, progress: Double, isOriginal: Bool) {
self.current = current
self.total = total
self.progress = progress
self.isOriginal = isOriginal
}
}
///
public struct UploadResults {
public let original: ImageUploaderGetID.UploadResult
public let compressed: ImageUploaderGetID.UploadResult
public init(original: ImageUploaderGetID.UploadResult,
compressed: ImageUploaderGetID.UploadResult) {
self.original = original
self.compressed = compressed
}
}
}