Я заинтересован в разработке приложения, которое может накладывать стикеры / тексты поверх видео в течение определенного периода времени.В идеале, пользовательский интерфейс должен быть достаточно простым, чтобы пользователь мог легко перетаскивать стикер и легко изменять длительность.
Теперь я только начал изучать AVFoundation / AVPlayer.Как лучше всего подойти к этому?Вот мой прототип кода с использованием AVMutableVideoComposition:
Мой первоначальный план - использовать AVMutableVideoComposition.Но некоторые из моих опасений заключаются в следующем: будет ли рендеринг достаточно быстрым, чтобы каждый раз, когда пользователь перетаскивал стикер, он мог легко вносить изменения (положение / длина)?
// Add sticker by create a CALayer instance and add it afterward
func addSticker(_ sticker: Int, at position: Int) {
addStickerView?.removeFromSuperview()
// Create an AVMutableComposition for editing
let mutableComposition = getVideoComposition()
// Create a CALayer instance and configurate it
let stickerLayer = CALayer()
stickerLayer.contents = UIImage(named: "sticker\(sticker)")?.cgImage
stickerLayer.contentsGravity = CALayerContentsGravity.resizeAspect
stickerLayer.opacity = 0
// fade in animation
let animation = CABasicAnimation(keyPath: "opacity")
animation.delegate = self
animation.fillMode = .removed
animation.isRemovedOnCompletion = true
animation.duration = 1
animation.fromValue = CGFloat(0.0)
animation.toValue = CGFloat(1.0)
animation.beginTime = AVCoreAnimationBeginTimeAtZero
stickerLayer.add(animation, forKey: "opacity")
let videoSize = videoAsset.tracks(withMediaType: AVMediaType.video)[0].naturalSize
let videoWidth = videoSize.width
let videoHeight = videoSize.height
let stickerWidth = videoWidth/6
let stickerX = videoWidth * CGFloat(5 * (position % 3)) / 12
let stickerY = videoHeight * CGFloat(position / 3) / 3
stickerLayer.frame = CGRect(x: stickerX, y: stickerY, width: stickerWidth, height: stickerWidth)
// Further export the video with the layer
exportWithAddedLayer(mutableComposition, with: stickerLayer, doneEditing: false)
}
}
func exportWithAddedLayer(_ mutableComposition: AVMutableComposition, with addedLayer: CALayer, doneEditing: Bool) {
self.view.addSubview(processingLabel)
processingLabel.frame = self.toolsTableView.frame
let videoTrack: AVAssetTrack = mutableComposition.tracks(withMediaType: AVMediaType.video)[0]
let videoSize = videoTrack.naturalSize
let videoLayer = CALayer()
videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
let containerLayer = CALayer()
containerLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
containerLayer.addSublayer(videoLayer)
containerLayer.addSublayer(addedLayer)
let layerComposition = AVMutableVideoComposition()
layerComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
layerComposition.renderSize = videoSize
layerComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: containerLayer)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: mutableComposition.duration)
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
instruction.layerInstructions = [layerInstruction]
layerComposition.instructions = [instruction]
let exportUrl = generateExportUrl()
// Set up exporter
guard let exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
exporter.videoComposition = layerComposition
exporter.outputURL = exportUrl as URL
exporter.outputFileType = AVFileType.mov
exporter.exportAsynchronously() {
DispatchQueue.main.async {
self.exportDidComplete(exportURL: exporter.outputURL!, doneEditing: doneEditing)
}
}
}