Qasem Zreaq
Qasem Zreaq

Reputation: 1

Playing video using AVAsset player returns error "Cannot Open"

Im trying to make a cache system so that cache some videos in splash screen to have better user experience specially for first time opening the app

here's what I did in my cache manager

class CacheManager {static let shared = LocalJackpotsCacheManager()

    private let fileManager = FileManager.default
    private let cacheDirectory: URL
    private let databaseURL: URL
    private var db: OpaquePointer?
    
    private init() {
        // Set cacheDirectory to the Documents directory
        cacheDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("VideoCache")
        databaseURL = cacheDirectory.appendingPathComponent("videoCacheMetadata.sqlite")
        
        createCacheDirectoryIfNeeded()
        setupDatabase()
    }
    
    private func createCacheDirectoryIfNeeded() {
        if !fileManager.fileExists(atPath: cacheDirectory.path) {
            do {
                try fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil)
            } catch {
                print("Failed to create cache directory: \(error)")
            }
        }
    }
    
    private func setupDatabase() {
        if sqlite3_open(databaseURL.path, &db) == SQLITE_OK {
            let createTableQuery = """
            CREATE TABLE IF NOT EXISTS VideoCache (
                id TEXT PRIMARY KEY,
                path TEXT,
                lastAccessed INTEGER
            );
            """
            if sqlite3_exec(db, createTableQuery, nil, nil, nil) != SQLITE_OK {
                print("Failed to create VideoCache table")
            }
        } else {
            print("Unable to open database")
        }
    }
    
    func cacheVideo(from url: URL, forKey key: String, completion: @escaping () -> Void) {
        let hashedKey = key.sha256()
        let destinationURL = cacheDirectory.appendingPathComponent(hashedKey)
        
        if fileManager.fileExists(atPath: destinationURL.path) {
            completion() // File already cached
            return
        }
        
        let downloadTask = URLSession.shared.downloadTask(with: url) { tempURL, _, error in
            guard let tempURL = tempURL, error == nil else {
                print("Download error: \(error?.localizedDescription ?? "Unknown error")")
                completion()
                return
            }
            
            do {
                try self.fileManager.moveItem(at: tempURL, to: destinationURL)
                self.updateDatabase(with: hashedKey, filePath: destinationURL.path)
                completion() // Call completion on success
            } catch {
                print("Error saving video to cache: \(error)")
                completion()
            }
        }
        downloadTask.resume()
    }
    
    func getCachedURL(forKey key: String) -> URL? {
        let hashedKey = key.sha256()
        let fileURL = cacheDirectory.appendingPathComponent(hashedKey)
        return fileManager.fileExists(atPath: fileURL.path) ? fileURL : nil
    }
    
    private func updateDatabase(with key: String, filePath: String) {
        let updateQuery = """
        INSERT OR REPLACE INTO VideoCache (id, path, lastAccessed) VALUES (?, ?, ?);
        """
        var statement: OpaquePointer?
        
        if sqlite3_prepare_v2(db, updateQuery, -1, &statement, nil) == SQLITE_OK {
            let timestamp = Int(Date().timeIntervalSince1970)
            sqlite3_bind_text(statement, 1, key, -1, nil)
            sqlite3_bind_text(statement, 2, filePath, -1, nil)
            sqlite3_bind_int(statement, 3, Int32(timestamp))
            sqlite3_step(statement)
        } else {
            print("Error preparing update statement")
        }
        
        sqlite3_finalize(statement)
    }
    
    func cleanOldCache(limit: Int) {
        let selectOldItemsQuery = """
        SELECT id, path FROM VideoCache ORDER BY lastAccessed ASC LIMIT ?;
        """
        var selectStatement: OpaquePointer?
        
        if sqlite3_prepare_v2(db, selectOldItemsQuery, -1, &selectStatement, nil) == SQLITE_OK {
            sqlite3_bind_int(selectStatement, 1, Int32(limit))
            
            while sqlite3_step(selectStatement) == SQLITE_ROW {
                let id = String(cString: sqlite3_column_text(selectStatement, 0))
                let path = String(cString: sqlite3_column_text(selectStatement, 1))
                
                // Delete the file from disk
                do {
                    try fileManager.removeItem(atPath: path)
                } catch {
                    print("Error deleting file from disk: \(error)")
                }
                
                // Remove from database
                deleteCacheEntry(withId: id)
            }
        }
        
        sqlite3_finalize(selectStatement)
    }
    
    private func deleteCacheEntry(withId id: String) {
        let deleteQuery = "DELETE FROM VideoCache WHERE id = ?;"
        var deleteStatement: OpaquePointer?
        
        if sqlite3_prepare_v2(db, deleteQuery, -1, &deleteStatement, nil) == SQLITE_OK {
            sqlite3_bind_text(deleteStatement, 1, id, -1, nil)
            sqlite3_step(deleteStatement)
        } else {
            print("Error preparing delete statement")
        }
        
        sqlite3_finalize(deleteStatement)
    }

}

and how Im trying to use it

 if let cachedFileURL = LocalJackpotsCacheManager.shared.getCachedURL(forKey: cacheKey) {
                        // Log cached file details for confirmation
                        do {
                            let data = try Data(contentsOf: cachedFileURL)
                            print("Cached file successfully loaded into Data object, size: \(data.count) bytes")
                        } catch {
                            print("Failed to load cached file into Data object: \(error.localizedDescription)")
                        }
                        // Prepare the video for playback
                        let asset = AVURLAsset(url: cachedFileURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
                        let playerItem = AVPlayerItem(asset: asset)
                        let player = AVPlayer(playerItem: playerItem)
                        
                        // Configure the cell with the player
                        cell.configure(with: player)
                        player.play() // Start playback immediately if desired
                        
                    } else {
                        // If file is not cached, download and cache it
                        LocalJackpotsCacheManager.shared.cacheVideo(from: videoURL, forKey: cacheKey) {
                            if let cachedFileURL = LocalJackpotsCacheManager.shared.getCachedURL(forKey: cacheKey) {
                                
                                DispatchQueue.main.async {
                                    // Prepare the downloaded video for playback
                                    let asset = AVURLAsset(url: cachedFileURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
                                    let playerItem = AVPlayerItem(asset: asset)
                                    let player = AVPlayer(playerItem: playerItem)
                                    
                                    cell.configure(with: player)
                                    player.play()
                                }
                            }
                        }
                    }

dk what exactly wrong with this but file loaded and printed it's size ! keep says Cannot open

try to cache and play video from local and keep fails

Upvotes: 0

Views: 29

Answers (0)

Related Questions