Coder Social home page Coder Social logo

celestial's Introduction

Celestial

CI Status Version License Platform

Celestial is an in-app cache manager that allows you to easily cache both videos and images. You can use built-in UIViews URLImageView and URLVideoPlayerView to quickly display cached images and videos respectively. These two UIView classes provide flexible options such as determing where the cached image or video will be stored: in memory or in the local file system.



In this small demonstration, scrolling through requires each image to constantly be re-fetched, which results in redundant API calls and UI issues with flickering cells

Table of Contents


  • Xcode 8.0 or higher
  • iOS 10.0 or higher

Celestial is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Celestial'

For caching images, use the URLImageView which is a subclass of the default UIImageView . The URLImageView can be initialized with a URL pointing to the image or this image can be manually loaded.

The first initializer accepts a sourceURLString: String which is the absoluteString of the URL at which the image file is located. Both initializers share 2 arguments:

  • delegate: The URLCachableViewDelegate notifies the receiver of events such as download completion, progress, errors, etc.
  • cacheLocation: The ResourceCacheLocation determines where the downloaded image will be stored upon completion. By default, images are stored inMemory. Images or videos stored with this setting are not persisted across app sessions and are subject to automatic removal by the system if memory is strained. Storing with .fileSystem will persist the images across app sessions until manually deleted. However, for images, caching to memory is often sufficient. Set to .none for no caching
import Celestial

let sourceURLString = <your URL string>
let imageView = URLImageView(sourceURLString: sourceURLString, delegate: nil, cacheLocation: .inMemory)

// Will automatically load the image from the sourceURLString, and cache the downloaded image
// in a local NSCache, for reuse later



Caching images in UICollectionViewCells and UITableViewCells is slightly different. In such cases, the URLImageView needs to be initialized first and the urlString will likely be provided some time later as the cell is dequeued.

In such cases, use the func loadImageFrom(urlString:) function:

struct CellModel {
    let urlString: String
}

class ImageCell: UICollectionViewCell {

    // Initialize the URLImageView within the cell as a variable.
    // NOTE: The second initializer is used which does NOT have the urlString argument.
    private var imageView: URLImageView = {
        let img = URLImageView(delegate: nil, cacheLocation: .inMemory)
        img.translatesAutoresizingMaskIntoConstraints = false
        return img
    }()
    
    
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addSubview(imageView)
        
        // Handle layout...
    }
    
    // This function is called during the cell dequeue process and will load the image
    // using the `CellModel` struct. However, this would be replaced with your method.
    public func configureCell(someCellModel: CellModel) {
        imageView.loadImageFrom(urlString: someCellModel.urlString)
    }

}

There are three possible events that occur when downloading images and videos:

  • The download completes successfully
  • The download is currently in progress
  • An error occurred whil downloading the image or video

Both URLImageView and URLVideoPlayerView offer different ways to observe these events:

With delegation:

Extend the URLCachableViewDelegate to be notified of such events

let sourceURLString = <your URL string>
let imageView = URLImageView(sourceURLString: sourceURLString, delegate: self, cacheLocation: .inMemory)

...


extension ViewController: URLCachableViewDelegate {
    
    func urlCachableView(_ view: URLCachableView, didFinishDownloading media: Any) {
        // Image has finished downloading and will be cached to specified cache location
    }
    
    func urlCachableView(_ view: URLCachableView, downloadFailedWith error: Error) {
        // Investigate download error
    }
    
    func urlCachableView(_ view: URLCachableView, downloadProgress progress: Float, humanReadableProgress: String) {
        // Update UI with download progress if necessary
    }
}

With completion blocks

The other option is receive these events using in-line completion blocks

let imageView = URLImageView(delegate: nil, cacheLocation: .inMemory, defaultImage: nil)

let sourceURLString = <your URL string>

imageView.loadImageFrom(urlString: sourceURLString,
progressHandler: { (progress) in
    print("current downlod progress: \(progress)")
}, completion: {
    print("Image has finished loading")
}) { (error) in
    print("Error loading image: \(error)")
}



Similar to caching and displaying images, the URLVideoPlayerView will display videos and cache them later for reuse. It encapsulates the usually tedious process or creating instances of AVAsset, AVPlayerItem, AVPlayerLayer and AVPlayer . Instead, all you need to do is provide a URL for it play.

