Handle the player cache

dev
wenlei 10 months ago
parent 5146380302
commit 815ee05c7f

@ -121,6 +121,12 @@
778B8AC12AF8ED280034AFD4 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB82AF8ED280034AFD4 /* TableView.swift */; }; 778B8AC12AF8ED280034AFD4 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB82AF8ED280034AFD4 /* TableView.swift */; };
778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB92AF8ED280034AFD4 /* TableViewController.swift */; }; 778B8AC22AF8ED280034AFD4 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8AB92AF8ED280034AFD4 /* TableViewController.swift */; };
778B8AC32AF8ED280034AFD4 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */; }; 778B8AC32AF8ED280034AFD4 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */; };
77A60D7C2B5B976900D4E435 /* ResourceLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A60D7B2B5B976900D4E435 /* ResourceLoader.swift */; };
77A60D7E2B5B977D00D4E435 /* ResourceLoaderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A60D7D2B5B977D00D4E435 /* ResourceLoaderRequest.swift */; };
77A60D802B5B979000D4E435 /* AssetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A60D7F2B5B979000D4E435 /* AssetData.swift */; };
77A60D822B5B97A100D4E435 /* AssetDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A60D812B5B97A100D4E435 /* AssetDataManager.swift */; };
77A60D842B5B97B500D4E435 /* PINCacheAssetDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A60D832B5B97B500D4E435 /* PINCacheAssetDataManager.swift */; };
77A60D862B5B97C300D4E435 /* CachingAVURLAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A60D852B5B97C300D4E435 /* CachingAVURLAsset.swift */; };
77A659CF2B4FDC4100B408C3 /* PullToDismissable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A659CE2B4FDC4100B408C3 /* PullToDismissable.swift */; }; 77A659CF2B4FDC4100B408C3 /* PullToDismissable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A659CE2B4FDC4100B408C3 /* PullToDismissable.swift */; };
77A659D12B4FDC5200B408C3 /* PullToDismissTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A659D02B4FDC5200B408C3 /* PullToDismissTransition.swift */; }; 77A659D12B4FDC5200B408C3 /* PullToDismissTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A659D02B4FDC5200B408C3 /* PullToDismissTransition.swift */; };
77A659DF2B51023200B408C3 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A659DE2B51023100B408C3 /* TestViewController.swift */; }; 77A659DF2B51023200B408C3 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A659DE2B51023100B408C3 /* TestViewController.swift */; };
@ -331,6 +337,12 @@
778B8AB82AF8ED280034AFD4 /* TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; }; 778B8AB82AF8ED280034AFD4 /* TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
778B8AB92AF8ED280034AFD4 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; }; 778B8AB92AF8ED280034AFD4 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; }; 778B8ABA2AF8ED280034AFD4 /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = "<group>"; };
77A60D7B2B5B976900D4E435 /* ResourceLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceLoader.swift; sourceTree = "<group>"; };
77A60D7D2B5B977D00D4E435 /* ResourceLoaderRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceLoaderRequest.swift; sourceTree = "<group>"; };
77A60D7F2B5B979000D4E435 /* AssetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetData.swift; sourceTree = "<group>"; };
77A60D812B5B97A100D4E435 /* AssetDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetDataManager.swift; sourceTree = "<group>"; };
77A60D832B5B97B500D4E435 /* PINCacheAssetDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINCacheAssetDataManager.swift; sourceTree = "<group>"; };
77A60D852B5B97C300D4E435 /* CachingAVURLAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachingAVURLAsset.swift; sourceTree = "<group>"; };
77A659CE2B4FDC4100B408C3 /* PullToDismissable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToDismissable.swift; sourceTree = "<group>"; }; 77A659CE2B4FDC4100B408C3 /* PullToDismissable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToDismissable.swift; sourceTree = "<group>"; };
77A659D02B4FDC5200B408C3 /* PullToDismissTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToDismissTransition.swift; sourceTree = "<group>"; }; 77A659D02B4FDC5200B408C3 /* PullToDismissTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToDismissTransition.swift; sourceTree = "<group>"; };
77A659DE2B51023100B408C3 /* TestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestViewController.swift; sourceTree = "<group>"; }; 77A659DE2B51023100B408C3 /* TestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestViewController.swift; sourceTree = "<group>"; };
@ -696,6 +708,7 @@
778B8A5A2AF8EAFD0034AFD4 /* Third Party */ = { 778B8A5A2AF8EAFD0034AFD4 /* Third Party */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
77A60D7A2B5B975600D4E435 /* ResourceLoader */,
770228F22B5A224500E07F7A /* SliderControl */, 770228F22B5A224500E07F7A /* SliderControl */,
770228E32B54FA3600E07F7A /* RxActivityIndicator */, 770228E32B54FA3600E07F7A /* RxActivityIndicator */,
778B8A602AF8ECC20034AFD4 /* RxErrorTracker */, 778B8A602AF8ECC20034AFD4 /* RxErrorTracker */,
@ -838,6 +851,19 @@
path = "RxSwift+Extension"; path = "RxSwift+Extension";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
77A60D7A2B5B975600D4E435 /* ResourceLoader */ = {
isa = PBXGroup;
children = (
77A60D7B2B5B976900D4E435 /* ResourceLoader.swift */,
77A60D7D2B5B977D00D4E435 /* ResourceLoaderRequest.swift */,
77A60D7F2B5B979000D4E435 /* AssetData.swift */,
77A60D812B5B97A100D4E435 /* AssetDataManager.swift */,
77A60D832B5B97B500D4E435 /* PINCacheAssetDataManager.swift */,
77A60D852B5B97C300D4E435 /* CachingAVURLAsset.swift */,
);
path = ResourceLoader;
sourceTree = "<group>";
};
77C9B9CB2B4B93D10006C83F /* Personal */ = { 77C9B9CB2B4B93D10006C83F /* Personal */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1147,6 +1173,7 @@
770228E52B54FA3600E07F7A /* ActivityIndicator.swift in Sources */, 770228E52B54FA3600E07F7A /* ActivityIndicator.swift in Sources */,
7751D3722B43B9ED00F1F2BD /* Search.swift in Sources */, 7751D3722B43B9ED00F1F2BD /* Search.swift in Sources */,
774A18072B06045200F56DF1 /* HomeView.swift in Sources */, 774A18072B06045200F56DF1 /* HomeView.swift in Sources */,
77A60D802B5B979000D4E435 /* AssetData.swift in Sources */,
77FAF7642B075FEB00FC2CA1 /* JournalDetailViewModel.swift in Sources */, 77FAF7642B075FEB00FC2CA1 /* JournalDetailViewModel.swift in Sources */,
778B8AB12AF8ED1B0034AFD4 /* Configs.swift in Sources */, 778B8AB12AF8ED1B0034AFD4 /* Configs.swift in Sources */,
77FA0B482B0DFB0E00404C5E /* EditBindPhoneViewController.swift in Sources */, 77FA0B482B0DFB0E00404C5E /* EditBindPhoneViewController.swift in Sources */,
@ -1192,8 +1219,10 @@
778B8ABF2AF8ED280034AFD4 /* NavigationController.swift in Sources */, 778B8ABF2AF8ED280034AFD4 /* NavigationController.swift in Sources */,
774399AA2AFE3170006F8EEA /* PaddingLabel.swift in Sources */, 774399AA2AFE3170006F8EEA /* PaddingLabel.swift in Sources */,
77FB7A702B48074600B64030 /* MusicStyleViewModel.swift in Sources */, 77FB7A702B48074600B64030 /* MusicStyleViewModel.swift in Sources */,
77A60D7E2B5B977D00D4E435 /* ResourceLoaderRequest.swift in Sources */,
770228ED2B55284F00E07F7A /* Login.swift in Sources */, 770228ED2B55284F00E07F7A /* Login.swift in Sources */,
7751D3502B42ABBF00F1F2BD /* SettingViewController.swift in Sources */, 7751D3502B42ABBF00F1F2BD /* SettingViewController.swift in Sources */,
77A60D842B5B97B500D4E435 /* PINCacheAssetDataManager.swift in Sources */,
77A659DF2B51023200B408C3 /* TestViewController.swift in Sources */, 77A659DF2B51023200B408C3 /* TestViewController.swift in Sources */,
77FA0B4E2B0EF8C700404C5E /* PhoneCodeViewModel.swift in Sources */, 77FA0B4E2B0EF8C700404C5E /* PhoneCodeViewModel.swift in Sources */,
778B8A822AF8ECE50034AFD4 /* HomeViewModel.swift in Sources */, 778B8A822AF8ECE50034AFD4 /* HomeViewModel.swift in Sources */,
@ -1265,6 +1294,7 @@
778B8AA92AF8ED0E0034AFD4 /* UIImage+IndieMusic.swift in Sources */, 778B8AA92AF8ED0E0034AFD4 /* UIImage+IndieMusic.swift in Sources */,
77C9B9D12B4B99600006C83F /* BadgeButton.swift in Sources */, 77C9B9D12B4B99600006C83F /* BadgeButton.swift in Sources */,
778B8A7F2AF8ECE50034AFD4 /* MineViewModel.swift in Sources */, 778B8A7F2AF8ECE50034AFD4 /* MineViewModel.swift in Sources */,
77A60D862B5B97C300D4E435 /* CachingAVURLAsset.swift in Sources */,
778B8A9A2AF8ECFC0034AFD4 /* AudioManager.swift in Sources */, 778B8A9A2AF8ECFC0034AFD4 /* AudioManager.swift in Sources */,
774A180E2B07000C00F56DF1 /* JournalDetailView.swift in Sources */, 774A180E2B07000C00F56DF1 /* JournalDetailView.swift in Sources */,
778B8A842AF8ECE50034AFD4 /* HomeTabBarController.swift in Sources */, 778B8A842AF8ECE50034AFD4 /* HomeTabBarController.swift in Sources */,
@ -1293,10 +1323,12 @@
77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */, 77FA0B3E2B0C573600404C5E /* Filter.swift in Sources */,
77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */, 77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */,
7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */, 7751D3662B42BE7F00F1F2BD /* FeedbackViewController.swift in Sources */,
77A60D822B5B97A100D4E435 /* AssetDataManager.swift in Sources */,
77FB7A7B2B4A4FC900B64030 /* MessageViewController.swift in Sources */, 77FB7A7B2B4A4FC900B64030 /* MessageViewController.swift in Sources */,
77FB7A722B48E93100B64030 /* SearchResultsViewModel.swift in Sources */, 77FB7A722B48E93100B64030 /* SearchResultsViewModel.swift in Sources */,
774A18142B07329600F56DF1 /* MultiUserAvatarView.swift in Sources */, 774A18142B07329600F56DF1 /* MultiUserAvatarView.swift in Sources */,
778B8AAA2AF8ED0E0034AFD4 /* AVPlayer.swift in Sources */, 778B8AAA2AF8ED0E0034AFD4 /* AVPlayer.swift in Sources */,
77A60D7C2B5B976900D4E435 /* ResourceLoader.swift in Sources */,
77FA0B442B0DFABD00404C5E /* LoginView.swift in Sources */, 77FA0B442B0DFABD00404C5E /* LoginView.swift in Sources */,
77FB7A7D2B4A4FD400B64030 /* MessageViewModel.swift in Sources */, 77FB7A7D2B4A4FD400B64030 /* MessageViewModel.swift in Sources */,
7743999E2AFA18C3006F8EEA /* PlayerViewController.swift in Sources */, 7743999E2AFA18C3006F8EEA /* PlayerViewController.swift in Sources */,

@ -80,7 +80,7 @@ class AudioManager {
try AVAudioSession.sharedInstance().setMode(.default) try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation) try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
let playerItem: AVPlayerItem = AVPlayerItem(url: url) let playerItem: AVPlayerItem = AVPlayerItem.init(asset: CachingAVURLAsset(url: url))
player?.pause() player?.pause()
player = AVPlayer(playerItem: playerItem) player = AVPlayer(playerItem: playerItem)
@ -90,17 +90,17 @@ class AudioManager {
self.currentTrack = track self.currentTrack = track
NotificationCenter.default.post(name: .notiPlayAudioTrack, object: track) NotificationCenter.default.post(name: .notiPlayAudioTrack, object: track)
setLockScreenDisplay(track: track)
// var nowPlayingInfo = [String: Any]()
var nowPlayingInfo = [String: Any]() // nowPlayingInfo[MPMediaItemPropertyTitle] = ""
nowPlayingInfo[MPMediaItemPropertyTitle] = "歌曲标题" // nowPlayingInfo[MPMediaItemPropertyArtist] = ""
nowPlayingInfo[MPMediaItemPropertyArtist] = "艺术家名称" // nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = 100
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = 100 // nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = 200
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = 200 // nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1 //
// MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo //
} catch { } catch {
@ -202,4 +202,17 @@ class AudioManager {
} }
} }
func setLockScreenDisplay(track: AudioTrack) {
var info = Dictionary<String, Any>()
info[MPMediaItemPropertyTitle] = track.title//
info[MPMediaItemPropertyArtist] = track.artist//
// [info setObject:self.model.filename forKey:MPMediaItemPropertyAlbumTitle];//
info[MPMediaItemPropertyAlbumArtist] = track.artist//
// info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image: UIImage.init(named: track.pic!))//
info[MPMediaItemPropertyPlaybackDuration] = 200
info[MPNowPlayingInfoPropertyPlaybackRate] = 1.0//
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
} }

