2024-01-12 18:17:14 +04:00

129 lines
3.8 KiB
Swift

import Foundation
import UIKit
import AVFoundation
final class ResultPreviewView: UIView {
let composition: AVComposition
let player: AVPlayer
let playerLayer: AVPlayerLayer
var didPlayToEndTimeObserver: NSObjectProtocol?
var trimRange: Range<Double>? {
didSet {
if let trimRange = self.trimRange {
self.player.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000))
} else {
self.player.currentItem?.forwardPlaybackEndTime = .invalid
}
}
}
var onLoop: () -> Void = {}
var isMuted = true {
didSet {
self.player.isMuted = self.isMuted
}
}
init(composition: AVComposition) {
self.composition = composition
self.player = AVPlayer(playerItem: AVPlayerItem(asset: composition))
self.player.isMuted = true
self.playerLayer = AVPlayerLayer(player: self.player)
super.init(frame: .zero)
self.layer.addSublayer(self.playerLayer)
self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: nil, using: { [weak self] notification in
guard let self else {
return
}
var start: Double = 0.0
if let trimRange = self.trimRange {
start = trimRange.lowerBound
}
self.player.pause()
self.seek(to: start, andPlay: true)
self.onLoop()
})
self.player.play()
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver {
NotificationCenter.default.removeObserver(didPlayToEndTimeObserver)
}
}
func updateTrimRange(start: Double, end: Double, updatedEnd: Bool, apply: Bool) {
if !apply {
self.player.pause()
} else {
self.trimRange = start..<end
}
let seekTo: Double
if updatedEnd && !apply {
seekTo = end
} else {
seekTo = start
}
self.seek(to: seekTo, andPlay: apply)
}
func play() {
self.player.play()
}
func pause() {
self.player.pause()
}
private var targetTimePosition: (CMTime, Bool)?
private var updatingTimePosition = false
func seek(to seconds: Double, andPlay play: Bool) {
let position = CMTime(seconds: seconds, preferredTimescale: CMTimeScale(1000.0))
self.targetTimePosition = (position, play)
if !self.updatingTimePosition {
self.updateVideoTimePosition()
}
}
private func updateVideoTimePosition() {
guard let (targetPosition, _) = self.targetTimePosition else {
return
}
self.updatingTimePosition = true
self.player.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: { [weak self] _ in
if let self {
if let (currentTargetPosition, play) = self.targetTimePosition, currentTargetPosition == targetPosition {
self.updatingTimePosition = false
self.targetTimePosition = nil
if play {
self.player.play()
}
} else {
self.updateVideoTimePosition()
}
}
})
}
override func layoutSubviews() {
self.playerLayer.frame = self.bounds
}
}