Various fixes

This commit is contained in:
Ilya Laktyushin 2023-07-02 00:03:00 +02:00
parent e5e5bc1eac
commit d10bf46443
11 changed files with 146 additions and 46 deletions

View File

@ -64,6 +64,8 @@ final class CameraDeviceContext {
self.device.configureDeviceFormat(maxDimensions: self.preferredMaxDimensions, maxFramerate: self.preferredMaxFrameRate)
self.output.configureVideoStabilization()
self.device.resetZoom()
}
func invalidate() {
@ -210,7 +212,7 @@ private final class CameraContext {
func stopCapture(invalidate: Bool = false) {
if invalidate {
self.setZoomLevel(1.0)
self.mainDeviceContext.device.resetZoom()
self.configure {
self.mainDeviceContext.invalidate()

View File

@ -31,10 +31,23 @@ final class CameraDevice {
func configure(for session: CameraSession, position: Camera.Position) {
self.position = position
if let videoDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first {
self.videoDevice = videoDevice
self.videoDevicePromise.set(.single(videoDevice))
var selectedDevice: AVCaptureDevice?
if #available(iOS 13.0, *) {
if let device = AVCaptureDevice.default(.builtInTripleCamera, for: .video, position: position) {
selectedDevice = device
} else if let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: position) {
selectedDevice = device
} else {
selectedDevice = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: position)
}
} else {
selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first
}
self.videoDevice = selectedDevice
self.videoDevicePromise.set(.single(selectedDevice))
self.audioDevice = AVCaptureDevice.default(for: .audio)
}
@ -229,4 +242,13 @@ final class CameraDevice {
device.videoZoomFactor = max(1.0, min(10.0, device.videoZoomFactor * zoomDelta))
}
}
func resetZoom() {
guard let device = self.videoDevice else {
return
}
self.transaction(device) { device in
device.videoZoomFactor = device.neutralZoomFactor
}
}
}

View File

@ -43,6 +43,16 @@ extension AVCaptureDevice {
return nil
}
var neutralZoomFactor: CGFloat {
if #available(iOS 13.0, *) {
if let indexOfWideAngle = self.constituentDevices.firstIndex(where: { $0.deviceType == .builtInWideAngleCamera }), indexOfWideAngle > 0 {
let zoomFactor = self.virtualDeviceSwitchOverVideoZoomFactors[indexOfWideAngle - 1]
return CGFloat(zoomFactor.doubleValue)
}
}
return 1.0
}
}
extension CMSampleBuffer {

View File

@ -162,7 +162,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.previousEntity = previousEntity
}
self.update(animated: false)
self.update(animated: false, updateEditingPosition: false)
if let superview = self.superview {
let fadeView = UIButton(frame: CGRect(origin: .zero, size: superview.frame.size))
@ -181,15 +181,8 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.textView.window?.makeKey()
self.textView.becomeFirstResponder()
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0) {
if let parentView = self.superview as? DrawingEntitiesView {
let scale = parentView.getEntityAdditionalScale() / (parentView.drawingView?.zoomScale ?? 1.0)
self.transform = CGAffineTransformMakeRotation(parentView.getEntityInitialRotation()).scaledBy(x: scale, y: scale)
self.center = parentView.getEntityCenterPosition()
}
}
self.updateEditingPosition(animated: true)
if let selectionView = self.selectionView as? DrawingTextEntititySelectionView {
selectionView.alpha = 0.0
if !self.textEntity.text.string.isEmpty {
@ -198,7 +191,42 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
}
}
func updateEditingPosition(animated: Bool) {
guard let parentView = self.superview as? DrawingEntitiesView else {
return
}
var position = parentView.getEntityCenterPosition()
if parentView.frame.width == 1080 && parentView.frame.height == 1920 {
let width = self.bounds.width
switch self.textEntity.alignment {
case .left:
position = CGPoint(x: 80.0 + width / 2.0, y: position.y)
case .right:
position = CGPoint(x: parentView.bounds.width - 80.0 - width / 2.0, y: position.y)
default:
break
}
}
let scale = parentView.getEntityAdditionalScale() / (parentView.drawingView?.zoomScale ?? 1.0)
let rotation = parentView.getEntityInitialRotation()
if animated {
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0) {
self.transform = CGAffineTransformMakeRotation(rotation).scaledBy(x: scale, y: scale)
self.center = position
}
} else {
self.transform = CGAffineTransformMakeRotation(rotation).scaledBy(x: scale, y: scale)
self.center = position
}
}
func endEditing(reset: Bool = false) {
guard let parentView = self.superview as? DrawingEntitiesView else {
return
}
self._isEditing = false
self.textView.resignFirstResponder()
self.textView.inputView = nil
@ -220,7 +248,6 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.containerView?.remove(uuid: self.textEntity.uuid)
}
} else {
// self.textEntity.text = self.textView.text.trimmingCharacters(in: .whitespacesAndNewlines)
if self.textEntity.text.string.isEmpty {
self.containerView?.remove(uuid: self.textEntity.uuid)
}
@ -233,6 +260,18 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
})
}
if self.previousEntity == nil && self.textEntity.alignment != .center, parentView.frame.width == 1080 && parentView.frame.height == 1920 {
let width = self.bounds.width
switch self.textEntity.alignment {
case .left:
self.textEntity.position = CGPoint(x: 80.0 + width / 2.0, y: self.textEntity.position.y)
case .right:
self.textEntity.position = CGPoint(x: parentView.bounds.width - 80.0 - width / 2.0, y: self.textEntity.position.y)
default:
break
}
}
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0) {
self.transform = CGAffineTransformMakeRotation(self.textEntity.rotation)
self.center = self.textEntity.position
@ -305,7 +344,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.textView.setNeedsLayersUpdate()
var result = self.textView.sizeThatFits(CGSize(width: self.textEntity.width, height: .greatestFiniteMagnitude))
result.width = max(224.0, ceil(result.width) + 20.0)
result.height = ceil(result.height) //+ 20.0 + (self.textView.font?.pointSize ?? 0.0) // * _font.sizeCorrection;
result.height = ceil(result.height);
return result;
}
@ -507,10 +546,10 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
}
public override func update(animated: Bool = false) {
self.update(animated: animated, afterAppendingEmoji: false)
self.update(animated: animated, afterAppendingEmoji: false, updateEditingPosition: true)
}
func update(animated: Bool = false, afterAppendingEmoji: Bool = false) {
func update(animated: Bool = false, afterAppendingEmoji: Bool = false, updateEditingPosition: Bool = true) {
if !self.isEditing {
self.center = self.textEntity.position
self.transform = CGAffineTransformScale(CGAffineTransformMakeRotation(self.textEntity.rotation), self.textEntity.scale, self.textEntity.scale)
@ -555,12 +594,13 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
self.sizeToFit()
if updateEditingPosition && self.isEditing {
self.updateEditingPosition(animated: animated)
}
self.textView.onLayoutUpdate = {
self.updateEntities()
}
// Queue.mainQueue().after(afterAppendingEmoji ? 0.01 : 0.001) {
// self.updateEntities()
// }
super.update(animated: animated)
}

View File

@ -526,7 +526,8 @@ final class MediaPickerGridItemNode: GridItemNode {
override func layout() {
super.layout()
self.backgroundNode.frame = self.bounds
let backgroundSize = CGSize(width: self.bounds.width, height: floorToScreenPixels(self.bounds.height / 9.0 * 16.0))
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.bounds.height - backgroundSize.height) / 2.0)), size: backgroundSize)
self.imageNode.frame = self.bounds.insetBy(dx: -1.0 + UIScreenPixel, dy: -1.0 + UIScreenPixel)
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 36.0, width: self.bounds.width, height: 36.0)
self.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
@ -563,7 +564,8 @@ final class MediaPickerGridItemNode: GridItemNode {
func transitionImage() -> UIImage? {
if let backgroundImage = self.backgroundNode.image {
return generateImage(self.bounds.size, contextGenerator: { size, context in
let size = CGSize(width: self.bounds.width, height: self.bounds.height / 9.0 * 16.0)
return generateImage(size, contextGenerator: { size, context in
if let cgImage = backgroundImage.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size))
if let image = self.imageNode.image, let cgImage = image.cgImage {

View File

@ -1219,7 +1219,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
var itemHeight = itemWidth
if case let .assets(_, mode) = controller.subject, case .story = mode {
itemHeight = round(itemWidth / 9.0 * 16.0)
itemHeight = 180.0
}
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: itemHeight * 3.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemHeight), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in

View File

@ -1129,14 +1129,24 @@ private let hasHEVCHardwareEncoder: Bool = {
return result == noErr
}()
public func recommendedVideoExportConfiguration(values: MediaEditorValues, forceFullHd: Bool = false, frameRate: Float) -> MediaEditorVideoExport.Configuration {
public func recommendedVideoExportConfiguration(values: MediaEditorValues, duration: Double, image: Bool = false, forceFullHd: Bool = false, frameRate: Float) -> MediaEditorVideoExport.Configuration {
let compressionProperties: [String: Any]
let codecType: AVVideoCodecType
if hasHEVCHardwareEncoder {
var bitrate: Int = 3700
if image {
bitrate = 5000
} else {
if duration < 10 {
bitrate = 5500
} else if duration < 25 {
bitrate = 4500
}
}
codecType = AVVideoCodecType.hevc
compressionProperties = [
AVVideoAverageBitRateKey: 3800000,
AVVideoAverageBitRateKey: bitrate * 1000,
AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel
]
} else {

View File

@ -2164,6 +2164,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.previewContainerView.alpha = 1.0
let transitionInView = UIImageView(image: image)
transitionInView.contentMode = .scaleAspectFill
var initialScale: CGFloat
if image.size.height > image.size.width {
initialScale = max(self.previewContainerView.bounds.width / image.size.width, self.previewContainerView.bounds.height / image.size.height)
@ -3762,7 +3763,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard let self else {
return
}
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, forceFullHd: true, frameRate: 60.0)
var duration: Double = 0.0
if case let .video(video) = exportSubject {
duration = video.duration.seconds
}
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0)
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
let videoExport = MediaEditorVideoExport(account: self.context.account, subject: exportSubject, configuration: configuration, outputPath: outputPath)
self.videoExport = videoExport

View File

@ -836,7 +836,10 @@ public final class MessageInputPanelComponent: Component {
} else {
inputActionButtonOriginX = size.width
}
transition.setFrame(view: inputActionButtonView, frame: CGRect(origin: CGPoint(x: inputActionButtonOriginX, y: size.height - insets.bottom - baseFieldHeight + floorToScreenPixels((baseFieldHeight - inputActionButtonSize.height) * 0.5)), size: inputActionButtonSize))
let inputActionButtonFrame = CGRect(origin: CGPoint(x: inputActionButtonOriginX, y: size.height - insets.bottom - baseFieldHeight + floor((baseFieldHeight - inputActionButtonSize.height) * 0.5)), size: inputActionButtonSize)
transition.setPosition(view: inputActionButtonView, position: inputActionButtonFrame.center)
transition.setBounds(view: inputActionButtonView, bounds: CGRect(origin: CGPoint(), size: inputActionButtonFrame.size))
}
var fieldIconNextX = fieldBackgroundFrame.maxX - 4.0

View File

@ -206,8 +206,11 @@ private final class StoryContainerScreenComponent: Component {
guard let self, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return []
}
if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) {
return []
if let environment = self.environment, case .regular = environment.metrics.widthClass {
} else {
if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) {
return []
}
}
if !itemSetComponentView.allowsInteractiveGestures() {
return []
@ -220,8 +223,11 @@ private final class StoryContainerScreenComponent: Component {
guard let self, let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return []
}
if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) {
return []
if let environment = self.environment, case .regular = environment.metrics.widthClass {
} else {
if !itemSetComponentView.isPointInsideContentArea(point: self.convert(point, to: itemSetComponentView)) {
return []
}
}
if !itemSetComponentView.allowsInteractiveGestures() {
return []
@ -262,8 +268,12 @@ private final class StoryContainerScreenComponent: Component {
return false
}
if !itemSetComponentView.isPointInsideContentArea(point: touch.location(in: itemSetComponentView)) {
return false
if let environment = self.environment, case .regular = environment.metrics.widthClass {
} else {
if !itemSetComponentView.isPointInsideContentArea(point: touch.location(in: itemSetComponentView)) {
return false
}
}
return true
@ -481,18 +491,12 @@ private final class StoryContainerScreenComponent: Component {
let location = recognizer.location(in: recognizer.view)
if let currentItemView = self.visibleItemSetViews.first?.value {
if location.x < currentItemView.frame.minX {
if stateValue.previousSlice == nil {
} else {
self.beginHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: 100.0, y: 0.0))
}
component.content.navigate(navigation: .item(.previous))
} else if location.x > currentItemView.frame.maxX {
if stateValue.nextSlice == nil {
environment.controller()?.dismiss()
} else {
self.beginHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: -100.0, y: 0.0))
component.content.navigate(navigation: .item(.next))
}
}
}

View File

@ -247,7 +247,7 @@ public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibr
if let mediaEditorValues {
Logger.shared.log("FetchVideoResource", "Requesting video export")
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, frameRate: 30.0)
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, duration: 5.0, image: true, frameRate: 30.0)
let videoExport = MediaEditorVideoExport(account: account, subject: .image(image), configuration: configuration, outputPath: tempFile.path)
videoExport.start()
@ -337,7 +337,8 @@ public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibr
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
let updatedSize = Atomic<Int64>(value: 0)
if let mediaEditorValues {
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, frameRate: 30.0)
let duration: Double = avAsset.duration.seconds
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, duration: duration, frameRate: 30.0)
let videoExport = MediaEditorVideoExport(account: account, subject: .video(avAsset), configuration: configuration, outputPath: tempFile.path)
videoExport.start()
@ -487,7 +488,8 @@ func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideo
let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4")
let updatedSize = Atomic<Int64>(value: 0)
if let mediaEditorValues {
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, frameRate: 30.0)
let duration: Double = avAsset.duration.seconds
let configuration = recommendedVideoExportConfiguration(values: mediaEditorValues, duration: duration, frameRate: 30.0)
let subject: MediaEditorVideoExport.Subject
if filteredPath.contains(".jpg"), let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) {
subject = .image(image)