iOS Swift5 相册选取视频按30秒切片出现时长异常问题求助
iOS视频按固定30秒分割异常解决方案
核心问题点
- 裁剪时长计算错误:你当前在
cropVideo方法中构造CMTimeRange时,直接把endTime作为片段时长传入,正确的片段时长应该是endTime - startTime。当裁剪非首段视频时,起始时间大于0,就会出现片段时长远超过30秒的问题。 - 多导出任务并发冲突:你的
isVideoGettinGEdited变量是用来控制导出串行执行的,但你调用导出方法前没有将该变量设为true,导致多个导出任务同时执行,出现资源竞争、时长计算混乱的问题。 - 文件路径构造不规范:你用
URL(string:)构造本地文件路径、用absoluteString操作本地文件,不符合iOS文件路径规范,可能导致文件读写失败。
修改后的完整代码
import UIKit import AVKit import MobileCoreServices import Photos class ViewController: UIViewController { @IBOutlet weak var videoView: UIImageView! @IBOutlet var imageView: UIImageView! var player: AVPlayer! var avpController = AVPlayerViewController() var isVideoGettinGEdited = false // 新增变量存储待导出的任务参数 var exportQueue: [(url: URL, startTime: Double, endTime: Double, fileName: String)] = [] @IBAction func didTapButton(){ let picker = UIImagePickerController() picker.delegate = self picker.sourceType = .savedPhotosAlbum picker.mediaTypes = [kUTTypeMovie as String] picker.allowsEditing = false present(picker, animated: true, completion: nil) } }
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let image = info[.editedImage] as? UIImage{ imageView.image = image } else { guard let mediaType = info[.mediaType] as? String, mediaType == (kUTTypeMovie as String), let url = info[.mediaURL] as? URL else { return } let videoAsset = AVURLAsset(url: url) let durationTime = ceil(CMTimeGetSeconds(videoAsset.duration)) exportQueue.removeAll() // 生成所有待导出的片段参数 var currentStart = 0.0 while currentStart < durationTime { let currentEnd = min(currentStart + 30, durationTime) let fileName = "Output-\(Int(currentStart/30)+1).mp4" exportQueue.append((url: url, startTime: currentStart, endTime: currentEnd, fileName: fileName)) currentStart = currentEnd } // 开始串行导出 startNextExport() } picker.dismiss(animated: true, completion: nil) } // 新增串行导出控制方法 func startNextExport() { guard !exportQueue.isEmpty, !isVideoGettinGEdited else { return } isVideoGettinGEdited = true let task = exportQueue.removeFirst() cropVideo(atURL: task.url, startTime: task.startTime, endTime: task.endTime, fileName: task.fileName) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } }
extension ViewController { func cropVideo(atURL url:URL, startTime:Double, endTime:Double, fileName:String) { let asset = AVURLAsset(url: url) guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else { isVideoGettinGEdited = false startNextExport() return } // 修正本地路径构造方式 let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! var outputURL = URL(fileURLWithPath: docPath) let fileManager = FileManager.default do { try fileManager.createDirectory(at: outputURL, withIntermediateDirectories: true) } catch { print(error) } outputURL.appendPathComponent(fileName) // 修正文件删除逻辑 if fileManager.fileExists(atPath: outputURL.path) { do { try fileManager.removeItem(at: outputURL) } catch { print(error) } } exportSession.outputURL = outputURL exportSession.shouldOptimizeForNetworkUse = true exportSession.outputFileType = .mp4 // 修正时长计算:片段时长 = 结束时间 - 开始时间 let start = CMTimeMakeWithSeconds(startTime, preferredTimescale: 600) let duration = CMTimeMakeWithSeconds(endTime - startTime, preferredTimescale: 600) let range = CMTimeRangeMake(start: start, duration: duration) exportSession.timeRange = range exportSession.exportAsynchronously { defer { self.isVideoGettinGEdited = false self.startNextExport() } switch exportSession.status { case .completed: PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL) }) { completed, error in DispatchQueue.main.async { self.view.isUserInteractionEnabled = true if completed { print("\(fileName) 已保存到相册") } else if let error = error { print("\(fileName) 保存失败:\(error)") } } } case .failed: print("导出失败:\(exportSession.error?.localizedDescription ?? "未知错误")") default: break } } } //MARK:- saveVideoFromURL func saveVideoFromURL(_ videoURL:String) { self.view.isUserInteractionEnabled = false DispatchQueue.global(qos: .background).async { if let url = URL(string: videoURL), let urlData = NSData(contentsOf: url) { let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]; print("url path component = \(url.lastPathComponent)") let filePath="\(documentsPath)/\(url.lastPathComponent)" urlData.write(toFile: filePath, atomically: true) PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath)) }) { completed, error in DispatchQueue.main.async { self.view.isUserInteractionEnabled = true if completed { print("Video has been saved to your photos.") } else if let error = error { print("Failed to save video:\(error)") } } } } } } }
内容的提问来源于stack exchange,提问作者Nasser FN
相关产品推荐
相关产品推荐

