UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: AVPlayer streaming from downloading file

Forums > Swift

I am currently downloading and caching a video file with the following CacheManager:

public enum Result<T> {
    case success(T)
    case failure
}

class CacheManager {

    static let shared = CacheManager()
    private let fileManager = FileManager.default
    private lazy var mainDirectoryUrl: URL = {

    let documentsUrl = self.fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
        return documentsUrl
    }()

    func getFileWith(stringUrl: String, completionHandler: @escaping (Result<URL>) -> Void ) {
        let file = directoryFor(stringUrl: stringUrl)

        //return file path if already exists in cache directory
        guard !fileManager.fileExists(atPath: file.path)  else {
            completionHandler(Result.success(file))
            return
        }

        DispatchQueue.global().async {

            if let videoData = NSData(contentsOf: URL(string: stringUrl)!) {
                videoData.write(to: file, atomically: true)

                DispatchQueue.main.async {
                    completionHandler(Result.success(file))
                }
            } else {
                DispatchQueue.main.async {
                    completionHandler(Result.failure)
                }
            }
        }
    }

    private func directoryFor(stringUrl: String) -> URL {
        let fileURL = URL(string: stringUrl)!.lastPathComponent
        let file = self.mainDirectoryUrl.appendingPathComponent(fileURL)

        return file
    }
}

And injecting the AVPlayer to my View in this way:

CacheManager.shared.getFileWith(stringUrl: video.url) { result in

                switch result {
                case .success(let url):
                    self.player = AVPlayer(url: url)
                    self.player?.play()
                    break
                case .failure:
                    self.showError.toggle()
                    break
                }
            }

But how could I stream the file file while it is downloading without waiting it for complete?

3      

If you pass the remote URL directly to the AVPlayer? Does it do anything? I remember playing remote videos in similar fashion without the need for download.

3      

@nemecek-filip it streams the video but the CacheManager class is needed for caching the video and prevent it from downloading over and over again. Furthermore streaming it directly from a Firebase Storage link with AVPlayer causes a incredibly high bandwidth usage (I do not know why).

3      

I see. Maybe this could help - https://github.com/neekeetab/CachingPlayerItem?

3      

@nemecek-filip i tried to implement CachingPlayerItem but for a reason unknown to me it always download the entire .mp4 file from the database storage...

This is my simple implementation but I think that the problem is on CachingPlayerItem class presented on the Git repo:

let videoUrl: URL = URL(string: video.url)!
let playerItem = CachingPlayerItem(url: videoUrl)
self.player = AVPlayer(playerItem: playerItem)
player?.automaticallyWaitsToMinimizeStalling = false
player?.play()

3      

I solved the problem integrating ChachingPlayerItem ( https://github.com/neekeetab/CachingPlayerItem ) with Cache ( https://github.com/hyperoslo/Cache )

import AVKit
import Cache

class VideoPlayerWorker {

    private var player: AVPlayer!
    let diskConfig = DiskConfig(name: "DiskCache")
    let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)

    lazy var storage: Cache.Storage<String, Data>? = {
        return try? Cache.Storage(diskConfig: diskConfig, memoryConfig: memoryConfig, transformer: TransformerFactory.forData())
    }()

    /// Plays a video either from the network if it's not cached or from the cache.
    func play(with url: URL) -> AVPlayer {
        let playerItem: CachingPlayerItem
        do {
            let result = try storage!.entry(forKey: url.absoluteString)
            // The video is cached.
            playerItem = CachingPlayerItem(data: result.object, mimeType: "video/mp4", fileExtension: "mp4")
        } catch {
            // The video is not cached.
            playerItem = CachingPlayerItem(url: url)
        }

        playerItem.delegate = self
        self.player = AVPlayer(playerItem: playerItem)
        self.player.automaticallyWaitsToMinimizeStalling = false
        self.player.play()
        return self.player
    }

}

// MARK: - CachingPlayerItemDelegate
extension VideoPlayerWorker: CachingPlayerItemDelegate {
    func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data) {
        // Video is downloaded. Saving it to the cache asynchronously.
        storage?.async.setObject(data, forKey: playerItem.url.absoluteString, completion: { _ in })
    }
}

5      

Hi,

So you get it to work?

How you build it, can you give more information? What kind of structure is in the app?

3      

Super helpful stuff. Just one gripe is I don't get how to get the delegate extension code to run before starting the player. I end up having to tap on an audio twice for it to play; I guess this means that the AVPlayer can only play a cached item for whatever reason. I am sure I am messing something up somewhere in the usage of the delegate. Any pointers would be appreciated.

3      

can you share full code?

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.