PQAliOssUtil.swift 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. //
  2. // PQAliOssUtil.swift
  3. // PQSpeed
  4. //
  5. // Created by SanW on 2020/12/9.
  6. // Copyright © 2020 BytesFlow. All rights reserved.
  7. //
  8. import UIKit
  9. import BFCommonKit
  10. import BFNetRequestKit
  11. import BFAnalyzeKit
  12. // MARK: - 阿里OSS工具类
  13. /// 阿里OSS工具类
  14. public class PQAliOssUtil: NSObject {
  15. static public let shared = PQAliOssUtil()
  16. public var client: OSSClient?
  17. // 文件类型:materialType (1:PICTURE, 2:VIDEO, 3:VOICE, 4:FILE, 5:GIF)
  18. public var aliOssHander: ((_ isMatarialUpload: Bool, _ materialType: StickerType, _ fileExtensions: String, _ code: Int, _ objectKey: String?, _ contentMD5: String, _ width: CGFloat, _ height: CGFloat, _ duration: CGFloat, _ frameNumber: Int, _ netResourceUrl: String?, _ fileURL: URL?, _ data: Data?, _ msg: String?) -> Void)?
  19. public var aliOssProgressHander: ((_ bytesSent: Int64, _ totalBytesSent: Int64, _ totalBytesExpectedToSend: Int64, _ resttime: Int64, _ uploadSpeed: String?) -> Void)?
  20. public var lastInterfaceBytes: Int64 = 0 // 上次网速
  21. public var oldTime: Int = 0 // 上次进度时间S
  22. public var oloaded: Int64 = 0
  23. public var dics: [String: Any] = [:]
  24. public var allTasks: [String: OSSMultipartUploadRequest] = [:] // add by ak 保存当前所有任务 用于取消某个任务使用
  25. public func startClient(accessKeyId: String, secretKeyId: String, securityToken: String, endpoint: String) -> PQAliOssUtil {
  26. if endpoint.count == 0 {
  27. debugPrint("endpoint is nil xxxxxx \(endpoint)")
  28. }
  29. let credential = OSSStsTokenCredentialProvider(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken)
  30. let conf = OSSClientConfiguration()
  31. conf.timeoutIntervalForRequest = 60 // 连接超时,默认15秒
  32. conf.maxRetryCount = 3
  33. conf.maxConcurrentRequestCount = 5 // 最大并发请求书,默认5个
  34. client = OSSClient(endpoint: endpoint, credentialProvider: credential, clientConfiguration: conf)
  35. return .shared
  36. }
  37. /// 图片上传
  38. /// - Parameters:
  39. /// - bucketName: <#bucketName description#>
  40. /// - objectKey: <#objectKey description#>
  41. /// - data: <#data description#>
  42. /// - Returns: <#description#>
  43. public func uploadObjectAsync(bucketName: String, objectKey: String, data: Data, materialType: StickerType = .IMAGE, fileExtensions: String, isMatarialUpload: Bool = false, contentMD5: String = "", width: CGFloat = 0, height: CGFloat = 0, imageUploadBlock: @escaping (_ osstask: OSSTask<AnyObject>?, _ code: Int, _ objectKey: String, _ fileExtensions: String) -> Void) -> PQAliOssUtil {
  44. let putRequest: OSSPutObjectRequest = OSSPutObjectRequest()
  45. putRequest.bucketName = bucketName
  46. putRequest.objectKey = objectKey
  47. putRequest.uploadingData = data
  48. putRequest.uploadProgress = { [weak self] _, totalByteSent, totalBytesExpectedToSend in
  49. debugPrint("文件上传进度:totalByteSent = \(totalByteSent),totalBytesExpectedToSend = \(totalBytesExpectedToSend)")
  50. if totalBytesExpectedToSend > 0, totalByteSent == totalBytesExpectedToSend {
  51. if isMatarialUpload, self?.aliOssHander != nil {
  52. self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 1, objectKey, contentMD5, width, height, 0, 0, nil, nil, data, "上传成功")
  53. }
  54. }
  55. }
  56. putRequest.contentType = "application/octet-stream"
  57. // putRequest.contentMd5 = contentMD5
  58. putRequest.contentEncoding = ""
  59. putRequest.contentDisposition = ""
  60. let putTask: OSSTask = (client?.putObject(putRequest))!
  61. putTask.continue(successBlock: { [weak self] (osstask) -> Any? in
  62. if osstask.error == nil {
  63. let task = self?.client?.presignPublicURL(withBucketName: putRequest.bucketName, withObjectKey: putRequest.objectKey)
  64. debugPrint("图片原方法上传完成=\(objectKey)")
  65. debugPrint("url == \(task?.result ?? "" as AnyObject)")
  66. imageUploadBlock(osstask, 1, objectKey, fileExtensions)
  67. } else {
  68. if self?.aliOssHander != nil {
  69. self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 0, objectKey, contentMD5, width, height, 0, 0, nil, nil, data, "上传失败")
  70. }
  71. debugPrint("图片原方法上传失败=\(objectKey),osstask.error = \(osstask.error ?? PQError(msg: "失败"))")
  72. imageUploadBlock(osstask, 0, objectKey, fileExtensions)
  73. }
  74. return nil
  75. }).waitUntilFinished()
  76. return .shared
  77. }
  78. /// 快速上传视频
  79. /// - Parameters:
  80. /// - localPath: <#localPath description#>
  81. /// - response: <#response description#>
  82. /// - Returns: <#description#>
  83. class public func multipartUpload(localPath: String, response: [String: Any]?,videoSource:String? = nil) {
  84. let accessKeyId: String = "\(response?["AccessKeyId"] ?? "")"
  85. let secretKeyId: String = "\(response?["AccessKeySecret"] ?? "")"
  86. let securityToken: String = "\(response?["SecurityToken"] ?? "")"
  87. let endpoint: String = "\(response?["Host"] ?? "")"
  88. let bucketName: String = "\(response?["Bucket"] ?? "")"
  89. let FileName: String = "\(response?["FileName"] ?? "")"
  90. let uploadID: String = "\(response?["Upload"] ?? "")"
  91. var endpoints: [String] = Array<String>.init()
  92. if response?.keys.contains("Hosts") ?? false {
  93. endpoints = response?["Hosts"] as! [String]
  94. endpoints.append(endpoint)
  95. }
  96. PQAliOssUtil.shared.PQOSSMultipartUpload(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, bucketName: bucketName, endpoints: endpoints, FileName: FileName, fileURL: URL(fileURLWithPath: localPath.replacingOccurrences(of: "file:///", with: "")), ossUploadID: uploadID, videoSource: videoSource)
  97. }
  98. /// 重新上传
  99. /// - Parameters:
  100. /// - response: <#response description#>
  101. /// - localPath: <#localPath description#>
  102. /// - Returns: <#description#>
  103. public func reloadTask(response: [String: Any]?, localPath: String?,videoSource:String? = nil) {
  104. if response == nil {
  105. return
  106. }
  107. let accessKeyId: String = "\(response?["AccessKeyId"] ?? "")"
  108. let secretKeyId: String = "\(response?["AccessKeySecret"] ?? "")"
  109. let securityToken: String = "\(response?["SecurityToken"] ?? "")"
  110. let endpoint: String = "\(response?["Host"] ?? "")"
  111. let bucketName: String = "\(response?["Bucket"] ?? "")"
  112. let FileName: String = "\(response?["FileName"] ?? "")"
  113. let uploadID: String = "\(response?["Upload"] ?? "")"
  114. var endpoints: [String] = Array<String>.init()
  115. if response?.keys.contains("Hosts") ?? false {
  116. endpoints = response?["Hosts"] as! [String]
  117. endpoints.append(endpoint)
  118. }
  119. debugPrint("取我方服务器STS 返回数据 \(String(describing: response))")
  120. PQAliOssUtil.shared.PQOSSMultipartUpload(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, bucketName: bucketName, endpoints: endpoints, FileName: FileName, fileURL: URL(fileURLWithPath: (localPath ?? "").replacingOccurrences(of: "file:///", with: "")), ossUploadID: uploadID,videoSource: videoSource)
  121. }
  122. /// 分片上传
  123. /// - Parameters:
  124. /// - accessKeyId: <#accessKeyId description#>
  125. /// - secretKeyId: <#secretKeyId description#>
  126. /// - securityToken: <#securityToken description#>
  127. /// - bucketName: <#bucketName description#>
  128. /// - endpoints: oss host域名
  129. /// - FileName: oss 资源地址
  130. /// - fileURL: 文件本地地址
  131. /// - ossUploadID: oss 上传ID
  132. /// - contentType: 文件类型
  133. /// - materialType: 素材类型
  134. /// - isMatarialUpload: 是否是素材上传
  135. /// - contentMD5: 素材内容MD5值 isMatarialUpload = true时传
  136. /// - width: 视频宽 isMatarialUpload = true时传
  137. /// - height: 视频高 isMatarialUpload = true时传
  138. /// - Returns: <#description#>
  139. public func PQOSSMultipartUpload(accessKeyId: String, secretKeyId: String, securityToken: String, bucketName: String, endpoints: [String], FileName: String, fileURL: URL, ossUploadID: String, materialType: StickerType = .VIDEO, isMatarialUpload: Bool = false, contentMD5: String = "", width: CGFloat = 0, height: CGFloat = 0,videoSource:String? = nil) -> PQAliOssUtil {
  140. debugPrint("上传数据 参数\n accessKeyId:\(accessKeyId)\n secretKeyId:\(secretKeyId)\n securityToken:\(securityToken) \n bucketName:\(bucketName)\n endpoint:\(endpoints)\n FileName:\(FileName)")
  141. if endpoints.count <= 0 || (endpoints.first?.count ?? 0) <= 0 {
  142. debugPrint("endpoints 为空")
  143. return .shared
  144. }
  145. #if DEBUG
  146. // 打开 oss 日志
  147. OSSLog.enable()
  148. #endif
  149. // 1,设置鉴权
  150. let url = PQENVUtil.shared.longvideoapi + (isMatarialUpload ? materialUploadStsTokenUrl : getStsTokenUrl)
  151. let authServerUrl = url + "?appType=\(commonParams()["appType"] as? String ?? "")" + "&machineCode=" + getMachineCode()
  152. + "&token=" + (BFConfig.shared.token ?? "") + "&loginUid" + (BFConfig.shared.uid ?? "") + "&fileType=2" + "&uploadId=\(ossUploadID)"
  153. debugPrint("authServerUrl is: \(authServerUrl)")
  154. debugPrint("当前上传authServerUrl线程:\(Thread.isMainThread) ")
  155. let provider = OSSAuthCredentialProvider(authServerUrl: authServerUrl) { (data) -> Data? in
  156. debugPrint("当前上传provider线程:\(Thread.isMainThread) ")
  157. // 在 OSSModel 代码中解析有自己的格式 所以接收到我方服务器后的数据要进行二次处理 AccessKeyId 等信息
  158. let str = String(data: data, encoding: .utf8)
  159. let jsonDic = jsonStringToDictionary(str!)
  160. var respDic: [String: Any] = [:]
  161. if jsonDic?.keys.contains("code") ?? false, "\(jsonDic?["code"] ?? "")" == "0" {
  162. respDic = [
  163. "StatusCode": "200",
  164. "AccessKeyId": (jsonDic?["data"] as? [String: Any])?["AccessKeyId"] ?? accessKeyId,
  165. "AccessKeySecret": (jsonDic?["data"] as? [String: Any])?["AccessKeySecret"] ?? secretKeyId,
  166. "SecurityToken": (jsonDic?["data"] as? [String: Any])?["SecurityToken"] ?? securityToken,
  167. "Expiration": (jsonDic?["data"] as? [String: Any])?["Expiration"] ?? securityToken,
  168. ]
  169. }
  170. let proStr = dictionaryToJsonString(respDic as [String: Any])
  171. debugPrint("处理后准备给 OSS SDK数据 \(String(describing: proStr))")
  172. let decodedData = proStr?.data(using: .utf8)
  173. if decodedData != nil {
  174. return decodedData
  175. }
  176. return data
  177. }
  178. // 2 拼装 request
  179. let request = OSSMultipartUploadRequest()
  180. // add by ak set contect type . doc https://help.aliyun.com/knowledge_detail/39522.html
  181. request.contentType = fileURL.absoluteString.mimeType() // 媒体类型
  182. request.uploadingFileURL = fileURL
  183. request.bucketName = bucketName
  184. request.objectKey = FileName
  185. // 根据文件大小 设置 part size
  186. var fileSize: UInt64 = 0
  187. do {
  188. let attr = try FileManager.default.attributesOfItem(atPath: fileURL.relativePath)
  189. fileSize = attr[FileAttributeKey.size] as! UInt64
  190. } catch {
  191. debugPrint("取文件大小 Error: \(error)")
  192. if aliOssHander != nil {
  193. DispatchQueue.main.async { [weak self] in
  194. self?.aliOssHander!(isMatarialUpload, materialType, fileURL.absoluteString.pathExtension, 260, FileName, contentMD5, width, height, 0, 0, nil, fileURL, nil, "文件已丢失")
  195. }
  196. }
  197. }
  198. debugPrint("文件已经存在 \(fileURL) 文件大小 \(fileSize)")
  199. var partSize: Int = 0
  200. // sdk中规定kClientMaximumOfChunks = 5000;
  201. let defaultChunkSize: UInt64 = 1024 * 1024
  202. if ceil(Float(fileSize / defaultChunkSize)) <= 1 {
  203. partSize = 500 * 1024
  204. } else if ceil(Float(fileSize / defaultChunkSize)) < 5000 {
  205. partSize = Int(fileSize / UInt64(ceil(Float(fileSize / defaultChunkSize))))
  206. } else {
  207. partSize = Int(ceil(Float(fileSize / 5000)))
  208. }
  209. debugPrint("partSize \(partSize)")
  210. // 除最后一片外 不能小于 102400(100kb)
  211. request.partSize = UInt(partSize)
  212. request.uploadId = ossUploadID
  213. request.uploadProgress = { [weak self] (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
  214. debugPrint("当前上传uploadProgress线程:\(Thread.isMainThread) ")
  215. self?.paraseSpeedAndRestTime(bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend) { uploadSpeed, resttime in
  216. if self?.aliOssProgressHander != nil {
  217. DispatchQueue.main.async { [weak self] in
  218. self?.aliOssProgressHander!(bytesSent, totalBytesSent, totalBytesExpectedToSend, resttime, uploadSpeed)
  219. }
  220. }
  221. }
  222. // 多个界面都要处理上传进度 所以使用通知 ? 通知有回到主线?
  223. // postNotification(name: cOSSUploadFileProgress,userInfo: ["bytesSent":bytesSent,"totalBytesSent":totalBytesSent,"totalBytesExpectedToSend":totalBytesExpectedToSend,"resttime":resttime])
  224. }
  225. // 3,设置 config
  226. let conf = OSSClientConfiguration()
  227. conf.timeoutIntervalForRequest = 30 // 连接超时,默认15秒
  228. conf.maxConcurrentRequestCount = 5 // 最大并发请求书,默认5个
  229. conf.maxRetryCount = 3 // 失败后最大重试次数,默认2次
  230. // 打开后台上传
  231. conf.enableBackgroundTransmitService = true
  232. conf.backgroundSesseionIdentifier = getUniqueId(desc: "\(ossUploadID)\(fileURL.absoluteString)\(FileName)")
  233. // 4,设置 clinet
  234. let client = OSSClient(endpoint: endpoints[0], credentialProvider: provider, clientConfiguration: conf)
  235. if !isMatarialUpload {
  236. allTasks[FileName] = request
  237. }
  238. // 5,发起任务
  239. let task = client.multipartUpload(request)
  240. task.continue ({ [weak self] (osstask) -> Any? in
  241. debugPrint("当前上传线程:\(Thread.isMainThread) ")
  242. if osstask.error == nil {
  243. debugPrint("上传成功")
  244. // 文件URL的格式为:BucketName.Endpoint/ObjectName。
  245. debugPrint(" 上传成功注意使用时拼接域名 url == \(FileName)")
  246. request.callbackParam = ["code": 1, "objectKey": FileName, "msg": "上传成功"]
  247. DispatchQueue.main.async { [weak self] in
  248. if self?.aliOssHander != nil {
  249. self?.aliOssHander!(isMatarialUpload, materialType, fileURL.absoluteString.pathExtension, 1, FileName, contentMD5, width, height, 0, 0, nil, fileURL, nil, "上传成功")
  250. }
  251. }
  252. // 如果在后台发送上传成功的本地通知
  253. if !isMatarialUpload {
  254. DispatchQueue.main.async {
  255. if UIApplication.shared.applicationState == .background {
  256. sendUploadNotification(isSuccess: true)
  257. }
  258. }
  259. postNotification(name: cUploadSuccessKey, userInfo: ["code": 1, "objectKey": FileName, "msg": "上传成功"])
  260. }
  261. // 上传完成
  262. var extParams:Dictionary<String,Any>?
  263. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  264. extParams = ["source":videoSource ?? ""]
  265. }
  266. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_success, pageSource: nil,extParams: extParams, commonParams: commonParams())
  267. } else {
  268. debugPrint("上传失败 \(osstask.error!)")
  269. request.callbackParam = ["code": (osstask.error! as NSError).code, "objectKey": FileName, "msg": osstask.error?.localizedDescription ?? ""]
  270. if self?.aliOssHander != nil {
  271. DispatchQueue.main.async { [weak self] in
  272. self?.aliOssHander!(isMatarialUpload, materialType, fileURL.absoluteString.pathExtension, (osstask.error! as NSError).code, FileName, contentMD5, width, height, 0, 0, nil, fileURL, nil, osstask.error?.localizedDescription)
  273. }
  274. }
  275. // 如果在后台发送上传失败的本地通知
  276. if !isMatarialUpload {
  277. DispatchQueue.main.async {
  278. if UIApplication.shared.applicationState == .background {
  279. sendUploadNotification(isSuccess: false)
  280. }
  281. }
  282. }
  283. // 上传失败
  284. var extParams:Dictionary<String,Any>?
  285. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  286. extParams = ["source":videoSource ?? ""]
  287. }
  288. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_fail, pageSource: nil,extParams: extParams, commonParams: commonParams())
  289. }
  290. return nil
  291. })
  292. // 开始上传
  293. var extParams:Dictionary<String,Any>?
  294. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  295. extParams = ["source":videoSource ?? ""]
  296. }
  297. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_start, pageSource: nil,extParams: extParams, commonParams: commonParams())
  298. return .shared
  299. }
  300. /// add by ak 简单上传方式 本方法支持后台运行 设置支持后台时会把 DATA
  301. /// - Parameters:
  302. /// - accessKeyId: <#accessKeyId description#>
  303. /// - secretKeyId: <#secretKeyId description#>
  304. /// - securityToken: <#securityToken description#>
  305. /// - bucketName: <#bucketName description#>
  306. /// - endpoint: oss host域名
  307. /// - objectKey: oss 资源地址
  308. /// - fileURL: 文件本地地址
  309. /// - data: 文件数据
  310. /// - fileExtensions: 文件后缀名 mp4/mp3
  311. /// - enableBackground: 是否支持后台下载
  312. /// - materialType: 素材类型
  313. /// - isMatarialUpload: 是否是素材上传
  314. /// - contentMD5: 素材内容MD5值 isMatarialUpload = true时传
  315. /// - width: 视频宽 isMatarialUpload = true时传
  316. /// - height: 视频高 isMatarialUpload = true时传
  317. /// - duration: 素材时长 isMatarialUpload = true时传
  318. /// - frameNumber: gif素材帧数 isMatarialUpload = true时传
  319. /// - netResourceUrl: 网络素材地址 isMatarialUpload = true时传
  320. /// - Returns: <#description#>
  321. public func putObjectAsync(accessKeyId: String, secretKeyId: String, securityToken: String, bucketName: String, endpoint: [String], objectKey: String, fileURL: URL?, data: Data?, fileExtensions: String, enableBackground: Bool, materialType: StickerType = .VIDEO, isMatarialUpload: Bool = false, contentMD5: String = "", width: CGFloat = 0, height: CGFloat = 0, duration: CGFloat, frameNumber: Int, netResourceUrl: String? = nil,videoSource:String? = nil) -> PQAliOssUtil {
  322. #if DEBUG
  323. OSSLog.enable()
  324. #endif
  325. debugPrint("普通上传数据 参数 accessKeyId:\(accessKeyId) secretKeyId:\(secretKeyId) securityToken:\(securityToken) bucketName:\(bucketName) endpoint:\(endpoint) objectKey:\(objectKey) enableBackground:\(enableBackground) fileURL : \(fileURL!)")
  326. if endpoint.count <= 0 || (endpoint.first?.count ?? 0) <= 0 {
  327. debugPrint("endpoints 为空")
  328. return .shared
  329. }
  330. let provider = OSSStsTokenCredentialProvider(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken)
  331. // let configuration = OSSClientConfiguration()
  332. // if enableBackground {
  333. // configuration.enableBackgroundTransmitService = true
  334. // configuration.backgroundSesseionIdentifier = objectKey
  335. // }
  336. // configuration.timeoutIntervalForRequest = 60 // 连接超时,默认15秒
  337. // configuration.maxConcurrentRequestCount = 20 // 最大并发请求书,默认5个
  338. // configuration.maxRetryCount = 3 // 失败后最大重试次数,默认2次
  339. startClient(accessKeyId: accessKeyId, secretKeyId: secretKeyId, securityToken: securityToken, endpoint: endpoint.first ?? "")
  340. let request = OSSPutObjectRequest()
  341. if fileURL != nil {
  342. request.uploadingFileURL = fileURL!
  343. request.contentType = fileURL!.absoluteString.mimeType() // 媒体类型
  344. } else if data != nil {
  345. request.uploadingData = data!
  346. request.contentType = materialType.mimeType() // 媒体类型
  347. }
  348. request.bucketName = bucketName
  349. request.objectKey = objectKey
  350. // request.contentMd5 = contentMD5
  351. request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
  352. var resttime: Int64 = 0
  353. if self.oldTime == 0 {
  354. self.oldTime = Int(Date().timeIntervalSince1970)
  355. }
  356. let nowTime = Int(Date().timeIntervalSince1970)
  357. let pertime = Int(nowTime - self.oldTime)
  358. if pertime != 0 {
  359. var speed = totalBytesSent / Int64(pertime) // 单位b/s
  360. if speed != 0 {
  361. resttime = ((totalBytesExpectedToSend - totalBytesSent) / speed)
  362. var units = "b/s" // 单位名称
  363. if speed / 1024 > 1 {
  364. speed = speed / 1024
  365. units = "k/s"
  366. }
  367. if speed / 1024 > 1 {
  368. speed = speed / 1024
  369. units = "M/s"
  370. }
  371. debugPrint("上传速度: \(speed)\(units) 还剩时间 \(resttime)")
  372. }
  373. }
  374. // 多个界面都要处理上传进度 所以使用通知 ? 通知有回到主线?
  375. postNotification(name: cOSSUploadFileProgress, userInfo: ["bytesSent": bytesSent, "totalBytesSent": totalBytesSent, "totalBytesExpectedToSend": totalBytesExpectedToSend, "resttime": resttime])
  376. }
  377. let task = client?.putObject(request)
  378. if fileURL?.absoluteString.contains("_noise_") ?? false {
  379. task?.continue ({ [weak self] (osstask) -> Any? in
  380. debugPrint("当前上传线程:\(Thread.isMainThread) ")
  381. if osstask.error == nil {
  382. debugPrint("上传成功")
  383. // 文件URL的格式为:BucketName.Endpoint/ObjectName。
  384. debugPrint(" 上传成功注意使用时拼接域名 url == \(objectKey)")
  385. request.callbackParam = ["code": 1, "objectKey": objectKey, "msg": "上传成功"]
  386. DispatchQueue.main.async { [weak self] in
  387. if self?.aliOssHander != nil {
  388. self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 1, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, "上传成功")
  389. }
  390. }
  391. // 如果在后台发送上传成功的本地通知
  392. if !isMatarialUpload {
  393. DispatchQueue.main.async {
  394. if UIApplication.shared.applicationState == .background {
  395. sendUploadNotification(isSuccess: true)
  396. }
  397. }
  398. postNotification(name: cUploadSuccessKey, userInfo: ["code": 1, "objectKey": objectKey, "msg": "上传成功"])
  399. }
  400. // 上传完成
  401. var extParams:Dictionary<String,Any>?
  402. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  403. extParams = ["source":videoSource ?? ""]
  404. }
  405. // 上传完成
  406. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_success, pageSource: nil,extParams: extParams, commonParams: commonParams())
  407. } else {
  408. debugPrint("上传失败 \(osstask.error!)")
  409. request.callbackParam = ["code": (osstask.error! as NSError).code, "objectKey": objectKey, "msg": osstask.error?.localizedDescription ?? ""]
  410. if self?.aliOssHander != nil {
  411. DispatchQueue.main.async { [weak self] in
  412. self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, (osstask.error! as NSError).code, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, osstask.error?.localizedDescription)
  413. }
  414. }
  415. // 如果在后台发送上传失败的本地通知
  416. if !isMatarialUpload {
  417. DispatchQueue.main.async {
  418. if UIApplication.shared.applicationState == .background {
  419. sendUploadNotification(isSuccess: false)
  420. }
  421. }
  422. }
  423. // 上传失败
  424. var extParams:Dictionary<String,Any>?
  425. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  426. extParams = ["source":videoSource ?? ""]
  427. }
  428. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_fail, pageSource: nil,extParams: extParams, commonParams: commonParams())
  429. }
  430. return nil
  431. }).waitUntilFinished()
  432. } else {
  433. task?.continue ({ [weak self] (osstask) -> Any? in
  434. debugPrint("当前上传线程:\(Thread.isMainThread) ")
  435. if osstask.error == nil {
  436. debugPrint("上传成功")
  437. // 文件URL的格式为:BucketName.Endpoint/ObjectName。
  438. debugPrint(" 上传成功注意使用时拼接域名 url == \(objectKey)")
  439. request.callbackParam = ["code": 1, "objectKey": objectKey, "msg": "上传成功"]
  440. DispatchQueue.main.async { [weak self] in
  441. if self?.aliOssHander != nil {
  442. self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, 1, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, "上传成功")
  443. }
  444. }
  445. // 如果在后台发送上传成功的本地通知
  446. if !isMatarialUpload {
  447. DispatchQueue.main.async {
  448. if UIApplication.shared.applicationState == .background {
  449. sendUploadNotification(isSuccess: true)
  450. }
  451. }
  452. postNotification(name: cUploadSuccessKey, userInfo: ["code": 1, "objectKey": objectKey, "msg": "上传成功"])
  453. }
  454. // 上传完成
  455. var extParams:Dictionary<String,Any>?
  456. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  457. extParams = ["source":videoSource ?? ""]
  458. }
  459. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_success, pageSource: nil,extParams: extParams, commonParams: commonParams())
  460. } else {
  461. debugPrint("上传失败 \(osstask.error!)")
  462. request.callbackParam = ["code": (osstask.error! as NSError).code, "objectKey": objectKey, "msg": osstask.error?.localizedDescription ?? ""]
  463. if self?.aliOssHander != nil {
  464. DispatchQueue.main.async { [weak self] in
  465. self?.aliOssHander!(isMatarialUpload, materialType, fileExtensions, (osstask.error! as NSError).code, objectKey, contentMD5, width, height, duration, frameNumber, netResourceUrl, fileURL, data, osstask.error?.localizedDescription)
  466. }
  467. }
  468. // 如果在后台发送上传失败的本地通知
  469. if !isMatarialUpload {
  470. DispatchQueue.main.async {
  471. if UIApplication.shared.applicationState == .background {
  472. sendUploadNotification(isSuccess: false)
  473. }
  474. }
  475. }
  476. // 上传失败
  477. var extParams:Dictionary<String,Any>?
  478. if videoSource != nil && (videoSource?.count ?? 0) > 0 {
  479. extParams = ["source":videoSource ?? ""]
  480. }
  481. BFEventTrackAdaptor.baseReportUpload(businessType: .bt_up_process, objectType: .ot_up_fail, pageSource: nil,extParams: extParams, commonParams: commonParams())
  482. }
  483. return nil
  484. })
  485. }
  486. return .shared
  487. }
  488. /// 取消某个任务
  489. /// - Parameter objectKey: 任务唯一标识
  490. public func putObjectCancel(objectKey: String) {
  491. for key in allTasks.keys {
  492. if key == objectKey {
  493. allTasks[key]!.cancel()
  494. allTasks.removeValue(forKey: key)
  495. }
  496. }
  497. }
  498. /// 解析上传速度及剩余时间
  499. /// - Parameters:
  500. /// - bytesSent: <#bytesSent description#>
  501. /// - totalBytesSent: <#totalBytesSent description#>
  502. /// - totalBytesExpectedToSend: <#totalBytesExpectedToSend description#>
  503. /// - complateHandle: <#complateHandle description#>
  504. /// - Returns: <#description#>
  505. public func paraseSpeedAndRestTime(bytesSent _: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64, complateHandle: (_ uploadSpeed: String, _ resttime: Int64) -> Void) {
  506. let newInterfaceBytes = PQBridgeObject.getInterfaceBytes()
  507. var interfaceBytes = abs(newInterfaceBytes - lastInterfaceBytes)
  508. if interfaceBytes <= 0 {
  509. interfaceBytes = 1
  510. }
  511. lastInterfaceBytes = newInterfaceBytes
  512. let resttime = (totalBytesExpectedToSend - totalBytesSent) / interfaceBytes
  513. debugPrint("interfaceBytes = \(interfaceBytes),totalBytesExpectedToSend = \(totalBytesExpectedToSend),totalBytesSent = \(totalBytesSent),")
  514. complateHandle("\(PQBridgeObject.formatNetWork(interfaceBytes))", resttime)
  515. }
  516. override private init() {
  517. super.init()
  518. }
  519. public override func copy() -> Any {
  520. return self
  521. }
  522. public override func mutableCopy() -> Any {
  523. return self
  524. }
  525. }