627 lines
27 KiB
Swift
627 lines
27 KiB
Swift
//
|
||
// MP_AVURLAsset.swift
|
||
// MusicPlayer
|
||
//
|
||
// Created by Mr.Zhou on 2024/5/23.
|
||
//
|
||
|
||
import UIKit
|
||
import AVFoundation
|
||
import MobileCoreServices
|
||
|
||
/////自定义媒体资产
|
||
class MP_AVURLAsset: AVURLAsset {
|
||
//加载器
|
||
private var playerResourceLoader:MP_PlayerResourceLoader?
|
||
//标题
|
||
private var title:String
|
||
//加载队列
|
||
private var assetQueue:DispatchQueue!
|
||
///加载网络媒体资源
|
||
init(_ url:URL, videoId:String, title:String) {
|
||
self.title = title
|
||
//判断是否缓存区中是否存在这个媒体资源
|
||
let path = MP_CacheAndArchiverManager.shared.createCachePath() ?? ""
|
||
let audioName = "/"+videoId+".mp4"
|
||
//缓存文件地址
|
||
let audioPath = path+audioName
|
||
var customURL:URL!
|
||
//判断这个文件是否存在
|
||
if MP_CacheAndArchiverManager.shared.fileManager.fileExists(atPath: audioPath) {
|
||
//存在,使用缓存文件播放
|
||
customURL = .init(fileURLWithPath: audioPath)
|
||
// print("从缓存中播放\(title)")
|
||
super.init(url: customURL, options: nil)
|
||
}else {
|
||
//不存在,使用网络加载器加载
|
||
customURL = MP_PlayerToolConfig.customURL(url)!
|
||
//创建对应的加载队列
|
||
assetQueue = .init(label: "com.relax.offline.mp3.\(videoId)")
|
||
playerResourceLoader = .init(videoId: videoId, url: customURL, title: title)
|
||
// print("从网络媒体中播放\(title)")
|
||
super.init(url: customURL, options: nil)
|
||
//将系统加载器切换为自定义加载器
|
||
self.resourceLoader.setDelegate(playerResourceLoader, queue: .global(qos: .background))
|
||
}
|
||
//实现预加载
|
||
preloading()
|
||
}
|
||
///加载本地媒体资源
|
||
init(LocalURL url: URL, videoId:String, title:String) {
|
||
self.title = title
|
||
super.init(url: url, options: nil)
|
||
//本地加载,不需要调用加载器
|
||
//实现预加载
|
||
preloading()
|
||
}
|
||
deinit{
|
||
//销毁内容
|
||
playerResourceLoader = nil
|
||
assetQueue = nil
|
||
}
|
||
//预加载
|
||
private func preloading() {
|
||
//对该Asset实现预加载,以让Asset触发resourceLoaderdelegate
|
||
//加载关键的播放属性
|
||
let keys = ["playable"]
|
||
self.loadValuesAsynchronously(forKeys: keys) {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
// 检查加载属性的结果
|
||
for key in keys {
|
||
var error: NSError?
|
||
let status = statusOfValue(forKey: key, error: &error)
|
||
if status == .loaded {
|
||
print("开始对\(title)的预加载")
|
||
} else {
|
||
// 处理加载失败的情况
|
||
print("无法加载 \(key): \(String(describing: error))")
|
||
//一般是网络数据问题,需要重新请求一遍
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
///媒体资源加载代理
|
||
protocol MP_PlayerResourceLoaderDelegate: NSObject {
|
||
///检测当前加载器是否缓存完毕
|
||
func loader(_ loader:MP_PlayerResourceLoader, isCached:Bool)
|
||
///检测当前加载器是否请求错误
|
||
func loader(_ loader:MP_PlayerResourceLoader, requestError errorCode:Int)
|
||
}
|
||
typealias CheckStatusBlock = (Int) -> Void
|
||
|
||
///媒体资源加载器
|
||
class MP_PlayerResourceLoader: NSObject, AVAssetResourceLoaderDelegate {
|
||
///传递来的videoId
|
||
private var videoId:String
|
||
///传递来的标题
|
||
private var title:String
|
||
///当前的LoadRequest数组
|
||
private var requestListes:[AVAssetResourceLoadingRequest]!
|
||
///检索状态码闭包
|
||
var checkStatusBlock:CheckStatusBlock?
|
||
//初始化方法
|
||
init(videoId key:String, url:URL, title:String) {
|
||
self.videoId = key
|
||
self.title = title
|
||
super.init()
|
||
//添加监听
|
||
NotificationCenter.notificationKey.add(observer: self, selector: #selector(requestManagerDidReceiveResponseAction(_ :)), notificationName: .asset_response)
|
||
NotificationCenter.notificationKey.add(observer: self, selector: #selector(requestManagerDidReceiveDataAction(_ :)), notificationName: .asset_receiveData)
|
||
//实现代理
|
||
// MP_PlayerTaskManager.shared.delegate = self
|
||
//检索当前请求组数量,如果有,就全部移除,避免重复生成/请求
|
||
if requestListes == nil {
|
||
requestListes = []
|
||
}
|
||
|
||
}
|
||
deinit {
|
||
NotificationCenter.default.removeObserver(self)
|
||
//移除对应的任务
|
||
MP_PlayerTaskManager.shared.removeTask(videoId, title: title)
|
||
}
|
||
//MARK: - AVAssetResourceLoaderDelegate
|
||
///拦截系统的网络请求
|
||
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
|
||
addLoadingRequest(loadingRequest)
|
||
return true
|
||
}
|
||
//取消系统的加载请求
|
||
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
|
||
MP_PlayerTaskManager.shared.accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
guard let index = requestListes.firstIndex(where: { $0 == loadingRequest }) else {
|
||
return
|
||
}
|
||
// 移除找到的请求
|
||
requestListes.remove(at: index)
|
||
}
|
||
}
|
||
//MARK: - 处理系统的LoadingRequest
|
||
///添加LoadingRequest
|
||
private func addLoadingRequest(_ loadingRequest:AVAssetResourceLoadingRequest) {
|
||
MP_PlayerTaskManager.shared.accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
requestListes.append(loadingRequest)
|
||
//获取当前请求的请求范围
|
||
let offset = loadingRequest.dataRequest?.requestedOffset ?? 0
|
||
//判断cacheLengths
|
||
if let cacheLength = MP_PlayerTaskManager.shared.cacheLengths[videoId] {
|
||
//获得已缓存长度
|
||
if (offset >= 0 && offset <= cacheLength) {
|
||
//已缓存数据超过请求范围,直接执行数据补全
|
||
processRequestList(videoId)
|
||
}else {
|
||
//数据还没缓存到请求范围,等待数据下载
|
||
newTaskWithLoadingRequest(loadingRequest)
|
||
}
|
||
}else {
|
||
//数据还未缓存,请求缓存
|
||
newTaskWithLoadingRequest(loadingRequest)
|
||
}
|
||
}
|
||
|
||
}
|
||
///实现互斥锁
|
||
private func synchronized(_ lock: Any, closure: () -> Void) {
|
||
objc_sync_enter(lock)
|
||
closure()
|
||
objc_sync_exit(lock)
|
||
}
|
||
///实现请求任务下载
|
||
private func newTaskWithLoadingRequest(_ loadingRequest:AVAssetResourceLoadingRequest) {
|
||
// var fileLength:Int64 = 0
|
||
// //获取资源长度
|
||
// if let length = MP_PlayerTaskManager.shared.fileLengths[videoId] {
|
||
// fileLength = length
|
||
//// MP_PlayerTaskManager.shared.cancels[videoId] = true
|
||
// }
|
||
guard let requestUrl = loadingRequest.request.url else {return}
|
||
//创建请求任务
|
||
MP_PlayerTaskManager.shared.resumeRequestStart(requestUrl, videoId: videoId, title: title)
|
||
}
|
||
///处理请求组
|
||
private func processRequestList(_ videoId:String) {
|
||
MP_PlayerTaskManager.shared.accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
if self.videoId == videoId {
|
||
var finishRequestList:[AVAssetResourceLoadingRequest] = []
|
||
//是当前videoId,操作请求组
|
||
requestListes.forEach { loadingRequest in
|
||
if self.finishLoadingWithLoadingRequest(loadingRequest, videoId: videoId) {
|
||
finishRequestList.append(loadingRequest)
|
||
}
|
||
}
|
||
//将所有完成的请求移除
|
||
self.requestListes = requestListes.filter({!finishRequestList.contains($0)})
|
||
}
|
||
}
|
||
}
|
||
///检索是否请求完成
|
||
private func finishLoadingWithLoadingRequest(_ loadingRequest: AVAssetResourceLoadingRequest, videoId:String) -> Bool {
|
||
// print("处理\(videoId)的请求信息")
|
||
if let contentInformationRequest = loadingRequest.contentInformationRequest {
|
||
//设置contentInformationRequest
|
||
if let contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (MP_PlayerTaskManager.shared.mimeTypes[videoId] ?? "") as CFString, nil)?.takeRetainedValue() {
|
||
contentInformationRequest.contentType = contentType as String
|
||
}
|
||
if let length = MP_PlayerTaskManager.shared.fileLengths[videoId] {
|
||
contentInformationRequest.contentLength = length
|
||
}
|
||
contentInformationRequest.isByteRangeAccessSupported = true
|
||
}
|
||
//读取文件,填充数据
|
||
if let cacheLength = MP_PlayerTaskManager.shared.cacheLengths[videoId], let dataRequest = loadingRequest.dataRequest{
|
||
var requestedOffset = dataRequest.requestedOffset
|
||
if (dataRequest.currentOffset != 0) {
|
||
//如果当前下载长度不为0
|
||
requestedOffset = dataRequest.currentOffset
|
||
}
|
||
//当前下载长度-要请求的长度
|
||
let canReadLength = cacheLength - requestedOffset
|
||
let respondLength = min(Int(canReadLength), (dataRequest.requestedLength))
|
||
guard let data = MP_CacheAndArchiverManager.shared.readTempFileDataWithOffset(UInt64(requestedOffset), length: respondLength, videoId: videoId) else {return false}
|
||
//对请求加载数据
|
||
dataRequest.respond(with: data)
|
||
//如果完全响应了所需要的数据,则完成
|
||
let nowendOffset = requestedOffset + Int64(respondLength)
|
||
let reqEndOffset = (dataRequest.requestedOffset)+Int64(dataRequest.requestedLength)
|
||
if nowendOffset >= reqEndOffset {
|
||
loadingRequest.finishLoading()
|
||
return true
|
||
}
|
||
}else {
|
||
print("\(videoId)没有加载数据")
|
||
}
|
||
return false
|
||
}
|
||
//MARK: - MP_PlayerTaskDelegate
|
||
//得到服务器响应
|
||
@objc private func requestManagerDidReceiveResponseAction(_ notification:Notification) {
|
||
MP_PlayerTaskManager.shared.accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self, let object = notification.object as? [String:Any] else {return}
|
||
let id = object["videoId"] as? String
|
||
let code = object["statusCode"] as? Int
|
||
if self.videoId == id {
|
||
if let block = checkStatusBlock {
|
||
block(code ?? 0)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//从服务器接收到数据
|
||
@objc private func requestManagerDidReceiveDataAction(_ notification:Notification){
|
||
MP_PlayerTaskManager.shared.accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self, let object = notification.object as? [String:Any] else {return}
|
||
let id = object["videoId"] as? String
|
||
processRequestList(id ?? "")
|
||
}
|
||
}
|
||
/// 缓存结果
|
||
func requestManagerIsCached(_ isCached: Bool, videoId:String){
|
||
if self.videoId == videoId {
|
||
|
||
}
|
||
}
|
||
/// 接收数据完成
|
||
func requestManagerDidComplete(withError errorCode: Int, videoId:String){
|
||
if self.videoId == videoId {
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
///媒体播放器工具配置
|
||
class MP_PlayerToolConfig:NSObject {
|
||
///自定义URL链接
|
||
static func customURL(_ url: URL) -> URL? {
|
||
var urlString = url.absoluteString
|
||
if let range = urlString.range(of: ":") {
|
||
let scheme = String(urlString[..<range.lowerBound])
|
||
let newScheme = scheme + "-streaming"
|
||
urlString = urlString.replacingOccurrences(of: scheme, with: newScheme)
|
||
return URL(string: urlString)
|
||
} else {
|
||
return nil
|
||
}
|
||
}
|
||
///原始URL链接
|
||
static func originalURL(_ url: URL) -> URL? {
|
||
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
|
||
components.scheme = components.scheme?.replacingOccurrences(of: "-streaming", with: "")
|
||
return components.url
|
||
}
|
||
}
|
||
|
||
class MP_PlayerTaskManager:NSObject, URLSessionDataDelegate{
|
||
static var shared = MP_PlayerTaskManager()
|
||
///请求代理
|
||
// weak var delegate:MP_PlayerTaskDelegate?
|
||
//默认的下载队列
|
||
var accessQueue:DispatchQueue = .init(label: "com.relax.offline.mp3.accessQueue")
|
||
//会话Session
|
||
var session:URLSession!
|
||
//路径组
|
||
var urls:[String:URL]!
|
||
//请求组
|
||
var requests:[String:URLRequest]!
|
||
//任务组
|
||
var dataTasks:[String:URLSessionDataTask]!
|
||
//响应体组
|
||
var responses:[String:URLResponse]!
|
||
//资源长度组
|
||
var fileLengths:[String:Int64]!
|
||
//已缓冲的长度组
|
||
var cacheLengths:[String:Int64]!
|
||
//资源类型组
|
||
var mimeTypes:[String:String]!
|
||
//是否取消组
|
||
var cancels:[String:Bool]!
|
||
//保存的下载进度组
|
||
var mediaDatas:[String:Data]!
|
||
//标题组
|
||
var titles:[String:String]!
|
||
//默认任务最大数
|
||
private var maxCount = 4
|
||
override init() {
|
||
super.init()
|
||
//实现会话
|
||
// let operationQueue = OperationQueue()
|
||
// //同时支持最多5个任务并发执行
|
||
// operationQueue.maxConcurrentOperationCount = 4
|
||
let configuration = URLSessionConfiguration.default
|
||
//设置回话超时时间
|
||
configuration.timeoutIntervalForRequest = 30
|
||
configuration.timeoutIntervalForResource = 30
|
||
//实现会话
|
||
session = .init(configuration: configuration, delegate: self, delegateQueue: nil)
|
||
//初始化
|
||
urls = [:]
|
||
requests = [:]
|
||
dataTasks = [:]
|
||
responses = [:]
|
||
fileLengths = [:]
|
||
cacheLengths = [:]
|
||
mimeTypes = [:]
|
||
cancels = [:]
|
||
mediaDatas = [:]
|
||
titles = [:]
|
||
}
|
||
//创建网络请求
|
||
func resumeRequestStart(_ customURL:URL, videoId:String, title:String) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self, let url = MP_PlayerToolConfig.originalURL(customURL) else {return}
|
||
//判断管理器是否已经持有这个请求
|
||
if urls[videoId] == nil {
|
||
urls[videoId] = url
|
||
}
|
||
if titles[videoId] == nil {
|
||
titles[videoId] = title
|
||
}
|
||
//创建请求
|
||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 50)
|
||
if requests[videoId] == nil {
|
||
requests[videoId] = request
|
||
}
|
||
//执行请求
|
||
requestDataTask(request, videoId: videoId, title: title)
|
||
}
|
||
}
|
||
//请求任务
|
||
private func requestDataTask(_ request:URLRequest, videoId:String, title:String) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
guard dataTasks[videoId] == nil else {return}
|
||
//判断当前任务组中是否达到最大值
|
||
// if dataTasks.count >= maxCount {
|
||
// //达到或超出了,移除第一个任务
|
||
// if let first = dataTasks.first {
|
||
//// first.value.cancel()
|
||
// let firstVideoID = first.key
|
||
// let firstTitle = titles[firstVideoID] ?? ""
|
||
// //移除其他相关设置
|
||
// removeTask(firstVideoID, title: firstTitle)
|
||
// }
|
||
// }
|
||
//创建一个临时文件
|
||
let _ = MP_CacheAndArchiverManager.shared.createTempFile(videoId)
|
||
//创建任务
|
||
let dataTask = session.dataTask(with: request)
|
||
dataTasks[videoId] = dataTask
|
||
dataTask.resume()
|
||
}
|
||
}
|
||
//MARK: - URLSessionDataDelegate
|
||
//服务器响应
|
||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
//根据任务获取对应的VideoId
|
||
guard let videoId = dataTasks.first(where: {$0.value == dataTask})?.key, cancels[videoId] != true else {return}
|
||
// print("\(videoId)已连接到服务器")
|
||
if responses[videoId] == nil {
|
||
//更新响应头
|
||
responses[videoId] = response
|
||
}
|
||
guard let httpResponse = response as? HTTPURLResponse else {return}
|
||
//获取状态码
|
||
let statusCode = httpResponse.statusCode
|
||
if statusCode == 200 {
|
||
//成功获取到数据
|
||
if let contentLength = httpResponse.allHeaderFields["Content-Length"] as? String, let contentLengthValue = Int64(contentLength) {
|
||
//跟新对应文件长度
|
||
fileLengths[videoId] = contentLengthValue > 0 ? contentLengthValue : response.expectedContentLength
|
||
} else {
|
||
fileLengths[videoId] = response.expectedContentLength
|
||
}
|
||
//更新文件类型
|
||
if let contentType = httpResponse.allHeaderFields["Content-Type"] as? String {
|
||
mimeTypes[videoId] = contentType
|
||
}else {
|
||
mimeTypes[videoId] = response.mimeType
|
||
}
|
||
}else if (statusCode == 206) {///携带Range,不需要处理
|
||
// if let contentLength = httpResponse.allHeaderFields["Content-Length"] as? String, let contentLengthValue = Int64(contentLength) {
|
||
// //跟新对应文件长度
|
||
// fileLengths[videoId] = contentLengthValue > 0 ? contentLengthValue : response.expectedContentLength
|
||
// } else {
|
||
// fileLengths[videoId] = response.expectedContentLength
|
||
// }
|
||
// //更新文件类型
|
||
// if let contentType = httpResponse.allHeaderFields["Content-Type"] as? String {
|
||
// mimeTypes[videoId] = contentType
|
||
// }else {
|
||
// mimeTypes[videoId] = response.mimeType
|
||
// }
|
||
}else if (statusCode == 403){//访问被拒绝了,通常是用户执行了切换IP的操作,导致油管服务器不准访问(IP限制)
|
||
print("响应码403-\(titles[videoId] ?? "")将重新请求资源")
|
||
//使用videoId重新获取权限数据
|
||
switchURLAction(task: dataTask, videoId: videoId)
|
||
}else if (statusCode == 416) {//超出文件大小,手动重试
|
||
print("\(titles[videoId] ?? "")请求范围超出文件大小")
|
||
retryTask(task: dataTask)
|
||
}else {
|
||
print("\(titles[videoId] ?? "")响应出错了,任务取消,状态码为\(statusCode)")
|
||
}
|
||
//执行代理
|
||
NotificationCenter.notificationKey.post(notificationName: .asset_response, object: ["statusCode":statusCode,"videoId":videoId])
|
||
// self.delegate?.requestManagerDidReceiveResponse(withStatusCode: statusCode, videoId: videoId)
|
||
//允许继续响应
|
||
completionHandler(.allow)
|
||
}
|
||
}
|
||
//服务器返回数据(多次调用)
|
||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
guard let videoId = dataTasks.first(where: {$0.value == dataTask})?.key, cancels[videoId] != true else {return}
|
||
//获取相应到的数据
|
||
let currentLength = self.cacheLengths[videoId] ?? 0
|
||
self.cacheLengths[videoId] = currentLength + Int64(data.count)
|
||
//根据videoId,写入相应的临时缓冲文件
|
||
MP_CacheAndArchiverManager.shared.writeDataToAudioFileTempPathWithData(data, videoId: videoId)
|
||
//回调下载事件代理
|
||
// self.delegate?.requestManagerDidReceiveData(videoId)
|
||
NotificationCenter.notificationKey.post(notificationName: .asset_receiveData, object: ["videoId":videoId])
|
||
}
|
||
}
|
||
//请求完成会调用该方法,请求失败则error有值
|
||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
guard let videoId = dataTasks.first(where: {$0.value == task})?.key, cancels[videoId] != true, let title = titles[videoId] else {return}
|
||
//检索是否存在报错
|
||
if let error = error {
|
||
//触发报错,打印报错内容,并重新调用
|
||
print("下载\(title)报错,错误详情:\(error.localizedDescription)")
|
||
//处理错误
|
||
if let urlError = error as? URLError {
|
||
switch urlError.code {
|
||
case .notConnectedToInternet://网络连接错误
|
||
print("网络连接错误")
|
||
case .timedOut://链接超时
|
||
print("\(title)链接超时")
|
||
//启用重试机制
|
||
retryTask(task: task)
|
||
case .cannotConnectToHost://服务器未响应
|
||
print("\(title)链接服务器未响应")
|
||
//启用重试机制
|
||
retryTask(task: task)
|
||
case .secureConnectionFailed://证书失效
|
||
print("\(title)链接证书失效")
|
||
//证书失效了
|
||
retryTask(task: task)
|
||
case .networkConnectionLost://链接中断
|
||
print("\(title)下载中断了")
|
||
//启用重试机制
|
||
retryTask(task: task)
|
||
case .cancelled:
|
||
print("\(title)下载取消了")
|
||
//启用重试机制
|
||
retryTask(task: task)
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
}else {
|
||
//没有报错,下载完成
|
||
let success = MP_CacheAndArchiverManager.shared.moveAudioFileFromTempPathToCachePath(videoId)
|
||
// self.delegate?.requestManagerIsCached(success, videoId: videoId)
|
||
NotificationCenter.notificationKey.post(notificationName: .asset_isCached, object: ["videoId":videoId])
|
||
}
|
||
}
|
||
}
|
||
//重试机制
|
||
private func retryTask(task:URLSessionTask) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
//获取videoID
|
||
guard let videoId = dataTasks.first(where: {$0.value == task})?.key, let title = titles[videoId] else {return}
|
||
// 根据需要设定重试延时,例如使用指数退避策略
|
||
let retryDelay:Double = 3 // 每次延时3秒
|
||
accessQueue.asyncAfter(deadline: .now() + retryDelay) {
|
||
[weak self] in
|
||
//尝试重试
|
||
print("\(title)尝试重试")
|
||
guard let self = self, var request = requests[videoId] else {return}
|
||
//假如cacheLength有值,那说明是断点传续
|
||
if let cacheLength = cacheLengths[videoId] {
|
||
var range:String!
|
||
if let filePathLength = fileLengths[videoId] {
|
||
if cacheLength < filePathLength {
|
||
//计算最后的值
|
||
range = "bytes=\(cacheLength)-\(filePathLength-1)"
|
||
request.setValue(range, forHTTPHeaderField: "Range")
|
||
}
|
||
}else {
|
||
range = "bytes=\(cacheLength)-"
|
||
//创建任务
|
||
request.setValue(range, forHTTPHeaderField: "Range")
|
||
}
|
||
}
|
||
let dataTask = session.dataTask(with: request)
|
||
requests[videoId] = request
|
||
dataTasks[videoId] = dataTask
|
||
dataTask.resume()
|
||
}
|
||
}
|
||
}
|
||
//403事件切换URL
|
||
private func switchURLAction(task:URLSessionTask, videoId:String) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
task.cancel()
|
||
MP_NetWorkManager.shared.requestAndroidPlayer(videoId, playlistId: "") { [weak self] results, cs in
|
||
//只需要获取results第一位,也就是新的资源路径
|
||
guard let self = self, let first = results?.0.first else {return}
|
||
//通知更新
|
||
NotificationCenter.notificationKey.post(notificationName: .player_asset_403, object: first)
|
||
guard let url = URL(string: first) else {return}
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
//取消原来的任务
|
||
if dataTasks[videoId] != nil {
|
||
dataTasks[videoId] = nil
|
||
}
|
||
//成功创建了新的资源路径,创建新的请求
|
||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 50)
|
||
//假如cacheLength有值,那说明是断点传续
|
||
if let cacheLength = cacheLengths[videoId] {
|
||
var range:String!
|
||
if let filePathLength = fileLengths[videoId] {
|
||
if cacheLength < filePathLength {
|
||
//计算最后的值
|
||
range = "bytes=\(cacheLength)-\(filePathLength-1)"
|
||
request.setValue(range, forHTTPHeaderField: "Range")
|
||
}
|
||
}else {
|
||
range = "bytes=\(cacheLength)-"
|
||
//创建任务
|
||
request.setValue(range, forHTTPHeaderField: "Range")
|
||
}
|
||
}
|
||
//创建新的任务
|
||
let dataTask = session.dataTask(with: request)
|
||
requests[videoId] = request
|
||
dataTasks[videoId] = dataTask
|
||
dataTask.resume()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//下载任务移除
|
||
func removeTask(_ videoId:String, title:String) {
|
||
accessQueue.async {
|
||
[weak self] in
|
||
guard let self = self else {return}
|
||
urls[videoId] = nil
|
||
requests[videoId] = nil
|
||
dataTasks[videoId]?.cancel()
|
||
dataTasks[videoId] = nil
|
||
responses[videoId] = nil
|
||
fileLengths[videoId] = nil
|
||
cacheLengths[videoId] = nil
|
||
mimeTypes[videoId] = nil
|
||
cancels[videoId] = nil
|
||
print("移除了\(title)")
|
||
}
|
||
}
|
||
}
|