swift - 无法使用 URLSession 的下载数据任务在 Swift 中播放 mp3
问题描述
我创建了一个带有单个视图控制器的示例空白项目,main.storyboard
下面是实现:
import UIKit
import AVFoundation
private enum DownloadErrors: Error {
case invalidRequest
case noResponse
case noTemporaryURL
case noCacheDirectory
case inplayable
case fileNotExists
case system(error: Error)
var localizedDescription: String {
switch self {
case .invalidRequest:
return "invalid request"
case .noResponse:
return "no response"
case .noTemporaryURL:
return "no temporary URL"
case .noCacheDirectory:
return "no cache directory"
case .inplayable:
return "invalid to play"
case .fileNotExists:
return "file not exists"
case let .system(error):
return error.localizedDescription
}
}
}
class ViewController: UIViewController {
// MARK: - View Life Cycle
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.test()
}
// MARK: - Private API
private func test() {
self.download(with: self.request, completion: { result in
switch result {
case let .failure(error):
print("failure: \(error.localizedDescription)")
return
case let .success(url):
self.play(atURL: url)
}
})
}
private let request: URLRequest? = {
var components = URLComponents()
components.scheme = "https"
components.host = "islex.arnastofnun.is"
components.path = "/islex-files/audio/10/1323741.mp3"
guard let url = components.url else {
return nil
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("audio/mpeg", forHTTPHeaderField: "Content-Type")
request.setValue(
"attachment; filename=\"1323741.mp3\"",
forHTTPHeaderField: "Content-Disposition"
)
print(request.url?.absoluteString ?? "invalid URL")
return request
}()
private typealias Callback = (Result<URL, DownloadErrors>) -> Void
private func download(with nilableRequest: URLRequest?, completion: @escaping Callback) {
guard let request = nilableRequest else {
completion(.failure(.invalidRequest))
return
}
let task = URLSession.shared.downloadTask(with: request) { (rawTemporaryFileURL, rawResponse, rawError) in
if let error = rawError {
completion(.failure(.system(error: error)))
return
}
guard let httpStatusCode = (rawResponse as? HTTPURLResponse)?.statusCode else {
completion(.failure(.noResponse))
return
}
print("http status code: \(httpStatusCode)")
guard let sourceFileURL = rawTemporaryFileURL else {
completion(.failure(.noTemporaryURL))
return
}
guard let cache = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
completion(.failure(.noCacheDirectory))
return
}
let targetFileURL = cache.appendingPathComponent("audio_10_1323741.mp3")
if !FileManager.default.fileExists(atPath: targetFileURL.path) {
do {
try FileManager.default.moveItem(at: sourceFileURL, to: targetFileURL)
} catch let error {
completion(.failure(.system(error: error)))
}
}
DispatchQueue.main.async {
completion(.success(targetFileURL))
}
}
task.resume()
}
private func play(atURL url: URL) {
do {
guard FileManager.default.fileExists(atPath: url.path) else {
print("play: \(DownloadErrors.fileNotExists)")
return
}
let player = try AVAudioPlayer(contentsOf: url)
player.volume = 1.0
player.prepareToPlay()
player.play()
print("play: finished")
} catch let error {
print("play error: \(error.localizedDescription)")
}
}
}
我做错了什么?在尝试使用 url 创建音频播放器时,我得到了成功响应并且没有出错,但是我的播放器没有播放任何内容。我正在尝试立即从链接下载和播放文件:
我的 Xcode 登录控制台:
https://islex.arnastofnun.is/islex-files/audio/10/1323741.mp3
http status code: 200
play: finished
解决方案
由于您位于完成块中,因此您很可能需要将调用包含在 DispatchQueue 块中,以便在主线程上显式运行它。简而言之,您可以这样做:
DispatchQueue.main.async {
self.play(atURL: url)
}
推荐阅读
- javascript - 子组件 componentDidMount() 应该调用不同的方法取决于父组件
- r - R闪亮设置复选框将值控制为数据框特定列中的值
- node.js - 为什么我在使用 Express 和 React.js 时得到 401 的静态文件?
- c++ - 如何保证共享库在 Linux 发行版之间可移植?
- r - 使用 read.csv 保存文件路径
- ios - 使用 SwiftUI 和 Combine 根据授权状态有条件地显示视图?
- python - 即使 cwd 已更改也访问文件
- c++ - 在 Intel 64 位 Red Hat 7.5 上交叉编译到 ARM64 (AARCH64) 找不到 dlfcn.h、cstddef.h 等
- javascript - 不允许拖动两个对象来清空 div
- javascript - 如何在提交前加密密码?