From dded56d8b8a9718ffd4826bb80e3a7861f638593 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:23:46 +0700 Subject: [PATCH] ios: converting video to mp4 and making quality lower (#3597) * ios: converting video to mp4 and making quality lower Lower quality allows to play that videos on Android as well * update export settings --------- Co-authored-by: Avently Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../Chat/ComposeMessage/ComposeView.swift | 13 +++++++-- .../Shared/Views/Helpers/ImagePicker.swift | 28 ++++++++++++------- .../ios/Shared/Views/Helpers/VideoUtils.swift | 26 +++++++++++++++++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 +++ 4 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/VideoUtils.swift diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 4001edffb..d089c7d6f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -104,7 +104,7 @@ struct ComposeState { var sendEnabled: Bool { switch preview { - case .mediaPreviews: return true + case let .mediaPreviews(media): return !media.isEmpty case .voicePreview: return voiceMessageRecordingState == .finished case .filePreview: return true default: return !message.isEmpty || liveMessage != nil @@ -384,7 +384,7 @@ struct ComposeView: View { } } .sheet(isPresented: $showMediaPicker) { - LibraryMediaListPicker(addMedia: addMediaContent, selectionLimit: 10) { itemsSelected in + LibraryMediaListPicker(addMedia: addMediaContent, selectionLimit: 10, finishedPreprocessing: finishedPreprocessingMediaContent) { itemsSelected in await MainActor.run { showMediaPicker = false if itemsSelected { @@ -503,6 +503,15 @@ struct ComposeView: View { } } + // When error occurs while converting video, remove media preview + private func finishedPreprocessingMediaContent() { + if case let .mediaPreviews(media) = composeState.preview, media.isEmpty { + DispatchQueue.main.async { + composeState = composeState.copy(preview: .noPreview) + } + } + } + private var maxFileSize: Int64 { getMaxFileSize(.xftp) } diff --git a/apps/ios/Shared/Views/Helpers/ImagePicker.swift b/apps/ios/Shared/Views/Helpers/ImagePicker.swift index efd42ee4b..fe8d5bbdd 100644 --- a/apps/ios/Shared/Views/Helpers/ImagePicker.swift +++ b/apps/ios/Shared/Views/Helpers/ImagePicker.swift @@ -33,6 +33,7 @@ struct LibraryMediaListPicker: UIViewControllerRepresentable { typealias UIViewControllerType = PHPickerViewController var addMedia: (_ content: UploadContent) async -> Void var selectionLimit: Int + var finishedPreprocessing: () -> Void = {} var didFinishPicking: (_ didSelectItems: Bool) async -> Void class Coordinator: PHPickerViewControllerDelegate { @@ -50,6 +51,7 @@ struct LibraryMediaListPicker: UIViewControllerRepresentable { for r in results { await loadItem(r.itemProvider) } + parent.finishedPreprocessing() } } @@ -103,21 +105,27 @@ struct LibraryMediaListPicker: UIViewControllerRepresentable { await withCheckedContinuation { cont in loadFileURL(p, type: UTType.movie) { url in if let url = url { - let tempUrl = URL(fileURLWithPath: generateNewFileName(getTempFilesDirectory().path + "/" + "video", url.pathExtension, fullPath: true)) + let tempUrl = URL(fileURLWithPath: generateNewFileName(getTempFilesDirectory().path + "/" + "rawvideo", url.pathExtension, fullPath: true)) + let convertedVideoUrl = URL(fileURLWithPath: generateNewFileName(getTempFilesDirectory().path + "/" + "video", "mp4", fullPath: true)) do { -// logger.debug("LibraryMediaListPicker copyItem \(url) to \(tempUrl)") +// logger.debug("LibraryMediaListPicker copyItem \(url) to \(tempUrl)") try FileManager.default.copyItem(at: url, to: tempUrl) - DispatchQueue.main.async { - _ = ChatModel.shared.filesToDelete.insert(tempUrl) - } - let video = UploadContent.loadVideoFromURL(url: tempUrl) - cont.resume(returning: video) - return } catch let err { logger.error("LibraryMediaListPicker copyItem error: \(err.localizedDescription)") + return cont.resume(returning: nil) + } + Task { + let success = await makeVideoQualityLower(tempUrl, outputUrl: convertedVideoUrl) + try? FileManager.default.removeItem(at: tempUrl) + if success { + _ = ChatModel.shared.filesToDelete.insert(convertedVideoUrl) + let video = UploadContent.loadVideoFromURL(url: convertedVideoUrl) + return cont.resume(returning: video) + } + try? FileManager.default.removeItem(at: convertedVideoUrl) + cont.resume(returning: nil) } } - cont.resume(returning: nil) } } } @@ -143,7 +151,7 @@ struct LibraryMediaListPicker: UIViewControllerRepresentable { config.filter = .any(of: [.images, .videos]) config.selectionLimit = selectionLimit config.selection = .ordered - //config.preferredAssetRepresentationMode = .current + config.preferredAssetRepresentationMode = .current let controller = PHPickerViewController(configuration: config) controller.delegate = context.coordinator return controller diff --git a/apps/ios/Shared/Views/Helpers/VideoUtils.swift b/apps/ios/Shared/Views/Helpers/VideoUtils.swift new file mode 100644 index 000000000..e13893de6 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/VideoUtils.swift @@ -0,0 +1,26 @@ +// +// VideoUtils.swift +// SimpleX (iOS) +// +// Created by Avently on 25.12.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import AVFoundation +import Foundation +import SimpleXChat + +func makeVideoQualityLower(_ input: URL, outputUrl: URL) async -> Bool { + let asset: AVURLAsset = AVURLAsset(url: input, options: nil) + if let s = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset640x480) { + s.outputURL = outputUrl + s.outputFileType = .mp4 + s.metadataItemFilter = AVMetadataItemFilter.forSharing() + await s.export() + if let err = s.error { + logger.error("Failed to export video with error: \(err)") + } + return s.status == .completed + } + return false +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 1a512c6f3..1e54bf353 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -188,6 +188,7 @@ 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; }; 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; }; 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; }; + 8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C05382D2B39887E006436DC /* VideoUtils.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; }; D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547729AF89AF0022400A /* StoreKit.framework */; }; @@ -476,6 +477,7 @@ 64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = ""; }; 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = ""; }; + 8C05382D2B39887E006436DC /* VideoUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoUtils.swift; sourceTree = ""; }; D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = ""; }; D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -642,6 +644,7 @@ 64466DCB29FFE3E800E3D48D /* MailView.swift */, 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */, 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */, + 8C05382D2B39887E006436DC /* VideoUtils.swift */, ); path = Helpers; sourceTree = ""; @@ -1212,6 +1215,7 @@ 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */, 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, + 8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */, 5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */, 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */, 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,