If you have read the Caching Images section, the initializers and functions are virtually identical between URLImageView and URLVideoPlayerView

let sourceURLString = <your URL string>
let videoView = URLVideoPlyerView(sourceURLString: sourceURLString, delegate: nil, cacheLocation: .fileSystem)

In this example, the video will be played and cached to the local file system. NOTE Caching to the local system will persist the image or video across app sessions until manually deleted.

Caching videos in cells

As previously mentioned, the functions provided in URLVideoPlayerView are virtually identical to those of URLImageView

public func configureCell(someCellModel: CellModel) {
    playerView.loadVideoFrom(urlString: someCellModel.urlString)
}

struct CellModel {
    let urlString: String
}

class VideoCell: UICollectionViewCell {

    private var playerView: URLVideoPlayerView = {
        // Observe events with delegation...
        let v = URLVideoPlayerView(delegate: self, cacheLocation: .fileSystem)
        v.translatesAutoresizingMaskIntoConstraints = false
        v.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        return v
    }()
    
    
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addSubview(playerView)
        
        // Handle layout...
    }
    
    // This function is called during the cell dequeue process and will load the image
    // using the `CellModel` struct. However, this would be replaced with your method.
    public func configureCell(someCellModel: CellModel) {
        
        // Or with completion handlers...
        
        playerView.loadVideoFrom(urlString: someCellModel.urlString, 
        progressHandler: { (progress) in
            print("current downlod progress: \(progress)")
        }, completion: {
            print("Video has finished loading")
        }) { (error) in
            print("Error loading video")=
        }
    }
}

Check out the full documentation here

To run the example project, clone the repo, and run pod install from the Example directory first.

Author

ChrishonWyllie, [email protected]

License

Celestial is available under the MIT license. See the LICENSE file for more info.

celestial's People

Contributors

8secz-johndpope avatar chrishonwyllie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

celestial's Issues

Include a CHANGELOG

A standard CHANGLOG file for labeling all major changes with each release

XIB support

Is your feature request related to a problem? Please describe.
there are a lot of cases when you setup UIImageView inside xib file but your lib forbids that

Describe the solution you'd like
support xibs. Currently your lib fails on:

required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

Describe alternatives you've considered
Alternative libraries support that

constructFormattedURL / needs a rethink

When I was using digger - it used this technique to simplify the name of url.
N.B. it's using md5.

static func tempPath(url: URL ) -> String {

    return url.absoluteString.md5.tmpDir
}

internal func constructFormattedURL(from sourceURL: URL, expectedDirectoryURL: URL, size: CGSize?) -> URL {

    // size: CGSize(width: 327.0, height: 246.0)
    // image name 1: URL: https://picsum.photos/id/0/5616/3744 -> becomes -> 3744-size-327.0-246.0 (no extension)
    // image name 2: URL: https://server/url/to/your/image.png -> becomes -> image-size-327-0-246-0.png
    
    var formattedFileName = sourceURL.localUniqueFileName()
    let fileExtension = sourceURL.pathExtension
    
    if let size = size {
        let width = String(describing: size.width).replacingOccurrences(of: ".", with: "-")
        let height = String(describing: size.height).replacingOccurrences(of: ".", with: "-")
        let sizePathComponent: String = "-size-\(width)-\(height)"
        
        formattedFileName += sizePathComponent
    }
    
    let sizeFormattedURL = expectedDirectoryURL
        .appendingPathComponent(formattedFileName)
        .appendingPathExtension(fileExtension)
    
    DebugLogger.shared.addDebugMessage("\(String(describing: type(of: self))) - Constructing formatted URL: \(sizeFormattedURL)")
    return sizeFormattedURL. /// WE NEED TO USE md5 here.
}