@ -0,0 +1,52 @@
//
// AssetData.swift
// IndieMusic
//
// Created by WenLei on 2024/1/20.
//
import Foundation
import CryptoKit
class AssetDataContentInformation: NSObject, NSCoding {
@objc var contentLength: Int64 = 0
@objc var contentType: String = ""
@objc var isByteRangeAccessSupported: Bool = false
func encode(with coder: NSCoder) {
coder.encode(self.contentLength, forKey: #keyPath(AssetDataContentInformation.contentLength))
coder.encode(self.contentType, forKey: #keyPath(AssetDataContentInformation.contentType))
coder.encode(self.isByteRangeAccessSupported, forKey: #keyPath(AssetDataContentInformation.isByteRangeAccessSupported))
}
override init() {
super.init()
}
required init?(coder: NSCoder) {
super.init()
self.contentLength = coder.decodeInt64(forKey: #keyPath(AssetDataContentInformation.contentLength))
self.contentType = coder.decodeObject(forKey: #keyPath(AssetDataContentInformation.contentType)) as? String ?? ""
self.isByteRangeAccessSupported = coder.decodeObject(forKey: #keyPath(AssetDataContentInformation.isByteRangeAccessSupported)) as? Bool ?? false
}
}
class AssetData: NSObject, NSCoding {
@objc var contentInformation: AssetDataContentInformation = AssetDataContentInformation()
@objc var mediaData: Data = Data()
override init() {
super.init()
}
func encode(with coder: NSCoder) {
coder.encode(self.contentInformation, forKey: #keyPath(AssetData.contentInformation))
coder.encode(self.mediaData, forKey: #keyPath(AssetData.mediaData))
}
required init?(coder: NSCoder) {
super.init()
self.contentInformation = coder.decodeObject(forKey: #keyPath(AssetData.contentInformation)) as? AssetDataContentInformation ?? AssetDataContentInformation()
self.mediaData = coder.decodeObject(forKey: #keyPath(AssetData.mediaData)) as? Data ?? Data()
}
}

@ -0,0 +1,27 @@
//
// AssetDataManager.swift
// IndieMusic
//
// Created by WenLei on 2024/1/20.
//
import Foundation
protocol AssetDataManager: NSObject {
func retrieveAssetData() -> AssetData?
func saveContentInformation(_ contentInformation: AssetDataContentInformation)
func saveDownloadedData(_ data: Data, offset: Int)
func mergeDownloadedDataIfIsContinuted(from: Data, with: Data, offset: Int) -> Data?
}
extension AssetDataManager {
func mergeDownloadedDataIfIsContinuted(from: Data, with: Data, offset: Int) -> Data? {
if offset <= from.count && (offset + with.count) > from.count {
let start = from.count - offset
var data = from
data.append(with.subdata(in: start..<with.count))
return data
}
return nil
}
}

@ -0,0 +1,49 @@
//
// CachingAVURLAsset.swift
// IndieMusic
//
// Created by WenLei on 2024/1/20.
//
import AVFoundation
import Foundation
class CachingAVURLAsset: AVURLAsset {
static let customScheme = "cachingPlayerItemScheme"
let originalURL: URL
private var _resourceLoader: ResourceLoader?
var cacheKey: String {
return self.url.lastPathComponent
}
static func isSchemeSupport(_ url: URL) -> Bool {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return false
}
return ["http", "https"].contains(components.scheme)
}
override init(url URL: URL, options: [String: Any]? = nil) {
self.originalURL = URL
guard var components = URLComponents(url: URL, resolvingAgainstBaseURL: false) else {
super.init(url: URL, options: options)
return
}
components.scheme = CachingAVURLAsset.customScheme
guard let url = components.url else {
super.init(url: URL, options: options)
return
}
super.init(url: url, options: options)
let resourceLoader = ResourceLoader(asset: self)
self.resourceLoader.setDelegate(resourceLoader, queue: resourceLoader.loaderQueue)
self._resourceLoader = resourceLoader
}
}

@ -0,0 +1,46 @@
//
// PINCacheAssetDataManager.swift
// IndieMusic
//
// Created by WenLei on 2024/1/20.
//
import PINCache
import Foundation
class PINCacheAssetDataManager: NSObject, AssetDataManager {
static let Cache: PINCache = PINCache(name: "ResourceLoader")
let cacheKey: String
init(cacheKey: String) {
self.cacheKey = cacheKey
super.init()
}
func saveContentInformation(_ contentInformation: AssetDataContentInformation) {
let assetData = AssetData()
assetData.contentInformation = contentInformation
PINCacheAssetDataManager.Cache.setObjectAsync(assetData, forKey: cacheKey, completion: nil)
}
func saveDownloadedData(_ data: Data, offset: Int) {
guard let assetData = self.retrieveAssetData() else {
return
}
if let mediaData = self.mergeDownloadedDataIfIsContinuted(from: assetData.mediaData, with: data, offset: offset) {
assetData.mediaData = mediaData
PINCacheAssetDataManager.Cache.setObjectAsync(assetData, forKey: cacheKey, completion: nil)
}
}
func retrieveAssetData() -> AssetData? {
guard let assetData = PINCacheAssetDataManager.Cache.object(forKey: cacheKey) as? AssetData else {
return nil
}
return assetData
}
}

@ -0,0 +1,151 @@
//
// ResourceLoader.swift
// IndieMusic
//
// Created by WenLei on 2024/1/20.
//
import AVFoundation
import Foundation
class ResourceLoader: NSObject {
let loaderQueue = DispatchQueue(label: "li.zhgchg.resourceLoader.queue")
private var requests: [AVAssetResourceLoadingRequest: ResourceLoaderRequest] = [:]
private let cacheKey: String
private let originalURL: URL
init(asset: CachingAVURLAsset) {
self.cacheKey = asset.cacheKey
self.originalURL = asset.originalURL
super.init()
}
deinit {
self.requests.forEach { (request) in
request.value.cancel()
}
}
}
extension ResourceLoader: AVAssetResourceLoaderDelegate {
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
let type = ResourceLoader.resourceLoaderRequestType(loadingRequest)
let assetDataManager = PINCacheAssetDataManager(cacheKey: self.cacheKey)
if let assetData = assetDataManager.retrieveAssetData() {
if type == .contentInformation {
loadingRequest.contentInformationRequest?.contentLength = assetData.contentInformation.contentLength
loadingRequest.contentInformationRequest?.contentType = assetData.contentInformation.contentType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = assetData.contentInformation.isByteRangeAccessSupported
loadingRequest.finishLoading()
return true
} else {
let range = ResourceLoader.resourceLoaderRequestRange(type, loadingRequest)
if assetData.mediaData.count > 0 {
let end: Int64
switch range.end {
case .requestTo(let rangeEnd):
end = rangeEnd
case .requestToEnd:
end = assetData.contentInformation.contentLength
}
if assetData.mediaData.count >= end {
let subData = assetData.mediaData.subdata(in: Int(range.start)..<Int(end))
loadingRequest.dataRequest?.respond(with: subData)
loadingRequest.finishLoading()
return true
} else if range.start <= assetData.mediaData.count {
// has cache data...but not enough
let subEnd = (assetData.mediaData.count > end) ? Int((end)) : (assetData.mediaData.count)
let subData = assetData.mediaData.subdata(in: Int(range.start)..<subEnd)
loadingRequest.dataRequest?.respond(with: subData)
}
}
}
}
let range = ResourceLoader.resourceLoaderRequestRange(type, loadingRequest)
let resourceLoaderRequest = ResourceLoaderRequest(originalURL: self.originalURL, type: type, loaderQueue: self.loaderQueue, assetDataManager: assetDataManager)
resourceLoaderRequest.delegate = self
self.requests[loadingRequest]?.cancel()
self.requests[loadingRequest] = resourceLoaderRequest
resourceLoaderRequest.start(requestRange: range)
return true
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
guard let resourceLoaderRequest = self.requests[loadingRequest] else {
return
}
resourceLoaderRequest.cancel()
requests.removeValue(forKey: loadingRequest)
}
}
extension ResourceLoader: ResourceLoaderRequestDelegate {
func contentInformationDidComplete(_ resourceLoaderRequest: ResourceLoaderRequest, _ result: Result<AssetDataContentInformation, Error>) {
guard let loadingRequest = self.requests.first(where: { $0.value == resourceLoaderRequest })?.key else {
return
}
switch result {
case .success(let contentInformation):
loadingRequest.contentInformationRequest?.contentType = contentInformation.contentType
loadingRequest.contentInformationRequest?.contentLength = contentInformation.contentLength
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = contentInformation.isByteRangeAccessSupported
loadingRequest.finishLoading()
case .failure(let error):
loadingRequest.finishLoading(with: error)
}
}
func dataRequestDidReceive(_ resourceLoaderRequest: ResourceLoaderRequest, _ data: Data) {
guard let loadingRequest = self.requests.first(where: { $0.value == resourceLoaderRequest })?.key else {
return
}
loadingRequest.dataRequest?.respond(with: data)
}
func dataRequestDidComplete(_ resourceLoaderRequest: ResourceLoaderRequest, _ error: Error?, _ downloadedData: Data) {
guard let loadingRequest = self.requests.first(where: { $0.value == resourceLoaderRequest })?.key else {
return
}
loadingRequest.finishLoading(with: error)
requests.removeValue(forKey: loadingRequest)
}
}
extension ResourceLoader {
static func resourceLoaderRequestType(_ loadingRequest: AVAssetResourceLoadingRequest) -> ResourceLoaderRequest.RequestType {
if let _ = loadingRequest.contentInformationRequest {
return .contentInformation
} else {
return .dataRequest
}
}
static func resourceLoaderRequestRange(_ type: ResourceLoaderRequest.RequestType, _ loadingRequest: AVAssetResourceLoadingRequest) -> ResourceLoaderRequest.RequestRange {
if type == .contentInformation {
return ResourceLoaderRequest.RequestRange(start: 0, end: .requestTo(1))
} else {
if loadingRequest.dataRequest?.requestsAllDataToEndOfResource == true {
let lowerBound = loadingRequest.dataRequest?.currentOffset ?? 0
return ResourceLoaderRequest.RequestRange(start: lowerBound, end: .requestToEnd)
} else {
let lowerBound = loadingRequest.dataRequest?.currentOffset ?? 0
let length = Int64(loadingRequest.dataRequest?.requestedLength ?? 1)
let upperBound = lowerBound + length
return ResourceLoaderRequest.RequestRange(start: lowerBound, end: .requestTo(upperBound))
}
}
}
}

@ -0,0 +1,167 @@
//
// ResourceLoaderRequest.swift
// IndieMusic
//
// Created by WenLei on 2024/1/20.
//
import Foundation
import CoreServices
protocol ResourceLoaderRequestDelegate: AnyObject {
func dataRequestDidReceive(_ resourceLoaderRequest: ResourceLoaderRequest, _ data: Data)
func dataRequestDidComplete(_ resourceLoaderRequest: ResourceLoaderRequest, _ error: Error?, _ downloadedData: Data)
func contentInformationDidComplete(_ resourceLoaderRequest: ResourceLoaderRequest, _ result: Result<AssetDataContentInformation, Error>)
}
class ResourceLoaderRequest: NSObject, URLSessionDataDelegate {
struct RequestRange {
var start: Int64
var end: RequestRangeEnd
enum RequestRangeEnd {
case requestTo(Int64)
case requestToEnd
}
}
enum RequestType {
case contentInformation
case dataRequest
}
struct ResponseUnExpectedError: Error { }
private let loaderQueue: DispatchQueue
let originalURL: URL
let type: RequestType
private var session: URLSession?
private var dataTask: URLSessionDataTask?
private var assetDataManager: AssetDataManager?
private(set) var requestRange: RequestRange?
private(set) var response: URLResponse?
private(set) var downloadedData: Data = Data()
private(set) var isCancelled: Bool = false {
didSet {
if isCancelled {
self.dataTask?.cancel()
self.session?.invalidateAndCancel()
}
}
}
private(set) var isFinished: Bool = false {
didSet {
if isFinished {
self.session?.finishTasksAndInvalidate()
}
}
}
weak var delegate: ResourceLoaderRequestDelegate?
init(originalURL: URL, type: RequestType, loaderQueue: DispatchQueue, assetDataManager: AssetDataManager?) {
self.originalURL = originalURL
self.type = type
self.loaderQueue = loaderQueue
self.assetDataManager = assetDataManager
super.init()
}
func start(requestRange: RequestRange) {
guard isCancelled == false, isFinished == false else {
return
}
self.loaderQueue.async { [weak self] in
guard let self = self else {
return
}
var request = URLRequest(url: self.originalURL)
self.requestRange = requestRange
let start = String(requestRange.start)
let end: String
switch requestRange.end {
case .requestTo(let rangeEnd):
end = String(rangeEnd)
case .requestToEnd:
end = ""
}
let rangeHeader = "bytes=\(start)-\(end)"
request.setValue(rangeHeader, forHTTPHeaderField: "Range")
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
self.session = session
let dataTask = session.dataTask(with: request)
self.dataTask = dataTask
dataTask.resume()
}
}
func cancel() {
self.isCancelled = true
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard self.type == .dataRequest else {
return
}
self.loaderQueue.async {
self.delegate?.dataRequestDidReceive(self, data)
self.downloadedData.append(data)
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
self.response = response
completionHandler(.allow)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
self.isFinished = true
self.loaderQueue.async {
if self.type == .contentInformation {
guard error == nil,
let response = self.response as? HTTPURLResponse else {
let responseError = error ?? ResponseUnExpectedError()
self.delegate?.contentInformationDidComplete(self, .failure(responseError))
return
}
let contentInformation = AssetDataContentInformation()
if let rangeString = response.allHeaderFields["Content-Range"] as? String,
let bytesString = rangeString.split(separator: "/").map({String($0)}).last,
let bytes = Int64(bytesString) {
contentInformation.contentLength = bytes
}
if let mimeType = response.mimeType,
let contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as CFString, nil)?.takeRetainedValue() {
contentInformation.contentType = contentType as String
}
if let value = response.allHeaderFields["Accept-Ranges"] as? String,
value == "bytes" {
contentInformation.isByteRangeAccessSupported = true
} else {
contentInformation.isByteRangeAccessSupported = false
}
self.assetDataManager?.saveContentInformation(contentInformation)
self.delegate?.contentInformationDidComplete(self, .success(contentInformation))
} else {
if let offset = self.requestRange?.start, self.downloadedData.count > 0 {
self.assetDataManager?.saveDownloadedData(self.downloadedData, offset: Int(offset))
}
self.delegate?.dataRequestDidComplete(self, error, self.downloadedData)
}
}
}
}

@ -26,6 +26,7 @@ target 'IndieMusic' do
pod 'IQKeyboardManagerSwift' pod 'IQKeyboardManagerSwift'
pod 'lottie-ios' pod 'lottie-ios'
pod 'MarqueeLabel' pod 'MarqueeLabel'
pod 'PINCache'
pod 'NSObject+Rx' pod 'NSObject+Rx'

Loading…
Cancel
Save