mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Video message recording improvements
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "VideoMessageCameraScreen",
|
||||
module_name = "VideoMessageCameraScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/MediaResources",
|
||||
"//submodules/LocalMediaResources",
|
||||
"//submodules/ImageCompression",
|
||||
"//submodules/Camera",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/CameraButtonComponent",
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/DeviceAccess",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
|
||||
private extension SimpleShapeLayer {
|
||||
func animateStrokeStart(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "strokeStart", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
|
||||
}
|
||||
|
||||
func animateStrokeEnd(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "strokeEnd", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
final class RecordingProgressView: UIView {
|
||||
let shapeLayer = SimpleShapeLayer()
|
||||
|
||||
var value: CGFloat = 0.0 {
|
||||
didSet {
|
||||
if abs(self.shapeLayer.strokeEnd - self.value) >= 0.01 {
|
||||
if abs(oldValue - self.value) < 0.1 {
|
||||
let previousStrokeEnd = self.shapeLayer.strokeEnd
|
||||
self.shapeLayer.strokeEnd = self.value
|
||||
self.shapeLayer.animateStrokeEnd(from: previousStrokeEnd, to: self.shapeLayer.strokeEnd, duration: abs(previousStrokeEnd - self.value) * 60.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue)
|
||||
} else {
|
||||
self.shapeLayer.strokeEnd = self.value
|
||||
self.shapeLayer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.shapeLayer.fillColor = UIColor.clear.cgColor
|
||||
self.shapeLayer.strokeColor = UIColor(white: 1.0, alpha: 0.6).cgColor
|
||||
self.shapeLayer.lineWidth = 4.0
|
||||
self.shapeLayer.lineCap = .round
|
||||
self.shapeLayer.transform = CATransform3DMakeRotation(-.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
self.shapeLayer.strokeEnd = 0.0
|
||||
|
||||
self.layer.addSublayer(self.shapeLayer)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if self.shapeLayer.frame != self.bounds {
|
||||
self.shapeLayer.frame = self.bounds
|
||||
|
||||
self.shapeLayer.path = CGPath(ellipseIn: self.bounds.insetBy(dx: self.shapeLayer.lineWidth, dy: self.shapeLayer.lineWidth), transform: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user