iOS

[Swift] AVAssetDownLoadTask 사용하기

스엠 2023. 12. 3. 19:07

이전 글까지는 proxyServer를 통하여 HLS 미디어를 다운받는 방법에 대해서 기술하고 있었는데(시간 나면 계속 쓸 예정입니다.),
글쓴지도 좀 오래됬기도 하고 HLS를 다른 방법으로 캐싱하는 방법이 있어 소개해드리려 오랜만에 글을 쓰게 되었습니다.

바로 AVAssetDownLoadTask를 이용하는 방법입니다.
우선 결론적으로 말하면 완전히 실시간으로 진행되는 스트림은 결국은 proxyserver를 이용해야 하며 AVAssetDownloadTask로는 캐싱처리를 하지 못합니다.


1. AVAssetDownLoadTask VS AVAggregateAssetDownloadTask

관련된 것을 찾아보시면 이렇게 2가지가 나올건데요. 간단하게 어떤것이 다른지 비교만 하고 넘어가겠습니다.

AVAssetDownLoadTask : HLS 스트림을 타입별로 단일 미디어 selection만 다운로드 받을 수 있습니다.
AVAggregateAssetDownloadTask : 여러 미디어 selections를 다운로드 받을 수 있습니다.

그러면 여기서 미디어란 무엇일까요?

미디어는 말 그대로 미디어 콘텐츠의 구성요소입니다.
비디오파일,
오디오파일,
자막
등등이 있습니다.

따라서 AVAssetDownLoadTask은 저런것들이 제공된다고 할때 하나한 연속적으로 다운을 받아야 하지만
AVAggregateAssetDownloadTask경우 한번 옵션처리를 하여 그룹으로 다운 받을 수 있습니다.
그래서 사실 왠만하면 AVAggregateAssetDownloadTask를 쓰지 않을까 싶습니다만, iOS11까지 지원해야해서 AVAssetDownLoadTask 를 썻고 이렇게 글을 쓰게 되었네요..

2. AVAssetDownLoadTask 쓰는 법

코드 먼저 보여드리고 가겠습니다.

fileprivate class AVAssetDownLoader  {
    
    private var configuration : URLSessionConfiguration?
    private var downloadSession : AVAssetDownloadURLSession?
    private var urlString : String?
    private var asset : AVURLAsset?
    private var downLoadTask : AVAssetDownloadTask?
    
    init(sessionIdentifier : String, urlString: String, assetDownloadDelegate : AVAssetDownloadDelegate) {
        guard let url = URL(string: urlString) else { return }
        asset = AVURLAsset(url: url)
        
        
        configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier)
        downloadSession = AVAssetDownloadURLSession(configuration: configuration!,
                                                    assetDownloadDelegate: assetDownloadDelegate,
                                                    delegateQueue: OperationQueue.main)
            
        downLoadTask = downloadSession!.makeAssetDownloadTask(asset: asset!,
                                                                  assetTitle: sessionIdentifier,
                                                                                       assetArtworkData: nil)!
        downLoadTask?.resume()
        
    }
    
    func cancelDownloading(){
        downLoadTask?.cancel()
        downLoadTask = nil
    }
}

 

보시는 것과 같이 AVAssetDownLoadTask도 결국은 URLSession의 자식입니다.
따라서 URLSession을 쓰는 방법과 매우 비슷합니다.

코드를 보시면 아시겠지만 굉장히 단순합니다.
여기서 제일 중요한건
AVAssetDownloadDelegate 인데요. 이 델리게이트에는 다음과 같은 함수가 있습니다.

  • func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL)

여기서 session.configuration.identifier를 통하여 위에서 세팅한 sessionIndentifier를 가져올 수 있습니다.
한번에 여러개를 부른다고 가정할때 필요한 옵션입니다.
그리고 중요한것이 didFinishDownloadingTo location: URL입니다.

애초에 요놈 때문에 글을 쓰게 된 이유도 있어요.

didFinishDownloadingTo location이 저장되는 위치를 찍어보신 분들은 아시겠지만아래와 같은 3가지 특징을 가지고 있습니다.
1. 앱의 sandbox환경에 데이터가 위치됩니다.
2.중간에 UUID가 앱을 실행할때마다 바뀌는데 이로 인해 같은 파일을 받더라도 다른 directory에 생성되어 중복 캐싱됩니다.
3.삭제를 하려고 해도 앱의 sandbox 디렉토리 및 파일에 대한 권한이 없기때문에 파일 하나하나 삭제 및 수정하기가 어렵습니다.

따라서 앱내에서 자유롭게 관리하고 싶다!라고 하시는 분들은 필수적으로 다운로드 밥은 path를 관리할 수 있는 곳으로 옮겨주셔야 합니다.

2 - 1. moveDownloadedData 

어김없이 코드 먼저 보여드리고 가겠습니다.



extension AVAssetDownloadManager : AVAssetDownloadDelegate {
    func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
        if let identifier = session.configuration.identifier {
            moveDownloadedData(from: location, to: identifier)
            if activeDownloadSession[identifier] != nil {
                activeDownloadSession.removeValue(forKey: identifier)
            }
            else {
                // print("[AVAssetDownloadManager] activeSession for \(identifier) doesnt exist")
            }
            if let reservecallback = reservedCallback[identifier] {
                if let cachedUrl = getCachedData(with: identifier) {
                    reservecallback(identifier,cachedUrl)
                    reservedCallback.removeValue(forKey: identifier)
                }
            }
        }
        else {
            // print("[AVAssetDownloadManager] cant resolve identifier")
        }
    }

	private func moveDownloadedData(from : URL, to : String) {
        guard let dirPathURL = dirPathURL else { return }
        guard let destinationPath = self.getFileNameFromUrl(url: to) else { return }
        let destinationUrl = dirPathURL.appendingPathComponent("\(destinationPath)")
        
        do {
            try FileManager.default.moveItem(at: from, to: destinationUrl)
        }
        catch(let error) {
           debugLog(" cache move error \(error.localizedDescription)")
        }
    }
}

 

일단 말씀드린바와 같이 AVAssetDownloadDelegate를 상속받으셔서 이벤트를 받으셔야 합니다.
그리고 이벤트를 받을때 moveDownloadedData(from:,to)함수를 이용하여 저희가 컨트롤할 수 있는 디렉토리로 데이터를 옮깁니다.
쉽죵?

사실 이러면 AVAssetDownLoadTask 쓰는 방법 끝입니다.ㅎㅎ

이제 남은 건 directory를 생성하고, download받기전에 캐싱되어 있는지 판단하고, cache Hit처리, cache 삭제 등 
AVAssetDownLoadTask과는 어떻게 보면 무관한 것들만 남아 있습니다.

깃헙레포지터리입니다.