Commit 65b05c9e authored by shigemi miura's avatar shigemi miura

動画処理が出来ないバージョン

parent 8cec0939
......@@ -149,6 +149,7 @@
D5E008762B2ADD5900C4070A /* MenuManualRADARView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E008752B2ADD5900C4070A /* MenuManualRADARView.swift */; };
D5E008782B2B022200C4070A /* MenuAboutAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E008772B2B022200C4070A /* MenuAboutAppView.swift */; };
D5E03A672B04484D00D65FCE /* SessionTaskList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E03A662B04484D00D65FCE /* SessionTaskList.swift */; };
D5F969BD2E8AAD4F005662B0 /* VideoQualityPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F969BC2E8AAD3C005662B0 /* VideoQualityPreset.swift */; };
D5FCEF552B478985009A81D0 /* ResChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FCEF542B478985009A81D0 /* ResChatMessage.swift */; };
D5FCEF572B4789A8009A81D0 /* ResAckMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FCEF562B4789A8009A81D0 /* ResAckMessage.swift */; };
D5FCEF592B4789D2009A81D0 /* ResChatMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FCEF582B4789D2009A81D0 /* ResChatMode.swift */; };
......@@ -339,6 +340,7 @@
D5E008752B2ADD5900C4070A /* MenuManualRADARView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MenuManualRADARView.swift; path = Sailassist/Menu/View/MenuManualRADARView.swift; sourceTree = SOURCE_ROOT; };
D5E008772B2B022200C4070A /* MenuAboutAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MenuAboutAppView.swift; path = Sailassist/Menu/View/MenuAboutAppView.swift; sourceTree = SOURCE_ROOT; };
D5E03A662B04484D00D65FCE /* SessionTaskList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionTaskList.swift; path = Sailassist/ServerSession/SessionTaskList.swift; sourceTree = SOURCE_ROOT; };
D5F969BC2E8AAD3C005662B0 /* VideoQualityPreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoQualityPreset.swift; sourceTree = "<group>"; };
D5FCEF542B478985009A81D0 /* ResChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ResChatMessage.swift; path = Sailassist/Json/ResChatMessage.swift; sourceTree = SOURCE_ROOT; };
D5FCEF562B4789A8009A81D0 /* ResAckMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ResAckMessage.swift; path = Sailassist/Json/ResAckMessage.swift; sourceTree = SOURCE_ROOT; };
D5FCEF582B4789D2009A81D0 /* ResChatMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ResChatMode.swift; path = Sailassist/Json/ResChatMode.swift; sourceTree = SOURCE_ROOT; };
......@@ -541,6 +543,7 @@
D5AF8A4B2E88CDF600BECA22 /* ViewModel */,
D5258CA42B036F0700365276 /* GetMessage.swift */,
D55186122E7E914C004CD8BD /* ImageQualityPreset.swift */,
D5F969BC2E8AAD3C005662B0 /* VideoQualityPreset.swift */,
D524DAF72B5A6D3600A399DD /* Imagepicker.swift */,
);
path = Chat;
......@@ -665,8 +668,6 @@
children = (
020B98622ADD14E40029DE4C /* ChatView.swift */,
02A1DE2E2AFB4AA0005BCF55 /* ChatInputView.swift */,
D57405052E827C35001C74DF /* ChatUrlVideoView.swift */,
D5A4C2522E7AB46B00642D7D /* SpeechRecognizer.swift */,
02C3E5D02AFCC16800AF7837 /* ChatTitleView.swift */,
02A1DE302AFB61D8005BCF55 /* MyChatContentView.swift */,
02C3E5CD2AFCA04700AF7837 /* OtherChatContentView.swift */,
......@@ -674,7 +675,9 @@
02C3E6082AFDF30000AF7837 /* ChatMemberView.swift */,
D524DAF92B5A6F5F00A399DD /* CameraView.swift */,
D5598B772C435A5C00611AE0 /* ChatUrlImageView.swift */,
D57405052E827C35001C74DF /* ChatUrlVideoView.swift */,
D5598B792C435C4500611AE0 /* ChatUrlRawImageView.swift */,
D5A4C2522E7AB46B00642D7D /* SpeechRecognizer.swift */,
);
path = View;
sourceTree = "<group>";
......@@ -1148,6 +1151,7 @@
020B98512AD9189A0029DE4C /* InputIdPassWordView.swift in Sources */,
D52C2C0A2B91BF24003B286C /* InformationView.swift in Sources */,
D52D213A2AEBAC0500324D58 /* HttpRequestType.swift in Sources */,
D5F969BD2E8AAD4F005662B0 /* VideoQualityPreset.swift in Sources */,
D53B97262B341867000B3D29 /* AboutAppView.swift in Sources */,
D5258CA12B03593500365276 /* SessionMonitoringRoute.swift in Sources */,
D5E03A672B04484D00D65FCE /* SessionTaskList.swift in Sources */,
......
//
// Imagepicker.swift
// Sailassist
//
// Created by 三浦薫巳 on 2024/01/19.
//
import SwiftUI
import AVFoundation
struct Imagepicker : UIViewControllerRepresentable {
@Binding var show: Bool
......@@ -30,6 +25,7 @@ struct Imagepicker : UIViewControllerRepresentable {
class Coordinator: NSObject,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
var parent : Imagepicker
var selectedPreset : VideoQualityPreset = .medium
init(parent : Imagepicker){
self.parent = parent
......@@ -40,7 +36,7 @@ struct Imagepicker : UIViewControllerRepresentable {
self.parent.show.toggle()
}
//MARK: - Use Photo
//MARK: - Use Photo / Movie
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
//MARK: - 画像データのリサイズ
......@@ -55,29 +51,26 @@ struct Imagepicker : UIViewControllerRepresentable {
}
} else {
DispatchQueue.main.async {
print("Failed to resize or compress image")
print(debug: "Failed to resize or compress image")
self.parent.show.toggle()
}
}
}
} else if let videoURL = info[.mediaURL] as? URL {
//MARK: - 動画データのリサイズ
DispatchQueue.global(qos: .userInitiated).async {
if let videoData = try? Data(contentsOf: videoURL) {
DispatchQueue.main.async {
self.parent.image = videoData
self.parent.show.toggle()
}
} else {
DispatchQueue.main.async {
print("Failed to process video data")
self.parent.show.toggle()
compressVideo(inputURL: videoURL, preset: selectedPreset) { compressedData in
DispatchQueue.main.async {
if let data = compressedData {
self.parent.image = data
} else {
print(debug: "Failed to compress video")
}
self.parent.show.toggle()
}
}
} else {
DispatchQueue.main.async {
print("No valid media selected")
print(debug: "No valid media selected")
self.parent.show.toggle()
}
}
......
import Network
import AVFoundation
enum VideoQualityPreset {
case low, medium, high
var exportPresetName: String {
switch self {
case .low: return AVAssetExportPresetLowQuality
case .medium: return AVAssetExportPresetMediumQuality
case .high: return AVAssetExportPresetHighestQuality
}
}
var fileExtension: String {
return "mp4"
}
var mimeType: String {
return "video/mp4"
}
}
func isVideoSizeAcceptable(_ data: Data, maxSizeMB: Double = 28.0) -> Bool {
let sizeMB = Double(data.count) / (1024.0 * 1024.0)
return sizeMB <= maxSizeMB
}
// MARK: - 動画圧縮処理
func compressVideo(inputURL: URL, preset: VideoQualityPreset, completion: @escaping (Data?) -> Void) {
let asset = AVAsset(url: inputURL)
guard let exportSession = AVAssetExportSession(asset: asset, presetName: preset.exportPresetName) else {
completion(nil)
return
}
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".mp4")
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously {
if exportSession.status == .completed {
if let data = try? Data(contentsOf: outputURL), isVideoSizeAcceptable(data) {
completion(data)
} else {
completion(nil) // サイズオーバー
}
} else {
completion(nil)
}
}
}
func selectPresetBasedOnNetwork() -> VideoQualityPreset {
let monitor = NWPathMonitor()
var selectedPreset: VideoQualityPreset = .medium
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
if path.isExpensive {
selectedPreset = .low // モバイル通信
} else {
selectedPreset = .high // Wi-Fi
}
} else {
selectedPreset = .low // オフラインまたは不安定
}
}
let queue = DispatchQueue(label: "NetworkMonitor")
monitor.start(queue: queue)
return selectedPreset
}
......@@ -4,7 +4,6 @@
//
// Created by 三浦薫巳 on 2024/01/19.
//
import SwiftUI
struct CameraView: View {
......
......@@ -136,6 +136,8 @@ struct ChatInputView: View {
if let image = UIImage(data: imageData) {
viewModel.sendImageToTemporary(image)
} else if !imageData.isEmpty {
viewModel.sendVideoToTemporary(imageData)
}
}) {
CameraView(imageData: $imageData, source: $source, isActionSheet: .constant(false), isImagePicker: $viewModel.isImagePickerPresented)
......@@ -206,6 +208,21 @@ struct ChatInputView: View {
} message: {
Text("Image upload failed. Would you like to try again?")
}
.alert("Upload failed", isPresented: $viewModel.isRetryDialogPresented) {
Button("retry") {
if let videoToRetry = viewModel.failedUploadImage {
viewModel.sendChatVideo(videoToRetry)
}
}
Button("Cancel", role: .cancel) {
viewModel.failedUploadImage = nil
viewModel.isRetryDialogPresented = false
SharingData.message.messages.removeAll { $0.messageId.caseInsensitiveCompare(viewModel.tempId) == .orderedSame }
}
} message: {
Text("Video upload failed. Would you like to try again?")
}
}
}
......
......@@ -6,67 +6,79 @@
//
import SwiftUI
import AVKit
struct MyChatContentView: View {
@State private var isVideoPlayerPresented = false
var message : ChatMessage
var onMediaLoaded: (() -> Void)? = nil
var body: some View {
HStack {
Spacer()
VStack(alignment: .trailing, spacing: 6) {
Group {
if let msg = message.message {
if msg.contains(".jpg") || msg.contains(".png") {
ChatUrlImageView(imageUrl: msg, onLoad: onMediaLoaded)
} else if msg.contains(".mp4") || msg.contains(".mov") {
ChatUrlVideoView(imageUrl: msg, onLoad: onMediaLoaded)
} else {
Text(msg)
.font(FontStyle.DefaultText.font)
.foregroundColor(ColorSet.BodyChat.color)
.padding(15)
.background(ColorSet.ChatBaloon.color)
.border(Color.red.gradient.opacity(0.8), width: (message.mode == ChatMode.warningProgress.rawValue ? 2 : 0))
.clipShape(
.rect(
topLeadingRadius: 10,
bottomLeadingRadius: 0,
bottomTrailingRadius: 10,
topTrailingRadius: 10
))
}
Spacer()
VStack(alignment: .trailing, spacing: 6) {
Group {
if let msg = message.message {
if msg.contains(".jpg") || msg.contains(".png") {
ChatUrlImageView(imageUrl: msg, onLoad: onMediaLoaded)
// .onTapGesture {
// if let videoUrl = message.videoUrl,
// let url = URL(string: videoUrl) {
// isVideoPlayerPresented = true
// }
// }
} else {
Text(msg)
.font(FontStyle.DefaultText.font)
.foregroundColor(ColorSet.BodyChat.color)
.padding(15)
.background(ColorSet.ChatBaloon.color)
.border(Color.red.gradient.opacity(0.8), width: (message.mode == ChatMode.warningProgress.rawValue ? 2 : 0))
.clipShape(
.rect(
topLeadingRadius: 10,
bottomLeadingRadius: 0,
bottomTrailingRadius: 10,
topTrailingRadius: 10
))
}
}
}
HStack(spacing: 5){
//MARK: - 既読マーク
Text(DateTextLib.ISO86012FormatText(message.time, format: "yyyy/MM/dd HH:mm", errFormat: ""))
.padding(.trailing, 8)
HStack(spacing: 5){
//MARK: - 既読マーク
Text(DateTextLib.ISO86012FormatText(message.time, format: "yyyy/MM/dd HH:mm", errFormat: ""))
.padding(.trailing, 8)
let viewerCnt = viewerCnt()
let viewerCnt = viewerCnt()
Image("chat_company")
.resizable()
.scaledToFit()
.frame(width: 12, height: 12)
.padding(.trailing, 4)
Image("chat_company")
.resizable()
.scaledToFit()
.frame(width: 12, height: 12)
.padding(.trailing, 4)
Text(String(viewerCnt.1))
Text(String(viewerCnt.1))
Image("chat_ship")
.resizable()
.scaledToFit()
.frame(width: 12, height: 12)
.padding(.trailing, 4)
Image("chat_ship")
.resizable()
.scaledToFit()
.frame(width: 12, height: 12)
.padding(.trailing, 4)
Text(String(viewerCnt.0))
}
.font(FontStyle.DateText.font)
.foregroundColor(ColorSet.ChatDate.color)
Text(String(viewerCnt.0))
}
.padding(.trailing, 20)
.font(FontStyle.DateText.font)
.foregroundColor(ColorSet.ChatDate.color)
}
.padding(.trailing, 20)
}
// .fullScreenCover(isPresented: $isVideoPlayerPresented) {
// if let videoUrl = message.videoUrl,
// let url = URL(string: videoUrl) {
// VideoPlayerView(videoURL: url)
// }
// }
}
func viewerCnt() -> (Int, Int) {
......@@ -83,6 +95,16 @@ struct MyChatContentView: View {
}
}
//struct VideoPlayerView: View {
// let videoURL: URL
//
// var body: some View {
// VideoPlayer(player: AVPlayer(url: videoURL))
// .navigationTitle("動画再生")
// .navigationBarTitleDisplayMode(.inline)
// }
//}
#Preview {
MyChatContentView(message: ChatMessage(
shipId: 10000003,
......
......@@ -4,10 +4,11 @@
//
// Created by Mamoru Sugita on 2023/11/09.
//
import SwiftUI
import AVKit
struct OtherChatContentView: View {
@State private var isVideoPlayerPresented = false
var message : ChatMessage
var onMediaLoaded: (() -> Void)? = nil
......@@ -24,8 +25,13 @@ struct OtherChatContentView: View {
if let msg = message.message {
if msg.contains(".jpg") || msg.contains(".png") {
ChatUrlImageView(imageUrl: msg, onLoad: onMediaLoaded)
} else if msg.contains(".mp4") || msg.contains(".mov") {
ChatUrlVideoView(imageUrl: msg, onLoad: onMediaLoaded)
// .onTapGesture {
// if message.type == 3,
// let videoUrl = message.videoUrl,
// let url = URL(string: videoUrl) {
// isVideoPlayerPresented = true
// }
// }
} else {
Text(msg)
.font(FontStyle.DefaultText.font)
......@@ -54,6 +60,12 @@ struct OtherChatContentView: View {
.padding(.leading, 20)
Spacer()
}
// .fullScreenCover(isPresented: $isVideoPlayerPresented) {
// if let videoUrl = message.videoUrl,
// let url = URL(string: videoUrl) {
// VideoPlayerView(videoURL: url)
// }
// }
}
}
......
import SwiftUI
import Combine
import Speech
import AVFoundation
import UIKit
enum MediaInputType {
case none
......@@ -38,30 +40,6 @@ class ChatInputViewModel: ObservableObject {
}
}
// MARK: - Send Chat Image
func sendChatImage(_ uploadImage: ReqUploadImage) {
Task {
do {
isUploadingDialogPresented = true
let response = try await sessionUploadImage.requestUploadImage(uploadImage)
print(debug: "Upload success: \(response)")
let serverSession = ServerSession()
_ = serverSession.fromJSON(resultData: response, resltType: ResLogin.self)
sessionUploadImage.progress = 0.0 // 完了後リセット
isUploadingDialogPresented = false
} catch {
print(debug: "Upload failed: \(error)")
sessionUploadImage.progress = 0.0 // エラー時もリセット
isUploadingDialogPresented = false
failedUploadImage = uploadImage
isRetryDialogPresented = true
}
}
}
// MARK: - Send Chat Message
func sendChatMessage() {
guard !SharingData.message.sendInf else { return }
......@@ -109,14 +87,62 @@ class ChatInputViewModel: ObservableObject {
textViewHeight = max(newSize.height, 40) // 最低高さを40に設定
}
// MARK: - ローカル画像を一時保存してURLを取得
// MARK: - Send Chat Image
func sendChatImage(_ uploadImage: ReqUploadImage) {
Task {
do {
isUploadingDialogPresented = true
let response = try await sessionUploadImage.requestUploadImage(uploadImage)
print(debug: "Upload success: \(response)")
let serverSession = ServerSession()
_ = serverSession.fromJSON(resultData: response, resltType: ResLogin.self)
sessionUploadImage.progress = 0.0 // 完了後リセット
isUploadingDialogPresented = false
} catch {
print(debug: "Upload failed: \(error)")
sessionUploadImage.progress = 0.0 // エラー時もリセット
isUploadingDialogPresented = false
failedUploadImage = uploadImage
isRetryDialogPresented = true
}
}
}
//MARK: - Send Chat Video
func sendChatVideo(_ uploadVideo: ReqUploadImage) {
Task {
do {
isUploadingDialogPresented = true
let response = try await SessionUploadImage.OnlyOne.requestUploadVideo(uploadVideo)
print(debug: "Video upload success: \(response)")
let serverSession = ServerSession()
_ = serverSession.fromJSON(resultData: response, resltType: ResLogin.self)
SessionUploadImage.OnlyOne.progress = 0.0
isUploadingDialogPresented = false
} catch {
print(debug: "Video upload failed: \(error)")
SessionUploadImage.OnlyOne.progress = 0.0
isUploadingDialogPresented = false
failedUploadImage = uploadVideo
isRetryDialogPresented = true
}
}
}
// MARK: - ローカル画像を一時保存してURLを取得 (静止画)
func saveImageToTemporaryDirectory(_ image: UIImage) -> String? {
let fileName = UUID().uuidString + ".jpg"
let tempDir = FileManager.default.temporaryDirectory
let fileURL = tempDir.appendingPathComponent(fileName)
guard let data = image.jpegData(compressionQuality: 0.8) else {
print("Image JPEG conversion failed")
print(debug: "Image JPEG conversion failed")
return nil
}
......@@ -124,7 +150,7 @@ class ChatInputViewModel: ObservableObject {
try data.write(to: fileURL)
return fileURL.absoluteString
} catch {
print("Image JPEG conversion failed: \(error.localizedDescription)")
print(debug: "Image JPEG conversion failed: \(error.localizedDescription)")
return nil
}
}
......@@ -167,4 +193,75 @@ class ChatInputViewModel: ObservableObject {
sendChatImage(uploadImage)
}
}
// MARK: - ローカル画像を一時保存してURLを取得 (動画)
func saveVideoToTemporaryDirectory(_ data: Data) -> URL? {
let fileName = UUID().uuidString + ".mp4"
let tempDir = FileManager.default.temporaryDirectory
let fileURL = tempDir.appendingPathComponent(fileName)
do {
try data.write(to: fileURL)
return fileURL
} catch {
print(debug: "Video save failed: \(error.localizedDescription)")
return nil
}
}
//MARK: - 動画データからサムネイル画像を生成する
func generateThumbnail(from videoURL: URL, at time: CMTime = CMTime(seconds: 1.0, preferredTimescale: 600)) -> UIImage? {
let asset = AVAsset(url: videoURL)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
do {
let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
return UIImage(cgImage: cgImage)
} catch {
print("サムネイル生成失敗: \(error.localizedDescription)")
return nil
}
}
//MARK: - 送信前に送信動画をテンポラリに入れる
func sendVideoToTemporary(_ data: Data) {
if let videoURL = saveVideoToTemporaryDirectory(data),
let image = generateThumbnail(from: videoURL),
let localURL = saveImageToTemporaryDirectory(image) {
tempId = UUID().uuidString
let viewer = Viewer(
time: DateTextLib.Date2ISO8601Text(Date()),
location: 2,
id: "",
name: ""
)
let tempMessage = ChatMessage(
shipId: Preferences.shipId,
messageId: tempId,
type: 3, // 動画タイプ
time: DateTextLib.Date2ISO8601Text(Date()),
location: 2,
from: Preferences.UserName,
fromId: String(SharingData.my.id),
mode: SharingData.message.mode ? 1 : 0,
message: localURL,
// videoUrl: videoURL.absoluteString,
stampId: 0,
viewer: [viewer]
)
SharingData.message.messages.append(tempMessage)
let uploadVideo = ReqUploadImage(
shipId: Preferences.shipId,
messageId: tempId,
location: 2,
from: Preferences.UserName,
fromId: String(SharingData.my.id),
files: data
)
sendChatVideo(uploadVideo)
}
}
}
......@@ -28,7 +28,10 @@ struct ChatMessage: Codable {
var fromId: String? //ユーザーID
var mode: Int // 0:通常 , 1:Warning中
var message: String? //テキスト時:テキスト , 画像時:サムネイルのUri
// var videoUrl: String? //TODO: - テスト
var stampId: Int //スタンプ番号 0:Fire~
// var Latitude: Double? //TODO: - テスト
// var Longitude: Double? //TODO: - テスト
var viewer: [Viewer] = [] //閲覧者情報
}
......
......@@ -218,7 +218,6 @@ class AppDelegate: NSObject, UIApplicationDelegate ,MSNotificationHubDelegate, M
if existingMsg.message!.hasPrefix("file://") {
updatedMsg.message = existingMsg.message
}
self.msg.messages[index] = updatedMsg
} else {
self.msg.messages.append(newMsg)
......
......@@ -28,9 +28,7 @@ class SessionUploadImage : ObservableObject {
//MARK: - 画像のアップロード
func requestUploadImage(_ uploadImage: ReqUploadImage) async throws -> Data {
print(debug: "called")
guard !Calling else {
throw APIError.busy
}
guard !Calling else { throw APIError.busy }
Calling = true
defer { Calling = false }
......@@ -92,6 +90,74 @@ class SessionUploadImage : ObservableObject {
}
}
// MARK: - 動画のアップロード
func requestUploadVideo(_ uploadVideo: ReqUploadImage) async throws -> Data {
print("Video upload called")
guard !Calling else {
throw APIError.busy
}
Calling = true
defer { Calling = false }
guard let req_url = URL(string: HttpRequestType.UploadImage.rawValue) else {
throw APIError.invalidURL
}
let boundary = "----------\(UUID().uuidString)"
var httpBody = Data()
func appendFormField(name: String, value: String) {
httpBody.append("--\(boundary)\r\n".data(using: .utf8)!)
httpBody.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data(using: .utf8)!)
httpBody.append("\(value)\r\n".data(using: .utf8)!)
}
appendFormField(name: "ShipId", value: String(uploadVideo.shipId))
appendFormField(name: "MessageId", value: uploadVideo.messageId)
appendFormField(name: "Location", value: String(uploadVideo.location))
appendFormField(name: "From", value: uploadVideo.from)
appendFormField(name: "FromId", value: uploadVideo.fromId)
httpBody.append("--\(boundary)\r\n".data(using: .utf8)!)
httpBody.append("Content-Disposition: form-data; name=\"files\"; filename=\"itemp.mp4\"\r\n".data(using: .utf8)!)
httpBody.append("Content-Type: video/mp4\r\n\r\n".data(using: .utf8)!)
httpBody.append(uploadVideo.files)
httpBody.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
var request = URLRequest(url: req_url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let session = URLSession(configuration: .default, delegate: UploadProgressDelegate(parent: self), delegateQueue: nil)
isUploading = true
return try await withCheckedThrowingContinuation { continuation in
self.uploadTask = session.uploadTask(with: request, from: httpBody) { data, response, error in
DispatchQueue.main.async {
self.isUploading = false
self.progress = 0.0
}
if let error = error {
continuation.resume(throwing: error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode),
let data = data else {
continuation.resume(throwing: APIError.serverError)
return
}
continuation.resume(returning: data)
}
self.uploadTask?.resume()
}
}
func postFormAsync(boundary: String, url: URL, body: Data) async throws -> Data {
var request = URLRequest(url: url)
request.httpMethod = "POST"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment