В моей программе есть класс, который объединяет множество видеофайлов для создания 1 общего видео. У меня есть 1 основной актив, который я использую в первую очередь, а остальные активы я использую сверху. Единственный используемый аудиофайл - из основного ресурса. Вот код:
import UIKit
import AVFoundation
import Photos
class Merger: NSObject {
var controller:EditVideoViewController!
var button:AddAssetButton!
var view:UIView!
var difference:Double!
var changed:Bool = false
var AI:AIView!
convenience init(controller:EditVideoViewController, button:AddAssetButton) {
self.init()
self.controller = controller
self.button = button
self.view = UIView(frame: controller.view.bounds)
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
self.controller.view.addSubview(self.view)
}
func setupAI() {
self.AI = AIView(view: self.view)
self.AI.start()
}
func removeAI() {
self.AI.stop()
self.AI.removeEverything()
}
//The video is displaying in Portrait after merge.
func merge(completion:@escaping () -> Void, assets:[Asset]) {
self.setupAI()
let assets = assets.sorted(by: { $0.layer.zPosition < $1.layer.zPosition })
if let firstAsset = controller.firstAsset {
let mixComposition = AVMutableComposition()
let firstTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
do {
try firstTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, self.controller.realDuration),
of: firstAsset.tracks(withMediaType: AVMediaTypeVideo)[0],
at: kCMTimeZero)
} catch _ {
print("Failed to load first track")
}
var myTracks:[AVMutableCompositionTrack] = []
for asset in assets {
let secondTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
secondTrack.preferredTransform = asset.asset.preferredTransform
do {
try secondTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.endTime-asset.beginTime),
of: asset.asset.tracks(withMediaType: AVMediaTypeVideo)[0],
at: CMTime(seconds: CMTimeGetSeconds(asset.beginTime), preferredTimescale: 600000))
} catch _ {
print("Failed to load second track")
}
myTracks.append(secondTrack)
}
if let loadedAudioAsset = controller.audioAsset {
let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: 0)
do {
try audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, self.controller.realDuration),
of: loadedAudioAsset.tracks(withMediaType: AVMediaTypeAudio)[0] ,
at: kCMTimeZero)
} catch _ {
print("Failed to load Audio track")
}
}
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.controller.realDuration)
let firstInstruction = videoCompositionInstructionForTrack(firstTrack, firstAsset)
var instructions:[AVMutableVideoCompositionLayerInstruction] = []
var counter:Int = 0
for tracks in myTracks {
firstInstruction.setOpacity(0.0, at: assets[counter].beginTime)
let secondInstruction = videoCompositionInstructionForTrack(tracks, assets[counter].asset, type:true)
secondInstruction.setOpacity(0.0, at: assets[counter].endTime)
firstInstruction.setOpacity(1.0, at: assets[counter].endTime)
instructions.append(secondInstruction)
counter += 1
}
mainInstruction.layerInstructions = [firstInstruction] + instructions
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(1, 30)
mainComposition.renderSize = self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let savePath = (documentDirectory as NSString).appendingPathComponent("mergeVideo.mov")
let url = URL(fileURLWithPath: savePath)
_ = try? FileManager().removeItem(at: url)
guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
exporter.outputFileType = AVFileTypeMPEG4
exporter.outputURL = url
exporter.videoComposition = mainComposition
exporter.exportAsynchronously(completionHandler: {
DispatchQueue.main.async(execute: {
self.exportDidFinish(exporter)
self.removeAI()
completion()
})
})
}
}
func exportDidFinish(_ exporter:AVAssetExportSession) {
if(exporter.status == AVAssetExportSessionStatus.completed) {
print("cool")
}
else if(exporter.status == AVAssetExportSessionStatus.failed) {
print(exporter.error as Any)
}
}
func videoCompositionInstructionForTrack(_ track: AVCompositionTrack, _ asset: AVAsset, type:Bool = false) -> AVMutableVideoCompositionLayerInstruction {
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
let assetTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0]
var transform = assetTrack.preferredTransform
let assetInfo = orientationFromTransform(transform)
let width = self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width/assetTrack.naturalSize.width
var height = self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height
if assetInfo.isPortrait {
//Vert Video taken from camera -- vert video from lib
height = self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.width
transform = transform.scaledBy(x: height, y: height)
let movement = ((1/height)*assetTrack.naturalSize.height)-assetTrack.naturalSize.height
transform = transform.translatedBy(x: 0, y: movement)
let totalBlackDistance = self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width-transform.tx
transform = transform.translatedBy(x: 0, y: -(totalBlackDistance/2)*(1/height))
} else {
//Main Video -- hor photo from camera -- hor video from camera -- hor photo frmo lib -- hor vid frmo lib -- vert photos lib - vert photos camera
transform = transform.scaledBy(x: width, y: height)
let scale:CGFloat = ((self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height)*(assetTrack.naturalSize.width))/self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width
transform = transform.scaledBy(x: scale, y: 1)
let movement = ((self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width-((self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height)*(assetTrack.naturalSize.width)))/2)*(1/(self.controller.firstAsset!.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height))
transform = transform.translatedBy(x: movement, y: 0)
}
instruction.setTransform(transform, at: kCMTimeZero)
return instruction
}
func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) {
var assetOrientation = UIImageOrientation.up
var isPortrait = false
if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .right
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .left
isPortrait = true
} else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
assetOrientation = .up
} else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
assetOrientation = .down
}
return (assetOrientation, isPortrait)
}
}
Для моего телефона iPhone 6s это никогда не падает, и у меня никогда не возникает проблем.
Один из моих тестеров, у которого есть iPhone 5s, случайно вылетает во время процесса экспорта. Когда его телефон выходит из строя, он, кажется, на самом деле не столько ломается, сколько зависает. Приложение полностью закрывается и ничего не происходит. В окно моего Организатора не отправляется отчет о сбоях (это часто случается), и, похоже, никаких проблем не возникает. Еще одна проблема с тестированием это его приложение делает это на разных. Он не падает каждый раз, даже с теми же активами. Если бы я мог регулярно дублировать эту проблему с некоторой стандартизацией, у меня не было бы так много проблем. Но не совсем уверен, куда идти отсюда.
Этот тестер не расположен рядом со мной - я использую окно органайзера Apple для получения отчетов о сбоях
Потенциальные решения:
Память: я думал, что это может быть проблема с памятью, так как я знаю, что если память перегружена, это приведет к закрытию приложения. Тем не менее, ничего не отправлено в «DidReceiveMemoryWarning», приложение просто завершается.
Есть предложения / решения? Вся помощь приветствуется.