Ric*_*ero 2 base64 ios video-gallery swift urlsession
我目前正在研究一种将视频短视频(10-30秒)上传到我的数据库的方法,并询问是否可以将视频从本地图库转换为base64,目前我使用 imagePickerController 获取视频你可以在这段代码中看到:
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
//Here is save the video URL
let url = info[.mediaURL] as? URL
//Here goes the code to convert this video URL to base64...
self.dismiss(animated: true)
}
Run Code Online (Sandbox Code Playgroud)
我还想知道是否可以将视频保存到 Base64 并将其发送到我的发布请求正文中,或者我应该使用其他方式将视频上传到服务器中?我愿意接受任何建议,谢谢
我建议不要对视频进行 Base64 编码。
\n该资产已经如此庞大:
\n您希望防止 base64 使资产变得更大(因此上传速度更慢);和
\n无论如何,您可能希望避免在任何给定时间将整个资产加载到内存中(即避免Data在构建此上传请求的过程中使用 a )。标准的 Base-64 编码Data方法实际上要求您在内存中拥有整个资产才能执行 Base-64 编码,并且同时内存中也将具有 Base-64 字符串。
例如,对Data50 MB 的视频使用标准的 Base-64 编码方法可能会使内存至少达到 116 MB。
请求multipart/form-data是标准方法(允许嵌入二进制有效负载并发送附加字段)。但要小心,因为您\xe2\x80\x99 会在网上找到大多数示例,Data然后将其发送,这可能并不谨慎。将其写入文件,而无需在任何给定时间尝试将整个资产加载到 RAM 中。然后执行基于文件的上传任务以将其发送到您的服务器。
例如,如果您想自己创建此多部分请求,您可以执行如下操作:
\n// MARK: - Public interface\n\nextension URLSession {\n /// Delegate-based upload task\n\n @discardableResult\n func uploadTask(\n from url: URL,\n headers: [String: String]? = nil,\n parameters: [String: String]? = nil,\n filePathKey: String,\n fileURLs: [URL]\n ) throws -> URLSessionUploadTask {\n let (request, fileURL) = try uploadRequestFile(from: url, headers: headers, parameters: parameters, filePathKey: filePathKey, fileURLs: fileURLs)\n return uploadTask(with: request, fromFile: fileURL)\n }\n\n /// Completion-handler-based upload task\n\n @discardableResult\n func uploadTask(\n from url: URL,\n headers: [String: String]? = nil,\n parameters: [String: String]? = nil,\n filePathKey: String,\n fileURLs: [URL],\n completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void\n ) -> URLSessionUploadTask? {\n do {\n let (request, fileURL) = try uploadRequestFile(\n from: url,\n headers: headers,\n parameters: parameters,\n filePathKey: filePathKey,\n fileURLs: fileURLs\n )\n return uploadTask(with: request, fromFile: fileURL, completionHandler: completionHandler)\n } catch {\n completionHandler(nil, nil, error)\n return nil\n }\n }\n\n /// Async-await-based upload task\n\n @available(iOS 15.0, *)\n func upload(\n from url: URL,\n headers: [String: String]? = nil,\n parameters: [String: String]? = nil,\n filePathKey: String,\n fileURLs: [URL],\n delegate: URLSessionTaskDelegate? = nil\n ) async throws -> (Data, URLResponse) {\n let (request, fileURL) = try uploadRequestFile(\n from: url,\n headers: headers,\n parameters: parameters,\n filePathKey: filePathKey,\n fileURLs: fileURLs\n )\n return try await upload(for: request, fromFile: fileURL, delegate: delegate)\n }\n}\n\n// MARK: - Private implementation\n\nprivate extension URLSession {\n private func uploadRequestFile(\n from url: URL,\n headers: [String: String]? = nil,\n parameters: [String: String]? = nil,\n filePathKey: String,\n fileURLs: [URL]\n ) throws -> (URLRequest, URL) {\n let boundary = "Boundary-" + UUID().uuidString\n\n var request = URLRequest(url: url)\n request.httpMethod = "POST"\n request.setValue("multipart/form-data; boundary=\\(boundary)", forHTTPHeaderField: "Content-Type")\n\n headers?.forEach { (key, value) in\n request.addValue(value, forHTTPHeaderField: key)\n }\n\n let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())\n .appendingPathComponent(UUID().uuidString)\n\n guard let stream = OutputStream(url: fileURL, append: false) else {\n throw OutputStreamError.unableToCreateFile\n }\n\n stream.open()\n \n try parameters?.forEach { (key, value) in\n try stream.write("--\\(boundary)\\r\\n")\n try stream.write("Content-Disposition: form-data; name=\\"\\(key)\\"\\r\\n\\r\\n")\n try stream.write("\\(value)\\r\\n")\n }\n\n for fileURL in fileURLs {\n let filename = fileURL.lastPathComponent\n\n try stream.write("--\\(boundary)\\r\\n")\n try stream.write("Content-Disposition: form-data; name=\\"\\(filePathKey)\\"; filename=\\"\\(filename)\\"\\r\\n")\n try stream.write("Content-Type: \\(fileURL.mimeType)\\r\\n\\r\\n")\n try stream.write(from: fileURL)\n try stream.write("\\r\\n")\n }\n\n try stream.write("--\\(boundary)--\\r\\n")\n\n stream.close()\n\n return (request, fileURL)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n和
\nextension URL {\n /// Mime type for the URL\n ///\n /// Requires `import UniformTypeIdentifiers` for iOS 14 solution.\n /// Requires `import MobileCoreServices` for pre-iOS 14 solution\n\n var mimeType: String {\n if #available(iOS 14.0, *) {\n return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"\n } else {\n guard\n let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),\n let mimeType = UTTypeCopyPreferredTagWithClass(identifier, kUTTagClassMIMEType)?.takeRetainedValue() as String?\n else {\n return "application/octet-stream"\n }\n\n return mimeType\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n和
\nenum OutputStreamError: Error {\n case stringConversionFailure\n case bufferFailure\n case writeFailure\n case unableToCreateFile\n case unableToReadFile\n}\n\nextension OutputStream {\n\n /// Write `String` to `OutputStream`\n ///\n /// - parameter string: The `String` to write.\n /// - parameter encoding: The `String.Encoding` to use when writing the string. This will default to `.utf8`.\n /// - parameter allowLossyConversion: Whether to permit lossy conversion when writing the string. Defaults to `false`.\n\n func write(_ string: String, encoding: String.Encoding = .utf8, allowLossyConversion: Bool = false) throws {\n guard let data = string.data(using: encoding, allowLossyConversion: allowLossyConversion) else {\n throw OutputStreamError.stringConversionFailure\n }\n try write(data)\n }\n\n /// Write `Data` to `OutputStream`\n ///\n /// - parameter data: The `Data` to write.\n\n func write(_ data: Data) throws {\n try data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) throws in\n guard var pointer = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {\n throw OutputStreamError.bufferFailure\n }\n\n var bytesRemaining = buffer.count\n\n while bytesRemaining > 0 {\n let bytesWritten = write(pointer, maxLength: bytesRemaining)\n if bytesWritten < 0 {\n throw OutputStreamError.writeFailure\n }\n\n bytesRemaining -= bytesWritten\n pointer += bytesWritten\n }\n }\n }\n\n /// Write `Data` to `OutputStream`\n ///\n /// - parameter data: The `Data` to write.\n\n func write(from url: URL) throws {\n guard let input = InputStream(url: url) else {\n throw OutputStreamError.unableToReadFile\n }\n\n input.open()\n defer { input.close() }\n\n let bufferSize = 65_536\n\n var data = Data(repeating: 0, count: bufferSize)\n\n try data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) throws in\n guard let buffer = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {\n throw OutputStreamError.bufferFailure\n }\n\n while input.hasBytesAvailable {\n var remainingCount = input.read(buffer, maxLength: bufferSize)\n if remainingCount < 0 { throw OutputStreamError.unableToReadFile }\n\n var pointer = buffer\n while remainingCount > 0 {\n let countWritten = write(pointer, maxLength: remainingCount)\n if countWritten < 0 { throw OutputStreamError.writeFailure }\n remainingCount -= countWritten\n pointer += countWritten\n }\n }\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n然后你可以执行以下操作(在 iOS 15 中):
\nextension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {\n func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {\n dismiss(animated: true)\n }\n\n func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {\n guard let fileURL = info[.mediaURL] as? URL else {\n print("no media URL")\n return\n }\n\n Task {\n do {\n let (data, response) = try await URLSession.shared.upload(from: url, filePathKey: "file", fileURLs: [fileURL])\n try? FileManager.default.removeItem(at: fileURL)\n\n // check `data` and `response` here\n } catch {\n print(error)\n }\n }\n\n dismiss(animated: true)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n或者,在早期的 Swift 版本中:
\nextension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {\n func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {\n dismiss(animated: true)\n }\n\n func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {\n guard let fileURL = info[.mediaURL] as? URL else {\n print("no media URL")\n return\n }\n\n URLSession.shared.uploadTask(from: url, filePathKey: "file", fileURLs: [fileURL]) { data, _, error in\n try? FileManager.default.removeItem(at: fileURL)\n\n guard let data = data, error == nil else {\n print(error!)\n return\n }\n\n // check `data` and `response` here\n }?.resume()\n\n dismiss(animated: true)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n在这里,虽然我上传了两个 55mb 的视频,但总分配从未超过 8mb(其中一些似乎是由图像选择器本身缓存的内存)。我重复了两次,以说明后续每次上传时内存不会继续增长。
\n\n(绿色间隔是在图像/视频选择器和相关视频压缩中花费的时间。红色间隔是实际上传的时间。这样您就可以将进程与内存使用情况关联起来。)
\n| 归档时间: |
|
| 查看次数: |
2148 次 |
| 最近记录: |