urlVideoPlayerIsReadyToPlay :Optional("https://d21m91m763fb64.cloudfront.net/videos-360p/a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4")
2021-07-26 11:14:09.726182+1000 8secondz[3502:1036162] DownloadTaskManager - finished download for url: https://d21m91m763fb64.cloudfront.net/videos-360p/a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4 - local file name: https:__d21m91m763fb64.cloudfront.net_videos-360p_a9731a30-a15f-11eb-8337-67b105f10dfa-. DownloadTask: BackgroundDownloadTask <14777A73-6827-45B1-BFC4-D3B32376EA46>.<1>. Temporary file location: file:///private/var/mobile/Containers/Data/Application/ABAE01F4-0A9D-487E-A39F-606A81A234FA/Library/Caches/com.apple.nsurlsessiond/Downloads/app.8secondz.ios/CFNetworkDownload_EuG8OL.tmp
2021-07-26 11:14:09.728414+1000 8secondz[3502:1036162] FileStorageManager - Attempting to delete file for path: /private/var/mobile/Containers/Data/Application/ABAE01F4-0A9D-487E-A39F-606A81A234FA**/tmp/https:__d21**m91m763fb64.cloudfront.net_videos-360p_a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4
2021-07-26 11:14:09.731695+1000 8secondz[3502:1036162] DownloadTaskManager - Moved to intermediate temporary file url: file:///private/var/mobile/Containers/Data/Application/ABAE01F4-0A9D-487E-A39F-606A81A234FA/tmp/https:__d21m91m763fb64.cloudfront.net_videos-360p_a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4
2021-07-26 11:14:09.733670+1000 8secondz[3502:1036162] FileStorageManager - Constructing formatted URL: file:///var/mobile/Containers/Data/Application/ABAE01F4-0A9D-487E-A39F-606A81A234FA/Documents/Celestial/CachedVideos/https:__d21m91m763fb64.cloudfront.net_videos-360p_a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4
2021-07-26 11:14:09.734680+1000 8secondz[3502:1036162] FileStorageManager - Attempting to delete file for path: /var/mobile/Containers/Data/Application/ABAE01F4-0A9D-487E-A39F-606A81A234FA/Documents/Celestial/CachedVideos/https:__d21m91m763fb64.cloudfront.net_videos-360p_a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4
2021-07-26 11:14:09.740544+1000 8secondz[3502:1036162] Optional - Error storing the downloaded video to the file cache Error Domain=NSCocoaErrorDomain Code=4 "“https/__d21m91m763fb64.cloudfront.net_videos-360p_a9731a30-a15f-11eb-8337-67b105f10dfa-.mp4” couldn’t be moved to “CachedVideos” because either the former doesn’t exist, or the folder containing the latter doesn’t exist."

Update the README

Include things like minimum iOS deployment target, pictures, videos and maybe an icon (space related)

Clarification on saving / storing videos to user documents.

background -
I've still got errors with files moving around - but I wonder why we would want to move the videos to the users' documents, as this will be backed up????!!! Of course, the videos can deleted on each run - but still - it's possible the videos will be uploaded / backed up.
This is not desirable.

Optional

  • some : Error Domain=NSCocoaErrorDomain Code=4 "“3d670aed625fd22d2bee581128a1477f.mp4” couldn’t be moved to “CachedVideos” because either the former doesn’t exist, or the folder containing the latter doesn’t exist." UserInfo={NSSourceFilePathErrorKey=/private/var/mobile/Containers/Data/Application/54D5E245-B9D6-424B-A5A8-B61B2FF3A53F/tmp/3d670aed625fd22d2bee581128a1477f.mp4, NSUserStringVariant=(
    Move
    ), NSDestinationFilePath=/var/mobile/Containers/Data/Application/54D5E245-B9D6-424B-A5A8-B61B2FF3A53F/Documents/Celestial/CachedVideos/3d670aed625fd22d2bee581128a1477f.mp4, NSFilePath=/private/var/mobile/Containers/Data/Application/54D5E245-B9D6-424B-A5A8-B61B2FF3A53F/tmp/3d670aed625fd22d2bee581128a1477f.mp4, NSUnderlyingError=0x282d30690 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

UPDATE - I definitely have the file - 3d670aed625fd22d2bee581128a1477f.mp4
not sure what's going wrong...

could it be a sandbox issue? isn't the tmp folder sufficient for housing this content?

downloaded file with local file name: 3d670aed625fd22d2bee581128a1477f exists in file system directory: file:///private/var/mobile/Containers/Data/Application/54D5E245-B9D6-424B-A5A8-B61B2FF3A53F/tmp/. Directory contents: ["httpsd21m91m763fb64.cloudfront.netvideos-540p4595f3a0-4c4d-11eb-b966-11af6d636bea-.mp4", "httpsd21m91m763fb64.cloudfront.netvideos-540pa1f55040-8a1f-11eb-8bc7-65134f4b7bab-.mp4", "3d670aed625fd22d2bee581128a1477f.mp4", "httpsd21m91m763fb64.cloudfront.netvideos-540p19f1efb0-8a1e-11eb-8bc7-65134f4b7bab-.mp4", "https:__d21m91m763fb64.cloudfront.net_videos-540p_d9379510-903f-11eb-9a88-f5ff80ee2719-.mp4", "https:__d21m91m763fb64.cloudfront.net_videos-540p_9edc1cd0-a9f1-11eb-b98a-69bcf457ca9a-.mp4", "httpsd21m91m763fb64.cloudfront.netvideos-540pfd292d70-89f1-11eb-bbdc-672a9fc57f81-.mp4", "ecc055b46ee87f987d14cb00a75a0d68.mp4", "httpsd21m91m763fb64.cloudfront.netvideos-540pe4b390e0-8560-11eb-822b-3bb0ffb6770c-.mp4", "stack-logs.3879.10a42c000.8secondz.g3", "28ed6

Investigate Data compression

Compressing Data requires iOS 13.0, which may be too restricting.
Additionally, it seems that compressing Data results in a larger file so it seems unnecessary given the original file size

Documentation on prefetching.

extension TheatreVideoVC: UICollectionViewDataSourcePrefetching {
    // TikTok eager download - fetch 5 movies as soon as possible on app start
    // get the next X videos /  start downloading them...
    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {

if indexPaths.count >= 1 {
                    let indexPath = indexPaths[0]
                    let idx0 = IndexPath(row: indexPath.row, section: 0)
                    let idx1 = IndexPath(row: indexPath.row+1, section: 0)
                    let idx2 = IndexPath(row: indexPath.row+2, section: 0)
                    let idx3 = IndexPath(row: indexPath.row+3, section: 0)
                    let idx4 = IndexPath(row: indexPath.row+4, section: 0)
                    let prefetchIndexPaths = [idx0, idx1, idx2, idx3, idx4]
                    for idx in prefetchIndexPaths {
                        self.prefetchItem(idx: idx)
                    }
                } 
    }

private func prefetchItem(idx: IndexPath) {
        print("INFO:prefetchItem \(idx)")
        if self.feedCollectionView != nil {
            if self.feedCollectionView.isValid(indexPath: idx) {
                if let video = self.theatreDataSource?.itemIdentifier(for: idx) as? Video {
                     Celestial.shared.startDownload(for: video.url)
                }
            }
        }
    }

I used this mistakenly Celestial.shared.prefetchResources(at: [url]) - and performance was crap. Was prepared to throw in bin - but when I switched to simple Celestial.shared.startDownload(for: video.url) - it's all good AND smooth. I think it's the sync calls in prefetch that cause scrolling to come unstuck.

Fix uncaught bug where downloaded video files would not override existing ones

When a video has been downloaded and ready to be cached to the file system, it first determines if the video should be exported a lower quality.

If not, the downloaded file is expected to be moved from its temporary download location (which is apparently random and provided by URLSession DownloadTask) to a more permanent location.

However, simply moving the file has the unintended possibility of collision. You can't move an item to a URL that already contains an item as evidenced by this error:

Error Domain=NSCocoaErrorDomain Code=516 "{FILE_NAME}"
couldn’t be moved to “{DIRECTORY_NAME}” because an item with the same name already exists." 
UserInfo={NSSourceFilePathErrorKey={FILE_PATH}, NSUserStringVariant=(
    Move
), 
NSDestinationFilePath={DESTINATION_FILE_PATH}, NSFilePath={TEMPORARY_FILE_PATH}, NSUnderlyingError=0x280871860 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}}

First check if the file exists or delete any files at that path before using the moveItem function

Add disclaimer for insecure URLs

Non secure URLs such as http://..... will not be loaded unless the developer adds the App Transport Security Key to their Info.plist.

This should be explained in proper detail

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.